parseInt() clears Serial.available but leaves terminator in serial buffer

Hi Gang,

(Apologies in advance if this is a really old issue that was answered years ago...!)

I'm using parseInt to read integers from the serial port and I'm noticing odd behaviour...:

If I send 123 followed by a Carriage Return, parseInt() correctly returns 123 but:
it sets .available() to zero - implying it has read and deleted the CR;
it leaves the CR in the buffer!

Is this expected?!

Here's the test code I'm using:

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

void loop() {
  while (Serial.available() == 0) ; // Wait for some data to arrive
  Serial.print("Serial.parseInt() returned "); Serial.println(Serial.parseInt()); // parse an int
  Serial.print("Serial.available() returned "); Serial.println(Serial.available()); // check available
  Serial.print("First Serial.read() returned "); Serial.println((int8_t)Serial.read()); // try to read anyway
  Serial.print("Second Serial.read() returned "); Serial.println((int8_t)Serial.read()); // try another read
  Serial.println();
}

Here are the results from sending:

123 followed by Carriage Return
456 followed by Line Feed
789 followed by a hash (#) and no line ending

Serial.parseInt() returned 123
Serial.available() returned 0
First Serial.read() returned 13
Second Serial.read() returned -1

Serial.parseInt() returned 456
Serial.available() returned 0
First Serial.read() returned 10
Second Serial.read() returned -1

Serial.parseInt() returned 789
Serial.available() returned 0
First Serial.read() returned 35
Second Serial.read() returned -1

I'm confused as to why Serial.available returns zero when there's clearly still a character in the buffer...
Surely it should return one?

Thanks in advance,

Paul

I think the answer is that Serial.parseInt() terminates and Serial.available() is called BEFORE the line-feed arrives. Remember serial data is very slow compared to an Arduino. But delay(2) before the Serial.available() and see what happens.

Have a look at the examples in Serial Input Basics - simple reliable ways to receive data.

...R

Thanks Robin,

It doesn't seem to be that. Even with a delay of 50 the results are the same.

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

void loop() {
  while (Serial.available() == 0) ; // Wait for some data to arrive
  Serial.print("Serial.parseInt() returned "); Serial.println(Serial.parseInt());
  delay(50);
  Serial.print("Serial.available() returned "); Serial.println(Serial.available());
  Serial.print("First Serial.read() returned "); Serial.println((int8_t)Serial.read());
  Serial.print("Second Serial.read() returned "); Serial.println((int8_t)Serial.read());
  Serial.println();
}

Serial.parseInt() returned 123
Serial.available() returned 0
First Serial.read() returned 13
Second Serial.read() returned -1

Serial.parseInt() returned 456
Serial.available() returned 0
First Serial.read() returned 10
Second Serial.read() returned -1

Serial.parseInt() returned 789
Serial.available() returned 0
First Serial.read() returned 35
Second Serial.read() returned -1

I can't duplicate your results. When I run your code on a Uno, Serial.available() return 1 and the first Serial.read() returns that character.

Where is the serial data coming from? If serial monitor, what exactly are you typing?

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

void loop() {
  while (Serial.available() == 0) ; // Wait for some data to arrive
  Serial.print("Serial.parseInt() returned "); Serial.println(Serial.parseInt()); // parse an int
  Serial.print("Serial.available() returned "); Serial.println(Serial.available()); // check available
  Serial.print("First Serial.read() returned "); Serial.println((int8_t)Serial.read()); // try to read anyway
  Serial.print("Second Serial.read() returned "); Serial.println((int8_t)Serial.read()); // try another read
  Serial.println();
}

Serial.parseInt() returned 123
Serial.available() returned 0
First Serial.read() returned -1
Second Serial.read() returned -1

No problem here.Tested in arduino Duemilanove

Thank you,

I'm using an Adafruit Feather M0 Adalogger with a SAMD21G18 M0 processor. It should be equivalent to an Arduino Zero. I did have the SERIAL_BUFFER_SIZE set to 1024, but changing it back to the default of 164 made no difference.

I am using Serial Monitor:
Set line ending to Carriage Return
Type 123
Press Send
Set line ending to Line Feed
Type 456
Press Send
Set line ending to No Line Ending
Type 789#
Press Send

Don't lose any sleep over this. It's more of an intellectual curiosity. A blocking version of Robin's Example 4 is what I need.

If I send 123 followed by a Carriage Return, parseInt() correctly returns 123 but:
it sets .available() to zero - implying it has read and deleted the CR;
it leaves the CR in the buffer!

There are several errors in this statement:

1) parseInt does not set available() to zero. The available() function will return zero if read() has been called enough times to empty the receive buffer.

2) parseInt starts by skipping non-digit characters. Then it reads digits until a non-digit is found. It returns the integer value it has accumulated up to that non-digit character. If your Serial Monitor window sends two line termination characters (CR & LF) when you press the Enter key (or the Send button), parseInt will only read one of them. The other character will still be available, and the next read call will get it.

3) It takes a looooong time for characters to be received, 1ms per character. This is about 10000 CPU instructions, or about 1000 lines of C/C++ code. So when parseInt reads the first line terminator and returns to your loop, the second line terminator may not have arrived yet. It’s still on its way over the USB cable. Your code checks immediately (within a few lines of code) to see if the second terminator is there (available). It may not be available.

This is one of the pitfalls of the parseXXX routines. They imply that you can expect a certain order of things. It doesn’t really matter when CR/LF characters appear, does it? Ignore them most of time, unless you are looking for the end of a line. parseInt will ignore them at the beginning, so why do you care if they’re still in the receive buffer? The next read will ignore it anyway. When it finally arrives, that is.

Another problem is the timeouts that are in effect. These routines may wait for the characters to arrive – this prevents your program from doing anything else. Want to blink a light? Check a sensor? Too bad. MUST. WAIT. FOR INT. This is called “blocking”.

Take a look at Robin2’s execellent tutorial. It is non-blocking. Here’s a version of her sketch, tailored down to your specific example.

size_t         count     = 0;
const size_t   MAX_CHARS = 64;
char           line[ MAX_CHARS ];


void setup()
{
  Serial.begin( 9600 );

}

void loop()
{
  if (lineReady()) {
    int value = atoi( line ); // parses char array to an integer value
    Serial.print( "value = " );
    Serial.println( value );
  }

  //  Do other things here while waiting for integers.
}


bool lineReady()
{
  bool          ready     = false;
  const char    endMarker = '\n';  // this is LF for Arduino, ASCII value 10

  while (Serial.available()) {

    char c = Serial.read();

    if (c != endMarker) {

      // Not the end, save another character.  But only
      //   only save the printable characters, and only
      //   if there's room.
      // This ignores CR characters (ASCII char value 13).
      //   Actually, this ignores all control characters:
      //   any thing less than a space char, ASCII value 32.

      if ((c >= ' ') and (count < MAX_CHARS-1)) {
        line[ count++ ] = c;
      }

    } else {
      //  It's the end marker, line is completely received

      line[count] = '\0'; // terminate the string
      count       = 0;    // reset for next time
      ready       = true;
      break;
    }
  }

  return ready;

} // lineReady

A blocking version of Robin’s Example 4 is what I need.

Doubtful.

If you insist, do something like this:

void loop()
{
  // block, no timeout
  while (not lineReady())
    ;

  int value = atoi( line ); // parses char array to an integer value
  Serial.print( "value = " );
  Serial.println( value );
}

I feel dirty. Your sketch will perform much better if it is structured in a non-blocking way. Post the whole sketch if you’d like suggestions.

Cheers,
/dev

Make sure when you send the serial data you’re not including both a line feed and a carriage return. Either of those signal the end of the string.

Thanks for the feedback. It really doesn’t seem to be CR+LF related. Here’s what I get from a completely fresh start using 789# with no line ending:

Now with second picture:

Do you have a real UNO around to test same code?

Does the problem exist in a previous versionof the IDE?

PaulZC:
Thanks for the feedback. It really doesn't seem to be CR+LF related. Here's what I get from a completely fresh start using 789# with no line ending:

Have you tried the examples in Serial Input Basics? I suspect you won't have a problem with them.

-dev:
Take a look at Robin2's execellent tutorial. It is non-blocking. Here's a version of her sketch,

Is that a slur on my masculinity? :slight_smile:

...R

Thanks Robin,

This works - but I suspect it will offend both you and /dev ! :grin:

      // Wait for the arrival of a one (or two digit) int menu choice followed by a CR
      int choice = 0;
      char receivedChars[3];
      while(Serial.available()==0) ; // Wait for first character
      receivedChars[0] = Serial.read(); // Read the first character
      if (isDigit(receivedChars[0])) { // Check if first character is a number
        while(Serial.available()==0) ; // Wait for second character
        receivedChars[1] = Serial.read(); // Read the second character
        if (isDigit(receivedChars[1])) { // Check if second character is a number
          while(Serial.available()==0) ; // Wait for third character
          receivedChars[2] = Serial.read(); // Read the third character
          if (receivedChars[2] == '\r') { // If the third character is CR
            receivedChars[2] = 0; // NULL-terminate the number
            choice = atoi(receivedChars); // Convert to int
          }
        }
        else if (receivedChars[1] == '\r') { // Second character was not a number so check if it is a CR
            receivedChars[1] = 0; // Second character was a CR so NULL-terminate the number
            choice = atoi(receivedChars); // Convert to int
        }
      }

I love Minecraft code me! (Blocking... Geddit!?)

Thanks robtillaart! Ta da! Here's what I get on a Mega 2560. So it's definitely hardware/library dependent...

PaulZC:
Thanks robtillaart! Ta da! Here's what I get on a Mega 2560. So it's definitely hardware/library dependent...

So you need to compare the source of parseInt (stream.cpp) of the AVR with the parseInt used by the other platform.
(Kdiff3 does a good job for this)