Update to Arduino Serial Input Basics Example #5

Hi, here's a little addition to the wonderful sketch Example 5 found in Serial Input Basics - Updated by Robin

I've updated it to make it more bulletproof. What I noticed was, in the original sketch if you send data in the wrong format, the next time you run it by inputting the data exactly as expected, the previous data is still there in the output message. I want to only see the output between the <> markers, because I want to use this for commands between a Teensy and a Processing app.

If data is entered like this:

<hi

Then I want nothing to happen

If <hi, 23

Again, nothing

But if <hi,23>

Then I want to see:

Message hi
Integer 23

if <hi> is entered, I want to see an error.

Basically the whole goal was to get it so that only entries beginning with < and ending with > would be recognized, and if data was entered between <> incorrectly, then I want to see an error message

All this, because I want to get reliable messages from my Teensy to a Processing app, and if the data is incorrect then it could cause problems.

I'm sure it could be better, so suggestions are welcome.

Thanks,

Mike

const byte numChars = 32;
char receivedChars[numChars];
char tempChars[numChars];// temporary array for use when parsing

// variables to hold the parsed data
char command[numChars] = {0};
int intRequested = 0;
//float floatFromPC = 0.0;

boolean messageReceived = false;

bool commandStarted = false;

String ErrorMessage = "";
String ErrNumber = 0;

void setup() {
    Serial.begin(9600);
    Serial.println("This demo expects 2 pieces of data - text, and an integer");
    Serial.println("Enter data in this style <Hi, 2>");
    Serial.println();
}

void loop() {
    recvWithStartEndMarkers();
    if (messageReceived) {
        strcpy(tempChars, receivedChars);
            // this temporary copy is necessary to protect the original data
            //   because strtok() used in parseData() replaces the commas with \0
        parseData();
        showParsedData();
        messageReceived = false;
    }  

}//end void loop

void recvWithStartEndMarkers() {
    static boolean recvInProgress = false;
    static byte ndx = 0;
    char startMarker = '<';
    char endMarker = '>';
    char rc;
    unsigned long TimeOut = 500;

    unsigned long ReceiveStart = millis();
    unsigned long ReceiveInterval = 0;

    while (Serial.available() > 0 && messageReceived == false) {

      ReceiveInterval = millis() - ReceiveStart;

      if (ReceiveInterval < TimeOut) {

        rc = Serial.read();
        if (commandStarted && rc == startMarker ) {
          ndx = 0;
          recvInProgress = false;
          commandStarted = false;
        }
        if ((ndx == numChars - 1) && rc != endMarker) {
          ndx = 0;
          recvInProgress = false;
          commandStarted = false;
        }
        
        if (recvInProgress == true) {
            if (rc != endMarker) {
                receivedChars[ndx] = rc;
                ndx++;
                if (ndx >= numChars) {
                    ndx = numChars - 1;
                }
            }
            else {
                receivedChars[ndx] = '\0'; // terminate the string
                recvInProgress = false;
                ndx = 0;
                messageReceived = true;
                //Serial.println("Command Ended!"); // ENABLE TO SEE WHEN CORRECTLY FORMATTED COMMAND IS COMPLETED
            }
        }

        else if (rc == startMarker) {
            recvInProgress = true;
            commandStarted = true;
            //Serial.println("Command Started!"); // ENABLE TO SEE WHEN CORRECTLY FORMATTED COMMAND STARTED
        }
    }//if not timeout
    else {
      ErrorOccurred("Time Out");
    }

    }
}//end void

void parseData() {      // split the data into its parts

  char * strtokIndx; // this is used by strtok() as an index

    strtokIndx = strtok(tempChars,",");      // get the first part - the string
    if (strtokIndx != NULL) {
      strcpy(command, strtokIndx); // copy it to command
    }
    else {
      ErrorOccurred("String missing");
    }
 
    strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
    if (strtokIndx != NULL) {
      intRequested = atoi(strtokIndx);// convert this part to an integer
    }
    else {
      intRequested = 999;
      ErrorOccurred("Integer missing");
      
    }
    // floatFromPC = atof(strtokIndx);     // convert this part to a float

}

void ErrorOccurred(String ErrMessage) {
  char ErrorMessage[4] = "ERR"; // Get a basic error message
  strcpy(command, ErrorMessage); 
  Serial.println(ErrMessage);
}

void showParsedData() {

  Serial.println();
  Serial.print("Message ");
  Serial.println(command);
  Serial.print("Integer ");
  Serial.println(intRequested);
  Serial.println();
  // Serial.print("Float ");
  // Serial.println(floatFromPC);
}

[sterretje edit]
fixed minor mistake with code tags
[/edit]

1 Like

What a shame that you had to go and spoil it all by using Strings

3 Likes

Edited out a stupid mistake using a String for a number

Strings were used in the error messages...but the point was to be able to use input strings so the commands are human readable. Could you be more specific about your objection?

I've updated the code to be even more robust, now showing timeout if message takes too long...

const byte numChars = 32;
char receivedChars[numChars];
char tempChars[numChars];// temporary array for use when parsing

// variables to hold the parsed data
char command[numChars] = {0};
int intRequested = 0;
//float floatFromPC = 0.0;

bool messageReceived = false;
bool messageStarted = false;

bool ParsingError = false;

unsigned long TimeOut = 500;
unsigned long ReceiveStart = millis();
unsigned long ReceiveInterval = 0;

bool messageDisplayed = false;

String ErrorMessage = "";
int ErrNumber = 0;

void setup() {
    Serial.begin(9600);
    Serial.println("This demo expects 2 pieces of data - text, and an integer");
    Serial.println("Enter data in this style <Hi, 2>");
    Serial.println();
}

void loop() {
    recvWithStartEndMarkers();
    if (messageReceived) {
        strcpy(tempChars, receivedChars);
            // this temporary copy is necessary to protect the original data
            //   because strtok() used in parseData() replaces the commas with \0
        parseData();
        showParsedData();
        messageReceived = false;
    }
    if ((messageStarted && !messageReceived)) { 
                   
      ReceiveInterval = millis() - ReceiveStart;

      if ((ReceiveInterval > TimeOut)) {
          if (!ParsingError) ErrorOccurred("Message Timed Out!");
          ParsingError = false;              
          ReceiveInterval = 0;
          ReceiveStart = 0;
          messageStarted = false;
      }
        
    }//if messageStarted && !messageReceived 

}//end void loop

void recvWithStartEndMarkers() {
    static boolean recvInProgress = false;
    static byte ndx = 0;
    char startMarker = '<';
    char endMarker = '>';
    char rc;

    while (Serial.available() > 0 && !messageReceived) {  

        rc = Serial.read();
        //Serial.println(rc);

        if ((recvInProgress && rc == startMarker) ||  ((ndx == numChars - 1) && rc != endMarker)) {
          ndx = 0;
          recvInProgress = false;
          messageStarted = false;
        }

        if (recvInProgress == true) { 
            if (rc != endMarker) {
                receivedChars[ndx] = rc;
                ndx++;
                if (ndx >= numChars) {
                    ndx = numChars - 1;
                }
            }
            else {
                receivedChars[ndx] = '\0'; // terminate the string
                recvInProgress = false;
                ndx = 0;
                messageReceived = true;
                ReceiveStart = 0;//reset the var so it doesn't get too big
                //Serial.println("Command Ended!"); // ENABLE TO SEE WHEN CORRECTLY FORMATTED COMMAND IS COMPLETED
            }
        }

        else if (rc == startMarker) {
            recvInProgress = true;
            messageStarted = true;
            ReceiveStart = millis();
            //Serial.println("Command Started!"); // ENABLE TO SEE WHEN CORRECTLY FORMATTED COMMAND STARTED
        }

    }//while serial available

}//end void

void parseData() {      // split the data into its parts

  char * strtokIndx; // this is used by strtok() as an index

  ParsingError = false;

    strtokIndx = strtok(tempChars,",");      // get the first part - the string
    if (strtokIndx != NULL) {
      strcpy(command, strtokIndx); // copy it to command
    }
    else {
      ErrorOccurred("String missing");
      ParsingError = true;
    }
 
    strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
    if (strtokIndx != NULL) {
      intRequested = atoi(strtokIndx);// convert this part to an integer
    }
    else {
      intRequested = 999;
      ErrorOccurred("Integer missing");
      ParsingError = true;
      
    }
    // floatFromPC = atof(strtokIndx);     // convert this part to a float

}//end void ParseData

void ErrorOccurred(String ErrMessage) {
  char ErrorMessage[4] = "ERR"; // Get a basic error message
  strcpy(command, ErrorMessage); 
  Serial.println(ErrMessage); 
}

void showParsedData() {

  Serial.println();
  Serial.print("Message ");
  Serial.println(command);
  Serial.print("Integer ");
  Serial.println(intRequested);
  Serial.println();
  // Serial.print("Float ");
  // Serial.println(floatFromPC);
}

Strings (capital S) have a bad reputation when used in the limited memory available on many Arduino and related boards. Whether this reputation is deserved or not is debatable, but when Robin wrote his example sketches he went out of his way to use C style strings (lowercase s) instead, hence my comment

Most forum members avoid them like the plague. Especially on AVR-based Arduino, Strings cause program crashes due to poor memory management.

It is never necessary, and pointless on a microcontroller, to use Strings.

1 Like

OK, well I got this example from an excellent tutorial on this site, from an expert. This particular sketch uses non-blocking functions. And these are not strings, they are char arrays - that's another whole point to this example.

It is necessary for a human to be able to understand the code being used, and I have a product with over 30,000 lines of code that I wrote, and maintain. If I were to use integers to represent all communications, it would be a nightmare to understand the code.

That's why I chose to go this route.

Also, the app I'm developing uses strings for names of things, so I really can't get away from it, as the user needs to be able to enter names for things...unless there's a better way?

How do people do it, when a large number of communications are necessary going back and forth between an app and a microcontroller then?

thank you for your time.

Mike

PS, you might change my mind...

The use of Strings has nothing to do with using integers so I am not sure what you are referring to

Does it use Strings (uppercase S) or strings (lowercase s) ? You seem to be using the terms interchangeably

What sort of hardware does your 30,000 lines of code application run on ?

Thank you.

It runs on a Teensy 4.0.

My (limited) understanding of this topic is that character arrays are not the same as strings. And I've specifically avoided using Strings or strings or any capitalization - these are all character arrays.

Which I thought was the right way to go?

If I don't use characters to transmit communications, then what would I use? That's why I said integers.

What is the "correct best practice route" to handle exchanging data between a Teensy MCU and a Processing app? I thought serial communications with character exchange made the most sense.

Thanks for your expertise.

Mike

String objects in C++ are not character arrays.

Most experts in MCU programming, primarily in industry, use straight C with professional IDEs, and when character data must be manipulated, use zero terminated character arrays (C-strings) and the <string.h> library to manipulate them.

The Arduino IDE is primarily for beginners, and caters to beginner tastes, including use of the very problematic String class.

Thanks for the tips.

I never claimed to be an MCU programming expert; no need to be rude. And I'm not going to change the IDE I'm using and have used for years just because someone says it's not pro-level.

It would be more helpful to actually give advice rather than be condescending.

I spent two entire days debugging this example I posted, so I hope it helps someone.

Mike

There was nothing rude about my reply, nor did I imply anything about your experience or expertise. I'm merely explaining the state of affairs, with a focus on industry MCU applications that must perform reliably.

If you want to use Arduino and Strings, be my guest. Have fun!

1 Like

What you got going on between the Teensy and Processing? I have used Processing to make two Hallowe'en arcade games and a gamified GI Joe file card rolodex/digital comic book reader.

1 Like

There are reasons that the String class use is not popular with the users of the processors with smaller memories. The evils of Arduino Strings.

2 Likes

Except for these

String ErrorMessage = "";
String ErrNumber = 0;

Especially the last of the two is much surprising... Using the String type for a number???

Mod edit,
Rude and unnecessary comment removed.

Fair enough. Apologies. I actually do appreciate what you said and I just took it wrong. It's been a lot of stress on this project lately. Sorry again. Thank you.

It's a music fader I make and sell. It started off with a handful of lines of code and over time I added to it and made it really cool. Thousands of hours of programming.

Do you have a link to your game?

1 Like

I appreciate that. I have seen this kind of thing before, but I will check out what you shared. Thank you :slight_smile:

Yes, true. In my original reply I mentioned that, but I edited it and forgot to include it back. This is just test code anyway, so I threw it in while debugging other stuff. Thank you.

Yes, touche. See my previous mention. I threw this in rather hurriedly as I was getting other things to work. Thanks for pointing it out. It was a dumb error, but the 'tude? Dude.

Specifically, I was trying to make sure if a message was sent only partway and was interrupted, or had a serial com problem, it would not screw up the following communications. Because that's what the original example #5 does (linked to in the original post). If you send the original example wrong data, then send it right data, the response is a jumble.

My version only accepts valid input. Perhaps not much of an achievement, but it was my goal.