Why is Serial.parseInt() so slow?

Hi all...

I have some simple code that reads in a digit character from 0 to 9 from the serial monitor and then does something based on the value. When I use this standard code...

void loop() {

  if (Serial.available()) {
    char ch = Serial.read();
    if (isDigit) {
      level = ch - '0';
    }
  }

// ... do something here.

}

...the sketch responds instantly to entered values. However, when I use the parseInt() functionality, like this...

void loop() {

  if (Serial.available()) {
    level = Serial.parseInt();
  }

  // ... do something here

}

...there's a noticeable delay (about a second or so) before the sketch picks up that the value has changed. What's going on? Is there a way to make use of parseInt() without the delay penality?

The parseInt() function is looking at the serial buffer, reading each character, until it finds one that is not an numeric character. If the serial buffer is empty, it waits for a while for serial data to arrive. If you send a non-numeric character right after the last digit, parseInt() will return much faster.

OK, that works for a second. For instance if I send the string "4f" the 4 value is picked up by the code instantly. But then something odder happens. A second or so later the parseInt() function spits out a value of zero. Where is that coming from? It happens whether or not I append a line ending character in the serial monitor. What's going on now?

Need to see your code.

I use paresInt almost exclusively on a project and what I found works for me is to use a for loop. I know how many integers I'm going to receive and it gets those integers and doesn't try anymore.

When I found that delay happening it was because I was not looping the right number of times. So for 14 integers, I was trying to read 15. It would wait for a bit until nothing arrived and then spit out a 0 and move on.

Here's a complete sketch demonstrating the issue...

/*

 Dim a LED on pin 9 based on the value entered in. 
 Entered value needs to be a digit 0-9.

 */

int level, brightness;
void setup() {
  pinMode(9,OUTPUT);
  level = 5;
  Serial.begin(9600);
}

void loop() {

  if (Serial.available()) {
    level = Serial.parseInt();
    Serial.println(level);
  }


  brightness = map(level, 0, 9, 0, 255);
  analogWrite(9,brightness);
}

Enter a number value such as 3 (with "No line ending" selected in the serial monitor) to see the sketch delay before getting the 3 value, but then use the value correctly. Enter a value such as "3x" (with or without a line ending) to see the sketch pick up the 3 value instantly but then after a delay receive a 0 value.

Thanks!

chrisspurgeon: Enter a number value such as 3 (with "No line ending" selected in the serial monitor) to see the sketch delay before getting the 3 value, but then use the value correctly. Enter a value such as "3x" (with or without a line ending) to see the sketch pick up the 3 value instantly but then after a delay receive a 0 value.

You call parseInt() and it sees the 3, starts to accumulate digits and knows it is done when it sees a non-digit (x) coming. It returns the 3.

You call parseInt() again and it sees the 'x'. Probably throws it away and waits for a digit to arrive. After a second it has no new input so it returns 0.

After each parseInt() you should check the serial buffer (.available()) and read the terminating character. You can turn on linefeed (newline) terminators and check that the terminating character is '\n'. If it isn't you have detected an input error and can ignore the number.

The problem with reading a terminal character is what if it is supposed to be another int.

I have run into this problem because I am using strictly integers. There is no convenient way to check for that terminating character. I look for a starting character and do a for loop for x number of integers depending on what that starting character is. This also has the benefit of allowing me to have multiple RX in the buffer. I do this because I can conceivable receive 7 different types of serial data. So the starting character dictates both how many integers and what to do with the data once I've received it. So if I have data coming in from 3 differnt sources, they can all stack up in the buffer, I read each set out individually. I have yet to try this in a real world situation since so far I am only sending from the monitor.

To address the OP's problem, I can't see why the very first example wouldn't work correctly. If you send exactly one number with no starting or terminating character, it should read that as int and move on. When I send my streams, i do not terminate at all and I get what appears to be instant response. I am writing data to an LCD so there is a very slight delay.

I can see where it would hang if you sent a terminating character since as stated, it reads the int then in the next loop it sees the 'f' so it sees serial.available() and tries to read an int that isnt't there.

With the f still in the sent data, try to call a Serial.flush() right after you parseint and see what it does.

I believe I know what it is.

If you use a terminating character from the serial monitor it is seeing that as still in the buffer. Parseint ignores any leading characters until it hits a number or minus sign. It then terminates when it hits it's first character. So when you are only sending the number it should work but it sees that terminating character so it comes back around in the next loop and doesn't see anything so it waits for a second and continues.

The same thing happens when you end with an 'f'. The parseint terminates with the 'f' on the next loop it discards the terminating character by design and waits for a number which it doesn't get so it times out again.

Change your serial monitor option to send no terminator character and You should be able to go with just sending the int.

chrisspurgeon:
Hi all…

I have some simple code that reads in a digit character from 0 to 9 from the serial monitor and then does something based on the value.

Since you will only be sending 1 digit at a time, why not just:
1 - check for serial available.
2 - get the character into a byte variable (say B for buffer)
3 - check if it is a digit ((B >= ‘0’) && (B <= ‘9’)) and
3a - if it is, act on it.
4 - end of loop(), long live the loop()

And you can set the serial monitor up to include end of line marker(s) but if you don’t code for that then you can use Hyperterminal or equivalent to send digits over without hitting Enter to get the data sent. IOW, as soon as you hit the digit key the character is sent.

To address the OP's problem, I can't see why the very first example wouldn't work correctly. If you send exactly one number with no starting or terminating character, it should read that as int and move on. When I send my streams, i do not terminate at all and I get what appears to be instant response. I am writing data to an LCD so there is a very slight delay.

I dunno, I think that delay may not be entirely due to the LCD. Try running my example sketch (no need to actually hook up a LED) and watch the serial monitor output. I think you'll see that when you have no terminating character you'll see a delay before the entered number is echoed out to the serial monitor.

Change your serial monitor option to send no terminator character and You should be able to go with just sending the int.

Tried that I'm afraid, that's the original issue. I don't have the problem with that final "0" from a terminating character appearing, but it DOES incorrectly take a second or so for the entered number to appear.

Michael Margolis' "Arduino Cookbook" (2nd edition, page 113) to the rescue!

It turns out that the Serial class (which extends the Stream class) uses the Stream.setTimeout() function. The default value for the timeout is 1000 (one second). I just set it to something smaller, like 50 ms, and the sketch is nice and zippy. Here's the complete example sketch. Turn off line endings in the serial monitor...

int level, brightness;
void setup() {
  pinMode(9,OUTPUT);
  level = 5;
  Serial.begin(9600);
  Serial.setTimeout(50);
}

void loop() {

  if (Serial.available()) {
    level = Serial.parseInt();
    Serial.println(level);
  }


  brightness = map(level, 0, 9, 0, 255);
  analogWrite(9,brightness);
}

Thanks to everyone who replied!

1 Like

I had the same issue with plotting graph in StampPlot and recieving returning data to arduino.

Serial.setTimeout(10); helped :)

Thanks.

Do you have Serial Monitor set to add a newline at the end of each entry? Newline is non-numeric. You can enter several numbers by putting spaces or commas between. But really, rolling your own will teach you a thing or two.

setTimeout function really worked for me.

I may start to be crazy because of parseInt function causes 1 second delay by default.

Thank u everyone for trying to solve such these problems.

1 Like

Don't use Serial.parseInt() at all. It is a blocking function.

The examples in serial input basics are simple, reliable and non-blocking. There is also a parse example.

...R

http://www.gammon.com.au/serial

Trying to control a 16-channel relay through arduino/raspberry pi combo - you just saved me a lot of headaches - this was a real show stopper. Thanks a ton for this hint!

Was having problems with Serial Monitor input skipping past my second question. Turning off Newline in the monitor allowed the sketch to work as desired. Thanks