Serial read needs delay?

Hello, I’ve made a function to receive a string via SoftwareSerial. In the function I’ve had to include a short delay <delay(5)> or the incoming string shows random ascii characters at seemingly random times. Can anyone explain to me why this happens? If I make the delay any shorter than 5 milliseconds the errors occur. The shorter the delay, the more frequent the errors occur.

Below is my test program which I will eventually implement on an RS485 network, so the send_message() function also sends a signal to the MAX485 chip to transmit on the network. Please ignore this part of the example.

An aside: Although I’m no software developer, I hope this example helps others in their Arduino serial communication. I’ve learned so much about programming from this forum that I hope to be able to give back. If this is somehow a poor example of code, please correct it for the benefit of everyone else. :slight_smile:

Why do I need that small delay in receive_message()?

#include <SoftwareSerial.h>

#define RS485RX          2
#define RS485TX          3
#define TxControl 4               //RS485 Direction control
#define Transmit    HIGH
#define Receive     LOW

String message;
byte start_of_text = 2;
byte end_of_text = 3;

SoftwareSerial RS485(RS485RX, RS485TX); // RX, TX






void setup() {
  Serial.begin(9600);
  RS485.begin(9600);
  delay(100);
  Serial.println("Connection established at 9600 baud from SLAVE to COMPUTER.");
}







void loop() {
  message = receive_message();
  if( message != "" )                    //only report meaningful strings
  {
    Serial.println(message);
    send_message(message);
  }
}







/*********************************************************************************************************
* This function checks for the "start of text" ascii char <2> and then reads in each char to a string.
* The function breaks out of reading into the string if the "end of text" char <3> is read or the maximum buffer size is reached
*********************************************************************************************************/
String receive_message(){
  char incoming_char;
  int max_size = 64;                                                        //size of serial buffer on UNO
  String message_string = "";
   
  if( RS485.read() == start_of_text )
  {
    delay(5);                                                               //Not sure why I need this, but it needs to be at least 5ms, as per experimentation
    while( message_string.length() <= max_size )                            //end the while loop if the "end of text" char is read or buffer maximum is reached
    {
      incoming_char = RS485.read();
      if( incoming_char == end_of_text ) break;
      else
      {
        message_string += incoming_char;
      } 
    }
  } 
  return message_string;
}






/*********************************************************************************************************
* This function outputs the message, msg, preceded by a start-text char and followed by an end-text char
* Since this will be used on an RS485 network, the enable-transmission line is also controlled.
*********************************************************************************************************/
void send_message(String msg){
  digitalWrite(TxControl, Transmit);
  RS485.write(start_of_text);
  RS485.print(msg);
  RS485.write(end_of_text);
  digitalWrite(TxControl, Receive);
}

Before you do any reading you must test if data is available. Reading without testing is like watching a TV program before they start broadcasting it.

Your use of delay() in this case fortuitously gives the data time to arrive, however that is not the recommended method.

See: Gammon Forum : Electronics : Microprocessors : How to process incoming serial data without blocking

Serial data is very slow compared to the speed of the Arduino processor. You can do a thousand different things in the time between characters. That's if it's running full speed. If you're waiting for a person typing on a keyboard, you can do millions of things between characters.

You need to check if there's a character available before you try to read a character. I'm not familiar with the library you're using, but I expect that it works that way.

Thank you, both, for your fast and clear replies. So, if I encapsulate the if() statement in the function receive_message() with another if() that checks for available serial data, then it would be more robust?

Like so?:

/*********************************************************************************************************
* This function checks for the "start of text" ascii char <2> and then reads in each char to a string.
* The function breaks out of reading into the string if the "end of text" char <3> is read or the maximum buffer size is reached
*********************************************************************************************************/
String receive_message(){
  char incoming_char;
  int max_size = 64;                                                        //size of serial buffer on UNO
  String message_string = "";
  
  
  if( RS485.available() )
  { 
    if( RS485.read() == start_of_text )
    {
      delay(5);                                                               //Not sure why I need this, but it needs to be at least 5ms, as per experimentation
      while( message_string.length() <= max_size )                            //end the while loop if the "end of text" char is read or buffer maximum is reached
      {
        incoming_char = RS485.read();
        if( incoming_char == end_of_text ) break;
        else
        {
          message_string += incoming_char;
        } 
      }
    } 
  }
  return message_string;
}

I just tested the new code I posted and it still exhibits the same problems without the delay in place. Any thoughts?

 while( message_string.length() <= max_size )                            //end the while loop if the "end of text" char is read or buffer maximum is reached
      {
        incoming_char = RS485.read();

You’re reading stuff without checking that that there’s something to read.
Don’t do that.
I think this has already been mentioned.

  if( RS485.available() )only checks to see if at least one character is available to read from the buffer. Just because one character is available it does not mean that the whole message is available.

kanurys:
Thank you, both, for your fast and clear replies. So, if I encapsulate the if() statement in the function receive_message() with another if() that checks for available serial data, then it would be more robust?

Not ideal, but this should give you the idea (note there is no call to delay, but we are stuck in a loop while waiting for characters):

/*********************************************************************************************************
* This function checks for the "start of text" ascii char <2> and then reads in each char to a string.
* The function breaks out of reading into the string if the "end of text" char <3> is read or the maximum buffer size is reached
*********************************************************************************************************/
String receive_message(){
  char incoming_char;
  int max_size = 64;                                                        //size of serial buffer on UNO
  String message_string = "";
 
 
  if( RS485.available() )
  {
    if( RS485.read() == start_of_text )
    {
      while( message_string.length() <= max_size )                            //end the while loop if the "end of text" char is read or buffer maximum is reached
      {
        if( RS485.available() )
        {
            incoming_char = RS485.read();
            if( incoming_char == end_of_text ) 
                break;
            else
            {
              message_string += incoming_char;
            }
        }
      }
    }
  }
  return message_string;
}

The examples in Serial Input Basics are simple, reliable ways to receive data and they don't need any delay()s.

...R

while( message_string.length() <= max_size )                            //end the while loop if the "end of text" char is read or buffer maximum is reached
      {
        incoming_char = RS485.read();
        if( incoming_char == end_of_text ) break;
        else
        {
          message_string += incoming_char;
        }

No, that isn’t more robust because you are not checking for each character. I suggest you read the link I posted.

Let’s say your friend said “I am sending you 100 parcels”. And you check if you got one parcel, and then proceeded and said “yep, I got all 100”.

Ah, I see now. Before I read() I need to check and see if there is anything to read. Maybe something like this?

String receive_message(){
  char incoming_char;
  int max_size = 64;                                                        //size of serial buffer on UNO
  String message_string = "";
  
  
  if( RS485.available() )
  { 
    if( RS485.read() == start_of_text )
    {
      //delay(5);                                                               //Not sure why I need this, but it needs to be at least 5ms, as per experimentation
      while( message_string.length() <= max_size )                            //end the while loop if the "end of text" char is read or buffer maximum is reached
      {
        if( RS485.available() > 0 )
        {
          incoming_char = RS485.read();
          if( incoming_char == end_of_text ) break;
          else
          {
            message_string += incoming_char;
          } 
        }
      }//end of while loop
    }
  }
  return message_string;
}

Nick, I read your web page. Thank you. Now it works well. When I remove the delay(5) it reads normally. Thanks for the clarification/repetition. Three brain cells only process in serial bytes. :slight_smile:

I understand there are other issue with this. It's a blocking function if it starts to receive chars and doesn't get an end char or reach the max buffer size. This is going to work, for now. I will research a way to check for failure to transmit. Thanks to everyone for their support.

You are right, that will block. You could add a timeout (for example, note when you start receiving data, and if you haven't finished after a second, exit anyway).

Add a "timeout" feature using millis().

64 characters...9600bps (1200 characters a second) = 64 characters in about 0.05s (50ms). So say after 50ms, break the loop.

I have no idea if this works…but I had a go? I am noobish btw!

#include <SoftwareSerial.h>

SoftwareSerial RS485(9, 10);

String receive_message() {
  
byte max_size = 64;                                                        //size of serial buffer on UNO
String message_string = "";
unsigned long start_timeout = 0;
char start_of_text = '

;    // A start character for messages
char end_of_text = ‘&’;      // An end of message character
byte time_out_length = 50;    // The required “timeout” length.

if (RS485.available() ) // If there is A SINGLE BYTE READY in the buffer, Do the next thing…
  {
    if ( RS485.read() == start_of_text){      // Got the first start of text character?
      start_timeout = millis();            // Start the timer
     
      while (((sizeof(message_string)) < max_size) && (millis() - start_timeout) < time_out_length) { // While there is less than 64 characters AND there are less than so many millis() passed…
       
        if (RS485.available()) {        // Check for available characters
         
          if (RS485.read() == end_of_text) {      // If there is one and it is the "End of text " character…go to the Return message.
            goto got_last_character;
          }

message_string += RS485.read();      //Otherwise, Read the character and append it to the string message.
        }

}
got_last_character:                          // Spit out the message.
      return message_string;
    }

}

}

void setup() {
  RS485.begin(9600);
  Serial.begin(115200);
}

void loop() {
  // put your main code here, to run repeatedly:
  Serial.print(receive_message());
 
}

Johnny010:
I have no idea if this works…but I had a go? I am noobish btw!

#include <SoftwareSerial.h>

SoftwareSerial RS485(9, 10);

String receive_message() {
 
byte max_size = 64;                                                        //size of serial buffer on UNO
String message_string = “”;
unsigned long start_timeout = 0;
char start_of_text = ’

Hey Johnny,

In C and C++, it is considered poor coding to use goto. It is almost never necessary, and makes the code difficult to maintain.

Essentially, don’t ever use goto.;    // A start character for messages
char end_of_text = ‘&’;      // An end of message character
byte time_out_length = 50;    // The required “timeout” length.

if (RS485.available() ) // If there is A SINGLE BYTE READY in the buffer, Do the next thing…
  {
    if ( RS485.read() == start_of_text){      // Got the first start of text character?
      start_timeout = millis();            // Start the timer
     
      while (((sizeof(message_string)) < max_size) && (millis() - start_timeout) < time_out_length) { // While there is less than 64 characters AND there are less than so many millis() passed…
       
        if (RS485.available()) {        // Check for available characters
         
          if (RS485.read() == end_of_text) {      // If there is one and it is the "End of text " character…go to the Return message.
            goto got_last_character;
          }

message_string += RS485.read();      //Otherwise, Read the character and append it to the string message.
        }

}
got_last_character:                          // Spit out the message.
      return message_string;
    }

}

}

void setup() {
  RS485.begin(9600);
  Serial.begin(115200);
}

void loop() {
  // put your main code here, to run repeatedly:
  Serial.print(receive_message());
 
}

Hey Johnny,

In C and C++, it is considered poor coding to use goto. It is almost never necessary, and makes the code difficult to maintain.

Essentially, don’t ever use goto.

Johnny010:
Add a "timeout" feature using millis().

64 characters...9600bps (1200 characters a second) = 64 characters in about 0.05s (50ms). So say after 50ms, break the loop.

Not sure how you arrive at those figures.
9600 bits per second = 960 characters per second (8,N,1) = 64 characters in about 66ms.

Johnny010:
I have no idea if this works...but I had a go? I am noobish btw!

It makes no sense to post code that you have not tried.
If it works there is no need to post a question
If it does not do what you want you will then be able to tell us what actually happens.

...R