Reading Serial on the Arduino

I see many many questions on the Arduino forums from people trying to read data from a serial connection and not fully understanding how it works - and hence failing.

So, how should you read from serial?

Well, what a lot of new users don't realise is that serial data arrives one character at a time, and you have little or no control over just when that data arrives.

The Arduino has a handy function: Serial.available(), which tells you how many characters are in the serial device's receive buffer. If you know beforehand how many characters you are going to be receiving this can be a very handy and simple way of managing your receiving. However, it has to be done right.

Too many times I have seen the following:

if (Serial.available() > 0) {
  for (int i=0; i<8; i++) {
    buffer[i] = Serial.read();
  }
}

What that is intended to do is wait for the message to arrive, then read all 8 characters of it into a buffer. What it actually does, is wait for the first character of the message to arrive and then try and read in 8 characters into the buffer, whether or not they have actually arrived.

What you should be doing is waiting for the internal serial buffer to have all 8 characters in it, and only then do you read them in:

if (Serial.available() >= 8) {
  for (int i=0; i<8; i++) {
    buffer[i] = Serial.read();
  }
}

See the subtle difference there?

Another problem is what to do if you don't know how many characters you will be receiving. A common misconception seems to be that if you send 5 characters from one end of the serial link you will instantly receive 5 characters at the other end, and that those 5 characters will form a single coherent lump that the receiver somehow knows are one transmission. That is not the case. The receiver just receives the characters one at a time and adds them to its internal buffer. It has no concept at all about how many characters were sent, and how long the message is meant to be.

For that you need to have some marker that tells the receiver when the whole message has arrived. The normal marker to use is character 13, or the carriage return character. This is what your keyboard sends when you press the RETURN or ENTER key, so it's a logical choice.

The receiver needs to just keep receiving characters and adding them to its buffer up until it receives this terminating character. Only then can you actually do anything with the message.

Remember - Serial.read() just returns one character (if it is available). So you will need to keep calling it over and over again until the whole message has arrived.

Take the following little sketch for example:

char buf[80];

int readline(int readch, char *buffer, int len) {
    static int pos = 0;
    int rpos;

    if (readch > 0) {
        switch (readch) {
            case '\r': // Ignore CR
                break;
            case '\n': // Return on new-line
                rpos = pos;
                pos = 0;  // Reset position index ready for next time
                return rpos;
            default:
                if (pos < len-1) {
                    buffer[pos++] = readch;
                    buffer[pos] = 0;
                }
        }
    }
    return 0;
}

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

void loop() {
    if (readline(Serial.read(), buf, 80) > 0) {
        Serial.print("You entered: >");
        Serial.print(buf);
        Serial.println("<");
    }
}

So, what are we doing here? Well, we have a little function called readline(). This takes an incoming character (provided by Serial.read()), and decides what to do with it. If it's a carriage return character (\r) then it gets ignored and thrown away. This is to make it handle "CRLF" terminated lines properly without leaving a mess. If it's a line feed, then we decide that the message is complete and return the number of characters in the message. If it's none of those, then just add the character to the buffer (if there's room).

Only when the message has been received (the return value > 0) will we actually look at the contents of the buffer and do something with it - in this case just print it back to the serial device. you'll notice that this method has several advantages:

  • You're not blocking while waiting for a character to arrive, so you can continue doing other things at the same time as receiving your message.
  • The terminating line feed (and possible carriage return) characters are automatically discarded, which makes string comparisons simpler.
  • Your program can continue doing other things while the message is being received - it's very simple to know if it's all there or not.
  • The buffer will always be properly null-character terminated.

So as soon as the return value of readline() is greater than 0 you know you have a full message of more that 0 characters in length. Now you can go and do whatever you want with that message - be it convert it to an integer with atoi(), compare it with other strings with strcmp(), etc.


The Finite State Machine