Reading Serial on the Arduino


Due to WordPress’s abysmal handling of code blocks this blog post is now hosted at https://majenko.co.uk/blog/

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 terminatingcharacter. 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("<");
    }
}

Trouble reading this snippet? Get it on BitBucket.

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 withatoi(), compare it with other strings with strcmp(), etc.

Advertisements

20 thoughts on “Reading Serial on the Arduino

  1. Josh

    I’m working on a serial communication protocol for the Arduino and I knew most of the example code I was seeing on forums wasn’t designed well. Your code illustrates proper design principles and understanding of serial buffers. Thank you for taking the time to share it, I can now code in confidence!

    Like

    Reply
  2. The Waz

    I really appreciate your taking time to share this post. I have recently built some projects that require communication with several serial devices. An adaptation of this scheme allows me to build up buffers for each in parallel instead of putting the rest of the sketch on hold while listening to a one device.

    Like

    Reply
  3. Pingback: Splitting Up Text in C | Majenko's Hardware Hacking Blog

  4. Ray

    How would I get this to react based on what is received over serial? I’ve been trying if, do/while, and switch but can’t quite get it to behave as desired.

    Like

    Reply
  5. mai ehab

    hey thank you for the great article it give me a great aproach to seria;
    i have a little problem and i would appreciate if you help
    i have sending 2 data on Serial1 by Mega and receiving these two data by your last sketch on node bu it seems like data streams doubled on nodeMcu !

    Like

    Reply
    1. majenko Post author

      I am sorry, I can’t provide technical support through blog comments. It’s just not practical. I suggest you post a question on arduino.stackexchange.com where I will most likely find it. Remember to post your full code (or an example code that demonstrates the problem) along with your wiring.

      Like

      Reply
  6. Tara

    Hi.
    Firstly thank you for taking time to explain this. What im struggling with being a novice in arduino is the bit about the parameters we are are passing to the readline function. Can you provide a code snipet about how call out readline in the main loop please

    Like

    Reply
    1. majenko Post author

      First parameter is the character you have read from serial. Second is a char * buffer to store the string in. Third is the size of that buffer in bytes.

      Like

      Reply
  7. Jfrank

    Thanks for writing this up, I’ve had a hard time finding more information about how Serial.Available() and Serial.Read() work. This is a great conceptual grounding.
    I’m having a hard time following your code snippet, I think some important characters may have gotten dropped while pasting into wordpress. Here are a couple of things I have questions on (assuming the lines in your code snippet are numbered starting at 1):
    -Line 10 ‘rpos = pos;’, it doesn’t look like pos is declared at 0, but inside the function, it doesn’t look like the value of pos is ever changed to anything other than 0. In the next line it’s value is then set to 0, but I’m not following how the value would have changed before that, or how rpos will be anything other than 0 as well.
    -Line 14 ‘if (pos 0) {‘, I’m assuming this should be ‘if (pos = 0) {‘ ? I can’t get the current code snippet to compile.
    -Line 14 ‘if (pos 0) {‘, is this if statement supposed to be inside the ‘default:’ code block (which starts on line 13) or follow after the end of the switch statement? I assume it supposed to follow the switch statement, but there are some ‘}’ brackets missing and it’s not clear what code should be the default, or what should be the end of the function. Also, are the Serial.print… lines (lines 15-17) supposed to be nested inside that if statement? Again it’s hard to tell without all of the close brackets.

    Thanks!

    Like

    Reply
    1. majenko Post author

      Yes, WordPress is complete jank when it comes to posting code. It’s forever trashing my post. I need to post the code elsewhere so it can be properly viewed.

      Like

      Reply
      1. Jfrank

        Github might be a good option if your willing to set up an account.

        Any chance you could paste your example into a reply here? Not a problem if the white space gets messed up, if all of the brackets make it through your readers will be able to figure it out.

        Like

  8. cjcharles

    I think your definition of:
    static int pos = 0;
    needs to be global, rather than within your readline function, otherwise pos will just stay as zero surely….

    Like

    Reply
  9. gus

    I do believe the test in the default branch should be:
    if (pos < len-1) {
    buffer[pos++] = readch;
    buffer[pos] = 0;
    }

    Please correct me if I am wrong.
    Thank you

    Like

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s