Arduino Serial Read: Or how to control your Arduino from a PC!  Find out how the Serial read function works to receive multiple bytes using interrupts and buffers. Understand its Internal Operation so you Don't Fall into a Trap...


Arduino Serial Read:
  • Allows you to very Easily Get Data from the PC via the serial port.

  • Uses Circular Buffers so you don't waste time polling hardware.

  • Requires use of Serial.begin() to initialise the Arduino serial hardware.

  • Requires use of Serial.available() to check for data ready.

  • Ensure you avoid the trap of missed data.

You'll need Arduino serial read to get data into your Arduino but first you will need to initialise the serial port with Serial.begin() followed by Serial.available() to check if any new data is ready from the serial port.

Given that the internal serial port hardware can only accept one byte at a time from the serial link, and you want to accept lots of character data from the port continuously.

The question is how can you ensure that no data is lost if your program is busy doing something else?

The answer is you don't have to think about this! But you should.

Code underlying Arduino Serial read implements interrupt driven input and circular buffering for both receive and transmit directions.

Arduino Serial Read: Operational Overview

Code written in HardwareSerial.h  and HardwareSerial.cpp defines circular queues that are 64 bytes long unless your Arduino SRAM space is less than 1024 bytes in which case the buffers are 16 bytes long.

There are also head and tail pointers:

volatile rx_buffer_index_t _rx_buffer_head;
volatile rx_buffer_index_t _rx_buffer_tail;
volatile tx_buffer_index_t _tx_buffer_head;
volatile tx_buffer_index_t _tx_buffer_tail; 

You can also see that there are two pointers for each buffer, so for an Arduino Uno or Nano 128Bytes are reserved for serial buffering. Just in case you were wondering where your SRAM disappeared!

Note: Two SRAM buffers are assigned when you use the Serial class.

The code also defines two interrupt vectors:

inline void _rx_complete_irq(void);
id _tx_udr_empty_irq(void);

These are attached to the RX buffer and TX buffer interrupts respectively.

When the RX interrupt triggers, a byte of data is placed into the circular buffer at _rx_buffer_tail and _rx_buffer_tail is incremented. When you use Serial.read() to get the data out bytes are read out from _rx_buffer_head until _rx_buffer_head equals _rx_buffer_tail - when the queue is empty.

Because it is a circular buffer code ensures that _rx_buffer_tail and _rx_buffer_tail wrap correctly.

A similar operation is done for the transmit buffer.

Arduino Serial Read: What use are circular buffers?

So what do circular buffers do for you?

Essentially interrupt driven circular buffers allow the processor to do other tasks without continually having the poll the serial hardware. So your program can be off doing some other important task and does not have to read the serial hardware, as that is done in the background.

Note: Buffering lets the main program execute with no polling.

The reason it works is that reading bytes from the SRAM is very fast - and your code does not have to wait for the serial port to update as data is transferred directly from the serial port hardware to the circular buffers automatically when a serial interrupt is generated.

Lets assume that the serial port is initialised to 9600Baud and since the  Arduino buffer is 64 bytes long it could receive 64 bytes. The time that could be spent in this is (1.0/9600)*64 = 6.6ms. i.e. this is the maximum time you could wait not getting data from the port. So for an Arduino running at 16MHz that is 6.6e-3/(1.0/16e6) = 105600 instructions.

That is a lot of instructions and means in practice the Arduino has plenty of time to do other stuff and can occasionally read the serial port with:

   Serial.read();
If you did not have a circular buffer you would need to read the internal  serial port hardware buffer every (1.0/9600)s or every 104us. That would allow 104e-6/(1.0/16e6) = 1664 instructions - far less than before so your program would be forced to read the data more often.

If the baud rate is increased then there would be even less time to capture data from the serial port.

Note: Increasing the Baud rate fills the buffers more quickly so your program must read the buffers more often.

Arduino Serial Read: Avoid the serial input trap

How to avoid the trap, that you could easily fall into, of missing data.

Note that for the calculation above the assumption is that data is coming back continuously from the PC - which may or may not be the case - it is the worst case operation where data is sent continually, and fast from the PC.

When you use the Arduino IDE you type in a string and hit enter. If you typed in a string that was 65 bytes long data could be missed! The Arudino code is assuming that the string length is usually less than or equal to the 64 byte buffer.

For human interaction this is a good assumption - as you generally type a string and wait to see the response. Where this falls down is if the PC serial output  is generated from another source e.g. a data logging application that could continuously send data. In this case reading the serial data at the Arduino in a timely manner becomes essential. This is a design/test decision.

Note for smaller devices with less SRAM i.e. smaller than 1024 bytes, the buffer is 16 bytes long so there is even less time to read the buffer, and you can't send long strings!

Arduino Serial Read: Example Sketch

The example is very simple  and outputs whatever character is received from the serial port (one character output per line). Serial.available() is used to find out if there is any data at the port, and because of the circular buffers used in the Arduino code there could be more than 1 character available, which is why the comparison "greater than" is used.

If there is a character available then it is printed to the serial output port using Serial.println() - the data appears in the Serial monitor in the Arduino IDE.

// Example showing Arduino Serial read operation

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

void loop() {
char ch;

  if (Serial.available() > 0) {
    ch = Serial.read();
    Serial.println(ch);
  }
}

Arduino Serial Read: Serial monitor output

This is the output you will see in the serial monitor

You have to type in a string of data in the serial monitor and then press enter to transmit the string to the Arduino where the following transparent text is shown "Message (Enter to send message to 'Arduino Uno' on 'COM4')"

Arduino Serial Read

Here the text "hello<Enter>" was typed.

When developing some programs I often use a serial terminal program as I don't want to have to hit Enter all the time - the data is sent immediately so that a program action can happen immediately.

You can use a character delimiter (detected in the program) to separate commands if you want to, or initially just react to the alphabet to do stuff immediately (or a mixture of both) - the serial decode can get quite complicated e.g. if you want to send floating point data etc.

Arduino Serial Read: Conclusions

The Arduino Serial read function is easy to use and provides built in buffers that allow serial port data to be captured with ease.

All you do is occasionally use the Serial.read() function in the main loop of your program, to receive serial data from the internal serial port hardware.

Ensure you read the buffer fast enough compared to the Baud rate - to avoid the trap of missing data. Note that if you increase the Baud rate the buffer will fill up faster so read it more often. As discussed above this also depends on the speed of output of the serial generating device.

You can see an example of serial asynchronicity using just your Arduino here.

The two other functions that for a basic part of Serial code are:

     Serial.available()
     Serial.begin()
   



Comments

Have your say about what you just read! Leave me a comment in the box below.

Don’t see the comments box? Log in to your Facebook account, give Facebook consent, then return to this page and refresh it.



Privacy Policy | Contact | About Me

Site Map | Terms of Use