9
\$\begingroup\$

I've written some C++ code, intended to run on an Arduino, the purpose of which is to talk to SparkFun's Simultaneous RFID Reader.

The current situation:

  • The code runs fine, most of the time.
  • However, sometimes it throws up a "Module failed to respond..." error.
  • The above error tends to occur - perhaps only occurs - after I've made a minor change to the code, e.g. correcting spelling in a Serial.println() call, and then uploaded it to the Arduino.
  • This makes me think that the problem may be in the code. But it may not.

Extra detail:

  • The GitHub repository for SparkFun's Simultaneous RFID Tag Reader is here. It includes both source code and examples.
  • I was thinking that perhaps the problem is lurking in some sort of cache, which is not being cleared properly when new code is uploaded. (The "Module failed to respond..." issue is only really solved by completely powering the Arduino down, and then restarting.)
  • With that in mind, I tried putting in a couple of Serial.flush() statements, but this didn't seem to achieve anything useful.
  • I was thinking about perhaps re-initialising the nano object, but here my knowledge of C++ fails me.
  • Any other suggestions would be more than welcome.

The code:

// Libraries.
#include <SoftwareSerial.h>
#include "SparkFun_UHF_RFID_Reader.h"

// Constants.
#define MAX_LOOPS 10
// 100 units of power (below) = 1 dBm. Keep power < 27 dBm, i.e. 2700 units.
#define POWER 2000
#define DOT 250
#define DASH 1500
#define MIN_TWO_DIGITS_HEX 0x10
#define START_EPC 31
#define STOP_EPC '$'
#define USER_DATA_LENGTH 64

// Baud rates.
#define SOFTSERIAL_BAUD_INITIAL 115200
#define SOFTSERIAL_BAUD_NORMAL 9600
#define BAUD_A 115200
#define BAUD_B 57600

// Antennae: configure as desired.
#define ANTENNA_A true

// Status codes.
#define FAILED_TO_RESPOND 100
#define PRESS_TO_BEGIN 101
#define SCANNING 102
#define TAG_FOUND 103
#define BAD_CRC 104
#define UNKNOWN_ERROR 105
#define READING_CONTINUOUSLY 106
#define ERROR_DETAILS 107
#define SETTING_UP 108
#define SETUP_SUCCESS 109
#define TRYING_AGAIN 110
#define GIVE_UP 111

// Global variables.
SoftwareSerial softSerial(2, 3);
RFID nano;

void setup()
{
  if(ANTENNA_A == false) Serial.begin(BAUD_B);
  else Serial.begin(BAUD_A);

  while(!Serial);

  setupSoftSerial();

  nano.setRegion(REGION_NORTHAMERICA);
  nano.setReadPower(POWER);

  // Comment out the line below, as desired.
  // nano.enableDebugging();

  Serial.print(PRESS_TO_BEGIN);
  Serial.println(F("|Press a key to begin scanning for tags."));
  while(!Serial.available());
  Serial.read();

  nano.startReading();
}

void loop()
{
  byte responseType;

  if(nano.check() == true)
  {
    responseType = nano.parseResponse();

    if(responseType == RESPONSE_IS_KEEPALIVE)
    {
      Serial.print(SCANNING);
      Serial.println(F("|Scanning..."));
    }
    else if(responseType == RESPONSE_IS_TAGFOUND)
    {
      printFoundTag(nano);
    }
    else if(responseType == ERROR_CORRUPT_RESPONSE)
    {
      Serial.print(BAD_CRC);
      Serial.println("|Bad CRC.");
    }
    else
    {
      Serial.print(UNKNOWN_ERROR);
      Serial.println("|Unknown error.");
    }
  }
}

// Ronseal.
void printFoundTag(RFID nano)
{
  byte x;
  byte tagEPCBytes = nano.getTagEPCBytes();
  String epc = bytesToASCII(nano.msg, START_EPC, tagEPCBytes);
  int rssi = nano.getTagRSSI();
  long freq = nano.getTagFreq();
  long timeStamp = nano.getTagTimestamp();
  String result;

  result = String(TAG_FOUND)+"|"+epc+":"+String(rssi);

  Serial.println(result);
}

// Ronseal.
String bytesToASCII(byte *theBytes, int start, byte byteLength)
{
  String result = "";
  char letter;
  char minASCII = ' ', maxASCII = '~';
  byte theByte;

  for(byte x = 0; x < byteLength; x++)
  {
    theByte = theBytes[start+x];
    letter = char(theByte);

    if((letter < minASCII) || (letter > maxASCII)) continue;
    else if(letter == STOP_EPC) break;
    else result = result+letter;
  }

  return result;
}

// Handles a reader that is already configured and reading.
boolean setupNano()
{
  nano.begin(softSerial);
  softSerial.begin(SOFTSERIAL_BAUD_NORMAL);

  while(softSerial.isListening() == false);

  while(softSerial.available()) softSerial.read();

  nano.getVersion();

  if (nano.msg[0] == ERROR_WRONG_OPCODE_RESPONSE)
  {
    nano.stopReading();
    Serial.print(READING_CONTINUOUSLY);
    Serial.println(F("|Module reading continuously. Asking it to stop..."));
    delay(DASH);
  }
  else
  {
    softSerial.begin(SOFTSERIAL_BAUD_INITIAL);
    nano.setBaud(SOFTSERIAL_BAUD_NORMAL);
    softSerial.begin(SOFTSERIAL_BAUD_NORMAL);
    delay(DOT);
  }

  nano.getVersion();
  if(nano.msg[0] != ALL_GOOD)
  {
    handleError(nano.msg[0]);
    return false;
  }

  nano.setTagProtocol();
  nano.setAntennaPort();

  return true;
}

// Prints a message for the error generated.
void handleError(int code)
{
  Serial.print(ERROR_DETAILS);
  Serial.print("|Error with code ");
  Serial.print(code);
  Serial.print(" = ");

  if(code == ERROR_COMMAND_RESPONSE_TIMEOUT)
  {
    Serial.println("Timeout error.");
  }
  else if(code == ERROR_CORRUPT_RESPONSE)
  {
    Serial.println("Corrupt response.");
  }
  else if(code == ERROR_WRONG_OPCODE_RESPONSE)
  {
    Serial.println("Wrong opcode.");
  }
  else if(code == ERROR_WRONG_OPCODE_RESPONSE)
  {
    Serial.println("Wrong opcode.");
  }
  else if(code == ERROR_UNKNOWN_OPCODE)
  {
    Serial.println("Unknown opcode.");
  }
  else Serial.println("*Very* unknown error.");
}

// Sets up the SoftSerial, which in turns sets up the nano.
void setupSoftSerial()
{
  int count = 0;

  Serial.print(SETTING_UP);
  Serial.println(F("|Setting up SoftSerial..."));

  while(true)
  {
    if(setupNano())
    {
      Serial.print(SETUP_SUCCESS);
      Serial.println(F("|Success!"));
      break;
    }
    else
    {
      Serial.print(FAILED_TO_RESPOND);
      Serial.println(F("|Module failed to respond."));
      count++;

      if(count < MAX_LOOPS)
      {
        Serial.print(TRYING_AGAIN);
        Serial.println(F("|Trying again..."));
        continue;
      }
      else
      {
        Serial.print(GIVE_UP);
        Serial.println(F("|Give up."));
        while(true);
      }
    }
  }
}
\$\endgroup\$
  • 3
    \$\begingroup\$ For those voting to close the question by suspicion that the code doesn't work as intended: as an electrical engineer I strongly suspect this has nothing to do with the code. Read errors after a code update are somewhat common, the usual approach is to discard the first so-many values by default. \$\endgroup\$ – Mast Oct 22 at 8:11
  • \$\begingroup\$ Or force a reboot, or whatever fixes the problem. But that's an electrical limitation of firmware, not a code problem. \$\endgroup\$ – Mast Oct 22 at 8:13
  • \$\begingroup\$ To those thinking that this is a non-code issue: Do you know, then, how I would carry out the hardest possible reset within the code? Because pulling out both the power and data cables every time this error crops up isn't going to be practical in the long run. \$\endgroup\$ – Tom Hosker Oct 22 at 8:23
  • \$\begingroup\$ Well, you could wire one of your pins to the RESET pin, should probably put a switch between it. Then all you have to do is put the correct signal on that pin (it's probably active low, so you'd need to pull it down). I'd do it with a switch and a resistor, I think. \$\endgroup\$ – Mast Oct 22 at 8:28
  • \$\begingroup\$ But, if you don't like additional hardware and prefer to live dangerous, it's explained here how to do it without. \$\endgroup\$ – Mast Oct 22 at 8:35
10
\$\begingroup\$

I see some things that may help you improve your program.

The Arduino language isn't C++

The language used for the Arduino isn't quite C and isn't quite C++, so if your goal is to learn or improve your C++, you might want to be careful about the differences. As they describe it "the Arduino language is merely a set of C/C++ functions that can be called from your code." So while the underlying compiler may actually be a C++ compiler, writing Arduino sketches is not the same as writing C++ programs. Specifically, the Arduino's use of setup and loop is unique to it. Also, all of the "built-in" things such as digitalWrite and Serial are non-standard. Unlike C++, there is no user-defined main. You can still learn useful things by learning to program the Arduino, but it's important to remain aware of the differences.

Add comments for non-obvious constructs

I happen to know that this line:

while(!Serial);

is intended to wait until the serial port is successfully open, but a comment to that effect would greatly aid people reading the code.

Use better naming

Looking at your setupNano code, I came across this line:

delay(DASH);

Then I had to look up DASH to find out it was a constant equal to 1500. Why make your readers work harder? While DASH and DOT are cute, LONG_DELAY and SHORT_DELAY would be more informative.

Understand pre- versus post-increment

If we write count++ it has a different meaning than ++count. The difference is that count++ increments the value and returns the previous (unicremented) value, while ++count returns the incremented value. It's a seemingly small difference, but on many processors, ++count takes a wee bit less time and uses fewer machine instructions. Unless you need to actually keep the old value, I recommend always using the preincrement version ++count. The compiler is probably smart enough to notice that you're not subsequently using the value and optimize that away anyway, but it's good to get into good habits.

Don't hide loop exit conditions

In setupSoftSerial() we have a rather convoluted loop construct:

while(true)
{
    if(setupNano())
    {
      Serial.print(SETUP_SUCCESS);
      Serial.println(F("|Success!"));
      break;
    }
    else
    {
      Serial.print(FAILED_TO_RESPOND);
      Serial.println(F("|Module failed to respond."));
      count++;

      if(count < MAX_LOOPS)
      {
        Serial.print(TRYING_AGAIN);
        Serial.println(F("|Trying again..."));
        continue;
      }
      else
      {
        Serial.print(GIVE_UP);
        Serial.println(F("|Give up."));
        while(true);
      }
    }
}

How, in human language, would you actually describe how this loop works? You might say "repeat the loop until either the setup works or halt if it exhausts retries." That sounds more like a for loop to me:

for(retries = MAX_LOOPS; retries; --retries) 
{
    if(setupNano())
    {
      Serial.print(SETUP_SUCCESS);
      Serial.println(F("|Success!"));
      break;
    }
    else
    {
      Serial.print(FAILED_TO_RESPOND);
      Serial.println(F("|Module failed to respond."));
      Serial.print(TRYING_AGAIN);
      Serial.println(F("|Trying again..."));
    }
}
// if we're out of retries, give up
if (retries == 0) {
    Serial.print(GIVE_UP);
    Serial.println(F("|Give up."));
    while(true);
}

Note also that I have renamed the vaguely named count (count of what?) to the more descriptive retries.

Use a switch instead of a long if..else

The Arduino language, like C and C++, implements a switch control structure which could replace a number of the long if..else chains in this code. It makes it more clear to the reader that it's one condition that's being checked for multiple values and also provides for a default case for the truly unexpected.

Control the external hardware

The RFID module you're using has a hardware EN line. If you connect that line to a GPIO pin on the Arduino, you can perform a hard reset of the module by bringing that line low and releasing it.

General embedded system troubleshooting

As every programmer knows, it's very common that things sometimes don't work. When code running on a PC doesn't work, we often have fancy tools to debug and troubleshoot, but if it's an embedded system (as with your Arduino), we don't have a screen or a lot of automatic debugging logs, so we have to get a bit more creative. In this case, you say that when you upload the code to the Arduino, things no longer seem to work. Since you're using a soft UART, I'd suggest starting there. Try to figure out if the problem is with the Arduino's serial or the RFID board. One way to do that would be to reset the RFID board as mentioned above. If you figure out which half isn't working (or perhaps they just disagree on baud rates?) then you can start figuring out why things aren't working. Are spurious characters written to the RFID board during reprogramming of the Arduino? Does the software serial port cleanly and completely reset? Are the RFID and Arduino out of sync? (I.e. does the Arduino only start listening in the middle of a sent character?) An oscilloscope or logic analyzer is extremely useful for this kind of troubleshooting. You can get a software-based device that supports both for less than you paid for the RFID board. I use a BitScope but there are other similar devices out there.

\$\endgroup\$
  • 2
    \$\begingroup\$ I think saying that arduino language isn't C++ is a bit of an overstatement. It is, it's just wrapped in something that adds some includes and calls setup and loop. But all C++ stuff is valid and I can't think of any non-standard syntax. I had used constexpr expressions to populate arrays on arduino, it worked. \$\endgroup\$ – Tomáš Zato Oct 22 at 16:21
  • \$\begingroup\$ @TomášZato It's based on what Arduino has said about it. "The Arduino language is based on C/C++" but I see that they have removed that statement from their web site. They now say "the Arduino language is merely a set of C/C++ functions that can be called from your code." \$\endgroup\$ – Edward Oct 22 at 16:33
  • \$\begingroup\$ Thank you ever so much for such a helpful and thorough review. I shall certainly implement most if not all of the suggestions you've made regarding the code. The while(true) loop is embarrassing! My only defense is that it works, and that it grew piece by piece through debugging. But your way's much better. I'll get back to you about resetting the reader board in the code. Don't hold your breath, though! My company has literally just suspended this project so I can build them a website. Luckily, I'm a lot better at JavaScript than C++! \$\endgroup\$ – Tom Hosker Oct 22 at 17:23
  • \$\begingroup\$ I'm glad it was useful. With any luck you'll be able to get back to it. \$\endgroup\$ – Edward Oct 22 at 18:43
  • 1
    \$\begingroup\$ If anyone's still paying attention to this question... @Edward was absolutely right about taking control of the external hardware using a jumper wire between the EN pin on the reader and a GPIO pin on the Arduino. Using such a connection, I was able to make the reader go through a hard reset each time the setup() part of the Arduino is called. And - touch wood - the "Module failed to respond..." error hasn't cropped up since! \$\endgroup\$ – Tom Hosker Nov 5 at 12:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.