Inconsistent range on Simultaneous RFID Reader - M6E Nano with Ring Antenna

I am using a Simultaneous RFID reader with a SparkX UHF RFID ring antenna, and an ESP32. I have a heat sink on the reader and it doesn’t feel too hot to me. My goal is to have an object light up in a different color depending on who is handling it, with each individual wearing a passive tag ID card. It “works” with the attached wiring. The problem is my range is very inconsistent—sometimes it is 2ft (good enough for my application), sometimes it is only a couple inches (not good enough). Sometimes the serial monitor kicks out a lot of “Bad CRC” responses—more at the highest read power. Does anyone have advice for getting a more consistent range (or for simply increasing it)? I am using a Anker battery, 5V 2A 5000mAh to power all three: reader, ESP32, and neopixels. Thank you for your time.

/*
  Reading multiple RFID tags, simultaneously!
  By: Nathan Seidle @ SparkFun Electronics
  Date: October 3rd, 2016
  https://github.com/sparkfun/Simultaneous_RFID_Tag_Reader

  KCCR May18 2022:changed Serial5 to Serial2 and used with ESP32
  I used this example with an ESP32 (in my case the DOIT DEVKIT V1) and the hardware serial pins.
  I used Serial2, which corresponds to RX2 and TX2 on pins 16 and 17 respectively.
  The serial slide switch must be in the 'HW-UART' position

  Connections:
  Nano        ESP32
  GND         GND
  VCC         VUSB
  RXI         17 (make sure to have voltage divider /resistors in place to bump 5V logic on nano down to 3.3V for ESP32)
  TXO         16

*/

#include "SparkFun_UHF_RFID_Reader.h" //Library for controlling the M6E Nano module
#include <Adafruit_NeoPixel.h>

#define pixelPin 32
#define LED_COUNT 30
#define BRIGHTNESS 50
// When we setup the NeoPixel library, we tell it how many pixels, and which pin to use to send signals.
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(LED_COUNT, pixelPin, NEO_GRB + NEO_KHZ800);

//neopixel CONSTANTS
//RBG for different colors
const byte numColors = 5; //4 colors/chacter types, 1 neutral/white
int colors[numColors][3] = {
    {209,   0,   0}, //red
    {  0, 255,   0}, //green 
    { 78, 244, 255}, //blue
    {255, 255,   0}, //yellow
    { 10,   0,  10}, //purple-white
};
String colorNames[5] = {"Red", "Green", "Blue", "Yellow", "Purple"};

// What is the "correct" UID that should be detected by the reader
//You'll need to figure these out first run on the serial monitor
uint8_t correctUid[][12] = {
  {0xE2, 0x0, 0x0, 0x1D, 0x1, 0xA, 0x1, 0x40, 0x25, 0x10, 0x6E, 0xAA}, // Red
  {0xE2, 0x0, 0x0, 0x1D, 0x1, 0xA, 0x1, 0x69, 0x24, 0x70, 0x90, 0x50}, // Green
  {0xE2, 0x0, 0x0, 0x1D, 0x1, 0xA, 0x2, 0x34, 0x25, 0x40, 0xD2, 0xB9}, // Blue
  {0xE2, 0x0, 0x0, 0x1D, 0x1, 0xA, 0x0, 0x66, 0x26, 0x10, 0x24, 0x69}, // Yellow
  {0x0,  0x0, 0x0, 0x0,  0x0, 0x0, 0x0, 0x0,  0x0,  0x0,  0x0,  0x0 }, // Purple
};

RFID nano; //Create instance

void setup()
{
    
  
  //setup neopixels
  pixels.begin(); // This initializes the NeoPixel library.
  pixels.show();
  pixels.setBrightness(BRIGHTNESS);
    
  //setup nano
  Serial.begin(115200);
  while (!Serial); //Wait for the serial port to come online
  
  // Print out the file and the date at which it was last compiled
  Serial.println(__FILE__ __DATE__);
  
  //Because we are using a hardware serial port in this example we can
  //push the serial speed much faster to 115200bps
  if (setupNano(115200) == false) //Configure nano to run at 115200bps
  {
    Serial.println(F("Module failed to respond. Please check wiring."));
    while (1); //Freeze!
  }

  nano.setRegion(REGION_NORTHAMERICA); //Set to North America

  nano.setReadPower(2700); //5.00 dBm. Higher values may caues USB port to brown out
  //Max Read TX Power is 27.00 dBm and may cause temperature-limit throttling

  //Eventually replace key-press start with MSQTT start from node red
  //Serial.println(F("Press a key to begin scanning for tags."));
  //while (!Serial.available()); //Wait for user to send a character
  //Serial.read(); //Throw away the user's character

  nano.startReading(); //Begin scanning for tags
}

void loop()
{
  if (nano.check() == true) //Check to see if any new data has come in from module
  {
    byte responseType = nano.parseResponse(); //Break response into tag ID, RSSI, frequency, and timestamp

    if (responseType == RESPONSE_IS_KEEPALIVE)
    {
      Serial.println(F("Scanning"));
    }
    else if (responseType == RESPONSE_IS_TAGFOUND)
    {
      //If we have a full record we can pull out the fun bits
      int rssi = nano.getTagRSSI(); //Get the RSSI for this tag read. 
      //RSSI is signal strength -100(weak) to 0(strong). Can be used as a proxy for distance

      //long freq = nano.getTagFreq(); //Get the frequency this tag was detected at

      //long timeStamp = nano.getTagTimestamp(); //Get the time this was read, (ms) since last keep-alive message

      byte tagEPCBytes = nano.getTagEPCBytes(); //Get the number of bytes of EPC from response
      uint8_t tagEPC[tagEPCBytes];
      for (byte x=0 ; x<tagEPCBytes ; x++)
      {
        tagEPC[x] = nano.msg[31+x];
      }

      Serial.print(F(" rssi["));
      Serial.print(rssi);
      Serial.print(F("]"));

      /*
      Serial.print(F(" freq["));
      Serial.print(freq);
      Serial.print(F("]"));

      Serial.print(F(" time["));
      Serial.print(timeStamp);
      Serial.print(F("]"));
      */

      //Print EPC bytes, this is a subsection of bytes from the response/msg array
      Serial.print(F(" epc["));
      for (byte x = 0 ; x < tagEPCBytes ; x++)
      {
        if (nano.msg[31 + x] < 0x10) Serial.print(F("0")); //Pretty print
        Serial.print(nano.msg[31 + x], HEX);
        Serial.print(F(" "));
      }
      Serial.print(F("]"));

      //check if tag matches one in our predefined set
      for (int k=0; k<numColors; k++) {
        if(memcmp(tagEPC, correctUid[k], 12) == 0) {
          Serial.print(colorNames[k]);
          pixels.fill(pixels.Color(colors[k][0], colors[k][1], colors[k][2]));
          pixels.show();
        }
      }

      Serial.println();
    }
    else if (responseType == ERROR_CORRUPT_RESPONSE)
    {
      Serial.println("Bad CRC");
    }
    else
    {
      //Unknown response
      Serial.print("Unknown error");
    }
  }
}

//Gracefully handles a reader that is already configured and already reading continuously
//Because Stream does not have a .begin() we have to do this outside the library
boolean setupNano(long baudRate)
{
  //nano.enableDebugging(Serial); //Print the debug statements to the Serial port
  
  //nano.begin(softSerial); //Tell the library to communicate over software serial port
  nano.begin(Serial2); //Tell the library to communicate over ESP32 Serial Port # 2 (pins 16/17)

  //Test to see if we are already connected to a module
  //This would be the case if the Arduino has been reprogrammed and the module has stayed powered
  
  //Software serial
  //softSerial.begin(baudRate); //For this test, assume module is already at our desired baud rate
  //while (softSerial.isListening() == false); //Wait for port to open

  //Hardware serial
  Serial2.begin(baudRate); //For this test, assume module is already at our desired baud rate
  while(!Serial2);

  //About 200ms from power on the module will send its firmware version at 115200. We need to ignore this.
  //while (softSerial.available()) softSerial.read();
  while (Serial2.available()) Serial2.read();

  nano.getVersion();

  if (nano.msg[0] == ERROR_WRONG_OPCODE_RESPONSE)
  {
    //This happens if the baud rate is correct but the module is doing a ccontinuous read
    nano.stopReading();

    Serial.println(F("Module continuously reading. Asking it to stop..."));

    delay(1500);
  }
  else
  {
    //The module did not respond so assume it's just been powered on and communicating at 115200bps
    //softSerial.begin(115200); //Start software serial at 115200
    Serial2.begin(115200); //Start hw serial at 115200

    nano.setBaud(baudRate); //Tell the module to go to the chosen baud rate. Ignore the response msg

    //softSerial.begin(baudRate); //Start the software serial port, this time at user's chosen baud rate
    Serial2.begin(baudRate); //Start the serial port, this time at user's chosen baud rate

    delay(250); //this isn't in the software serial example, don't know why
  }

  //Test the connection
  nano.getVersion();

  if (nano.msg[0] != ALL_GOOD) return (false); //Something is not right

  //The M6E has these settings no matter what
  nano.setTagProtocol(); //Set protocol to GEN2

  nano.setAntennaPort(); //Set TX/RX antenna ports to 1

  return (true); //We are ready to rock
}

How do you power the Nano M6E? I would advise applying the 5V directly on the separate 3.7 - 5V connector of the NANO M6E and NOT taking the power through the ESP32. Maybe you need to cut the link on the M6E to NOT use VCC from the ESP32.

Maybe also try to lower the speed to 57600.

Thank you Paul. I’m still chasing this down to some extend, but for future people who find this thread I wanted to update:

–Securing the antenna cable with hot glue seemed to help quite a lot

–Lowering the speed wasn’t successful

Re: power, it is connected directly to the battery. The ESP is also connected directly to the same battery, though, so maybe it is trying to power the board versus receive power…that would be unfortunate. I should isolate them to different power sources and check; however, since my problem was greatly improved by the antenna fix and is functioning fine, I haven’t been motivated to pursue it further and chase down the final "Bad CRC"s.