Please note that this is a revised version of advice in this earlier Thread which has become very long. As far as possible I have kept code examples identical or simplifed them slightly. It should not be necessary to refer to older Thread, but feel free to do so.
The following sections are in this Tutorial
Please note that tutorial continues into next 2 Posts
Newcomers often seem to have difficulty with process of receiving Serial data on Arduino - especially when they need to receive more than a single character. The fact that there are 18 different functions listed on Serial reference page probably does not help
You could write a small book and still not cover every possible situation for data reception. Rather than write pages and pages that few would read I thought it would be more useful to present a few examples which will probably cover all of a newcomer’s needs. And when you understand these examples you should be able to figure out solutions for other strange cases.
Almost all serial input data can be covered by three simple situations
A - when only a single character is required
B - when only simple manual input from Serial Monitor is required
C - other
When anything sends serial data to Arduino it arrives into Arduino input buffer at a speed set by baud rate. At 9600 baud about 960 characters arrive per second which means there is a gap of just over 1 millisecond between characters. The Arduino can do a lot in 1 millisecond so code that follows is designed not to waste time waiting when there is nothing in input buffer even if all of data has not yet arrived. Even at 115200 baud there is still 86 microseconds or 1376 Arduino instructions between characters.
And because data arrives relatively slowly it is easy for Arduino to empty serial input buffer even though all of data has not yet arrived. Many newcomers make mistake of assuming that something like while( Serial.available() > 0) { will pick up all data that is sent. But it is far more likely that WHILE will empty buffer even though only part of data has arrived.
In very many cases all that is needed is to send a single character to Arduino. Between upper and lower case letters and numeric characters there are 62 options. For example you could use ‘F’ for forward, ‘R’ for reverse and ‘S’ for stop.
Code to receive a single character is as simple as this
// Example 1 - Receiving single characters char receivedChar; boolean newData = false; /*F******************************************************************** * **********************************************************************/ void setup() { Serial.begin( 9600 ); Serial.println( "<Arduino is ready>"); } /*F******************************************************************** * **********************************************************************/ void loop() { recvOneChar(); showNewData(); } /*F******************************************************************** * **********************************************************************/ void recvOneChar() { if( Serial.available() > 0) { receivedChar = Serial.read(); newData = true; } } /*F******************************************************************** * **********************************************************************/ void showNewData() { if( newData == true ) { Serial.print( "This just in ... "); Serial.println( receivedChar); newData = false; } }
Even though this example is short and simple I have deliberately put code to receive character into a separate function called recvOneChar() as that makes it simple to add it into any other program. I also have code for showing character in function showNewData() because you can change that to do whatever you want without upsetting rest of code.
If you wish to use code from any of examples in your own program I suggest that you just copy complete functions from relevant example and create necessary global variables at top of your own program.
If you need to receive more than a single character from Serial Monitor (perhaps you want to input people’s names) you will need some method of letting Arduino know when it has received full message. The simplest way to do this is to set line-ending to newline.
This is done with box at bottom of Serial Monitor window. You can choose between “No line ending”, “Newline”, “Carriage return” and “Both NL and CR”. When you select “Newline” option a new-line character (’\n’) is added at end of everything you send.
// Example 2 - Receive with an end-marker const byte numChars = 32; char receivedChars[numChars]; // an array to store received data boolean newData = false; /*F******************************************************************** * **********************************************************************/ void setup() { Serial.begin( 9600 ); Serial.println( "<Arduino is ready>" ); } /*F******************************************************************** * **********************************************************************/ void loop() { recvWithEndMarker(); showNewData(); } /*F******************************************************************** * **********************************************************************/ void recvWithEndMarker() { static byte ndx = 0; char endMarker = '\n', char rc; while( Serial.available() > 0 && newData == false) { rc = Serial.read(); if( rc != endMarker) { receivedChars[ndx] = rc; ndx++; if( ndx >= numChars) ndx = numChars - 1; } else { receivedChars[ndx] = '\0'; // TERMINATE STRING ndx = 0; newData = true; } } } /*F******************************************************************** * **********************************************************************/ void showNewData() { if( newData == true) { Serial.print( "This just in ... " ); Serial.println( receivedChars ); newData = false; } }
This version of program reads all characters into an array until it detects Newline character as an end marker.
Continued in next Post
…R
The simple system in Example 2 will work well with a sympathetic human who does not try to mess it up. But if computer or person sending data cannot know when Arduino is ready to receive there is a real risk that Arduino will not know where data starts.
If you would like to explore this, change end marker in previous program from ‘\n’ to ‘>’ so that you can include end marker in your text for illustration purposes. (You can’t manually enter a Newline character in text you are sending from Serial Monitor). And put line ending back to “No line ending”
Now, with revised code send “qwert>” and you will see that it behaves exactly same as when you were using Newline as end marker.
But if you try this “asdfg>zxcvb” you will only see first part “asdfg”. And then if you send “qwert>” you will see “zxcvbqwert” because Arduino has become confused and cannot know that it should have ignored “zxcvb”.
The answer to this problem is to include a start-marker as well as an end-marker.
// Example 3 - Receive with start- and end-markers const byte numChars = 32; char receivedChars[numChars]; boolean newData = false; /*F******************************************************************** * **********************************************************************/ void setup() { Serial.begin( 9600 ); Serial.println( "<Arduino is ready>"); } /*F******************************************************************** * **********************************************************************/ void loop() { recvWithStartEndMarkers(); showNewData(); } /*F******************************************************************** * **********************************************************************/ void recvWithStartEndMarkers() { static boolean recvInProgress = false; static byte ndx = 0; char startMarker = '<'; char endMarker = '>'; char rc; while( Serial.available() > 0 && newData == false) { rc = Serial.read(); if( recvInProgress == true) { if( rc != endMarker) { receivedChars[ndx] = rc; ndx++; if( ndx >= numChars ) ndx = numChars - 1; } else { receivedChars[ndx] = '\0'; // TERMINATE STRING recvInProgress = false; ndx = 0; newData = true; } } else if( rc == startMarker ) recvInProgress = true; } } /*F******************************************************************** * **********************************************************************/ void showNewData() { if( newData == true ) { Serial.print( "This just in ... " ); Serial.println( receivedChars ); newData = false; } }
To see how it works try sending “qwertyzxcvb” and you will see that it ignores everything except “asdfg”.
In this program you will notice that there is a new variable called recvInProgress. This is necessary because a distinction needs to be made between unwanted characters that arrive before start marker and valid characters that arrive after start marker.
This version of program is very similar to Arduino code in this Python - Arduino demo .
It is important to notice that each time function recvWithEndMarker() or recvWithStartEndMarker() is called it reads whatever characters may have arrived in serial input buffer and places them in array receivedChars.
If there is nothing in buffer recvWithEndMarker() does not waste time waiting.
In case of recvWithStartEndMarker() all characters are discarded until start-marker is detected.
If end-marker has not yet arrived it will try again when loop() next repeats.
For best results it is important to ensure that loop() can repeat as quickly as possible - hundreds or even thousands of times per second.
In examples I have assumed that you will not need to receive more than 32 bytes. That can easily be altered by changing value in constant numChars.
Note that 64 byte size of Arduino serial input buffer does not limit number of characters that you can receive because code in examples can empty buffer faster than new data arrives.
You will notice that examples here do not use any of these Arduino functions
Serial.parseInt()
Serial.parseFloat()
Serial.readBytes()
Serial.readBytesUntil()
All of these are blocking functions that prevent Arduino from doing something else until they are satisfied, or until timeout expires. The examples here do exactly same job without blocking. That allows Arduino to do other things while it is waiting for data to arrive.
I don’t recommend using this function - I prefer to deal with Serial data when it suits me. It behaves just as if you had this code as last thing in loop().
if( Serial.available() > 0) {
mySerialEvent();
}
It is probably worth mentioning that poorly named Serial.flush() function does not empty input buffer. It is only relevant when Arduino is sending data and its purpose is to block Arduino until all outgoing data has been sent.
If you need to ensure Serial input buffer is empty you can do so like this
while( Serial.available() > 0) { Serial.read(); }
…R
So far examples have assumed you want to receive text. But perhaps you want to send a number or maybe a mix of text and numbers.
The simplest case is where you want to type a number into Serial Monitor (I am assuming you have line-ending set to newline). Let’s assume you want to send number 234. This is a variation on Example 2 and it will work with any integer value. Note that if you don’t enter a valid number it will show as 0 (zero).
// EXAMPLE 4 - RECEIVE A NUMBER AS TEXT AND CONVERT IT TO AN INT // ================= DEFINES ========================== // ================= PROTOTYPES ========================== void showNewNumber(); void recvWithEndMarker(); // ================= VARIABLES ========================== const byte numChars = 32; char receivedChars[numChars]; // AN ARRAY TO STORE RECEIVED DATA boolean newData = false; int dataNumber = 0; // NEW FOR THIS VERSION /*F******************************************************************** * **********************************************************************/ void setup() { Serial.begin( 9600 ); Serial.println( "<Arduino is ready>"); } /*F******************************************************************** * **********************************************************************/ void loop() { recvWithEndMarker(); showNewNumber(); } /*F******************************************************************** * **********************************************************************/ void recvWithEndMarker() { static byte ndx = 0; char endMarker = '\n', rc; if( Serial.available() > 0) { rc = Serial.read(); if( rc != endMarker ) { receivedChars[ndx] = rc; ndx++; if( ndx >= numChars ) ndx = numChars - 1; } else { receivedChars[ndx] = '\0'; // TERMINATE STRING ndx = 0; newData = true; } } } /*F******************************************************************** * **********************************************************************/ void showNewNumber() { if( newData == true ) { dataNumber = 0; // NEW FOR THIS VERSION dataNumber = atoi( receivedChars ); // NEW FOR THIS VERSION Serial.print( "This just in ... " ); Serial.println( receivedChars ); Serial.print( "Data as Number ... " ); // NEW FOR THIS VERSION Serial.println( dataNumber ); // NEW FOR THIS VERSION newData = false; } }
It is also straightforward to receive several pieces of data in a single message and parse data to assign them to individual variables. This example assumes you send something like “<HelloWorld, 12, 24.7>”. This is an extension of Example 3.
A function called parseData() has been added and function showParsedData() takes place of showNewData() in earlier example.
/*H******************************************************************** * EXAMPLE 5 - RECEIVE WITH START- AND END-MARKERS COMBINED WITH PARSING **********************************************************************/ // ==================== DEFINES ======================== // ==================== PROTOTYPES ======================== void showParsedData(); void parseData(); void recvWithStartEndMarkers(); // ==================== VARIABLES ======================== const byte numChars = 32; char receivedChars[numChars]; char tempChars[numChars]; // TEMPORARY ARRAY FOR USE WHEN PARSING char messageFromPC[numChars] = {0}; int integerFromPC = 0; float floatFromPC = 0.0; boolean newData = false; /*F******************************************************************** * **********************************************************************/ void setup() { Serial.begin( 9600 ); Serial.println( "This demo expects 3 pieces of data - text, an integer" " and a floating point value"); Serial.println( "Enter data in this style <HelloWorld, 12, 24.7>" ); Serial.println(); } /*F******************************************************************** * **********************************************************************/ void loop() { recvWithStartEndMarkers(); if( newData == true) { strcpy( tempChars, receivedChars); // TEMPORARY COPY NECESSARY PROTECTS ORIGINAL DATA // BECAUSE strtok() USED IN parseData() REPLACES COMMAS WITH \0 parseData(); showParsedData(); newData = false; } } /*F******************************************************************** * **********************************************************************/ void recvWithStartEndMarkers() { static boolean recvInProgress = false; static byte ndx = 0; char startMarker = '<', endMarker = '>', rc; while( Serial.available() > 0 && newData == false) { rc = Serial.read(); if( recvInProgress == true) { if( rc != endMarker) { receivedChars[ndx] = rc; ndx++; if( ndx >= numChars) ndx = numChars - 1; } else { receivedChars[ndx] = '\0'; // TERMINATE STRING recvInProgress = false; ndx = 0; newData = true; } } else if( rc == startMarker) recvInProgress = true; } } /*F******************************************************************** * SPLIT DATA INTO ITS PARTS **********************************************************************/ void parseData() { char * strtokIndx; // USED BY strtok() AS AN INDEX strtokIndx = strtok( tempChars,","); // GET FIRST PART - STRING strcpy( messageFromPC, strtokIndx); // COPY IT TO MESSAGEfROMPC strtokIndx = strtok( NULL, ","); // CONTINUES WHERE PREVIOUS CALL LEFT OFF integerFromPC = atoi( strtokIndx); // CONVERT TO INTEGER strtokIndx = strtok( NULL, ","); floatFromPC = atof( strtokIndx ); // CONVERT TO FLOAT } /*F******************************************************************** * **********************************************************************/ void showParsedData() { Serial.print( "Message "); Serial.println( messageFromPC); Serial.print( "Integer "); Serial.println( integerFromPC); Serial.print( "Float "); Serial.println( floatFromPC); }
So far we have been receiving character data - for example number 121 is represented by characters ‘1’, ‘2’ and ‘1’. It is also possible to send that value as binary data in a single byte - it happens to be Ascii value for character ‘y’. Note that 121 in decimal is same as 0x79 in HEX.
Note that if you are sending binary data it is quite likely that you will need to send as data same values that are used for start- and end-markers. That goes beyond scope of this Tutorial and one way of doing it is illustrated in demo here.
The examples that follow assume that binary data will NEVER include byte values used for start- and end-markers. For simplicity I will continue to use < and > as markers. The byte values for those characters are 0x3C and 0x3E. This will allow you to test program from Serial Monitor by sending, for example, <24y> which will be interpreted by receiving program as binary values 0x32, 0x34 and 0x79. These are Ascii codes for 2, 4 and y.
Of course it would be more usual for binary data to be sent by another computer program - on another Arduino or on a PC.
This is adapted from Example 3
/*H******************************************************************** * Example 6 - Receiving binary data **********************************************************************/ // ==================== DEFINES =========================== // ==================== PROTOTYPES =========================== void recvBytesWithStartEndMarkers(); void showNewData(); // ==================== VARIABLES =========================== const byte numBytes = 32; byte receivedBytes[numBytes]; byte numReceived = 0; boolean newData = false; /*F******************************************************************** * **********************************************************************/ void setup() { Serial.begin( 9600 ); Serial.println( "<Arduino is ready>"); } /*F******************************************************************** * **********************************************************************/ void loop() { recvBytesWithStartEndMarkers(); showNewData(); } /*F******************************************************************** * **********************************************************************/ void recvBytesWithStartEndMarkers() { static boolean recvInProgress = false; static byte ndx = 0, startMarker = 0x3C; byte endMarker = 0x3E, rb; while( Serial.available() > 0 && newData == false) { rb = Serial.read(); if( recvInProgress == true) { if( rb != endMarker) { receivedBytes[ndx] = rb; ndx++; if( ndx >= numBytes ) ndx = numBytes - 1; } else { receivedBytes[ndx] = '\0'; // TERMINATE STRING recvInProgress = false; numReceived = ndx; // SAVE NUMBER FOR USE WHEN PRINTING ndx = 0; newData = true; } } else if( rb == startMarker) recvInProgress = true; } } /*F******************************************************************** * **********************************************************************/ void showNewData() { if( newData == true) { Serial.print( "This just in (HEX values)... "); for( byte n = 0; n < numReceived; n++) { Serial.print( receivedBytes[n], HEX); Serial.print( ' ' ); } Serial.println(); newData = false; } }