L1: Intro to Serial
Table of Contents
- Serial communication with Arduino
- Developing serial communication software applications
- Example serial programs
- Next Lesson
Devices need to communicate. Sensors to microcontrollers. Microcontrollers to computers. Computers to the Internet. And beyond! Many different protocols have been created to support device-to-device communication from Ethernet and Zigbee to WiFi and Bluetooth. In this lesson, we will focus on asynchronous serial communication, specifically TTL serial (Transistor-Transistor Logic Serial)—an enduring standard that has prevailed since the beginning of personal computers and is what the Arduino Serial library uses.
Unlike other popular serial communication protocols like I2C and SPI, TTL serial is asynchronous, which means it does not rely on a shared clock signal (precisely timed voltage pulses) paired with its data lines. This has the benefit of fewer wires but does result in a bit of communication overhead for each transmitted “packet” or data frame.
In this lesson, we’ll dive into asynchronous serial communication and how we can use it for bidrectional
Computer ↔ Arduino communication.
Serial communication with Arduino
We’ve been using Arduino’s serial functionality since our very first set of lessons (e.g., L3: Serial Debugging). However, we’ve glossed over the details and used serial primarily for debugging rather than
Computer ↔ Arduino communication.
On Arduino, we initialize the serial port using
Serial.begin() function has two overloaded options:
Serial.begin() is called, the Arduino Uno and Leonardo take over Pins 1 and 0 for serial transmission and reception, respectively, and the RX and TX LEDs light up on the board. So, after
Serial.begin() is called, you should not use Pins 1 and 0 (unless you’re using them for cross-device communication or to hook up your logic analyzer!).
Thus far, in our lessons, we have been using the first function—
begin(unsigned long baud)—which sets the data rate in bits per second (baud). But what about the second function with
byte config and what does this parameter mean? We’ll dig in to both below.
The baud rate specifies how fast data is sent over serial, which is expressed in bits-per-second (bps). For communicating with a computer, the Arduino docs recommend: 300 bps, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, or 115200. Both devices—in this case, the Arduino and the computer—need to be set to the same baud rate to communicate.
Thus far, speed hasn’t been a concern. We’ve typically used 9600 bps (or 9.6 kbps) for transmitting our debugging info. At 9600 bps, the transmitter transmits one new voltage pulse (e.g., HIGH corresponding to +5V and LOW corresponding to 0V) every 1/9600th of a second, which is interpreted as a bit (a 1 or 0) by the receiver. Arduino recommends up to 115200 or 115.2 kbps, which is 12x faster than 9600 (but still slow by today’s networking standards, of course).
Figure. The Arduino IDE’s Serial Monitor, which has a drop down for baud rate. The baud rate used in
Serial.begin(<baud>) must match this drop down menu setting or Serial Monitor will not properly communicate with Arduino.
What’s the fastest serial baud rate?
This will be microcontroller dependent. The Arduino Uno uses a ATmega328P microcontroller, which states a maximum baud rate of 2,000,000 baud (2 Mbps). On Stack Overflow, Connor Wolf found that though the Uno was capable of communicating at 2Mpbs, the Arduino serial library resulted in only an effective 500 kbps communication rate.
The asynchronous serial communication frame
The second function,
begin(unsigned long baud, byte config), allows for an optional argument that configures the serial transmission packet or frame. A serial transmission frame consists of three pieces: data, parity, and synchronization bits (start and stop).
Figure. An asynchronous serial communication frame.
The data bit specifies the length of the data portion of the transmission frame (5-9), the parity bit is a simple form of error detecting code (and can be turned on with ‘1’ or off with ‘0’), and the synchronization bits help demarcate a frame. There is always one start bit at the beginning of a frame but there can be one or two stop bits at the end (though one is most common). On Arduino, the default transmission frame configuration is: 8 data bits, no parity, one stop bit—this is a common configuration.
Importantly, if the baud and config settings do not match between the Arduino and the computer, communication will not work. If something is not working for you, this is the first thing to double check!
Only one computer program can open a serial port at a time
Only one computer program can open a serial port at a time. For example, if you attempt to open Serial Monitor on the same COM port that has been opened by another program, you will receive an error like this:
Error opening serial port 'COM7'. (Port busy).
Figure. A demonstration of what happens if you try to open Serial Monitor on a COM port that is already opened by another program. The Arduino IDE shows an error stating
Error opening serial port 'COM7'. (Port busy).
Similarly, if we attempt to access a previously opened serial port with PowerShell, we receive
Access to the port 'COM7' is denied.
Figure. Only one software program can access a serial port at a time.
Incoming serial data is stored in a serial buffer, which is read as a first-in, first-out queue (FIFO). On the Arduino, this buffer is 64 bytes (defined in USBAPI.h) and is implemented as a circular or ring buffer. At 9600 baud, this buffer will fill in 53 milliseconds (9600 baud is 1,200 bytes/second or 1 byte every 0.83 millisecond).
Serial to USB? USB to serial?
In the 1980s and 1990s, computers had serial ports like RS-232 connections to support asynchronous serial communication. Now, we use USB (Universal Serial Bus)—a far more sophisticated and efficient serial communication standard that allows multiple devices to communicate over the same wires. However, because asynchronous serial communication persists, USB drivers and our operating systems support asynchronous serial communication over USB. Devices, like the Arduino, include a USB-to-serial converter that shows up as a serial port when you plug them in (just as if you were using an old serial connection). You might see the Arduino device, for example, as a USBtoUART device (UART is Universal Asynchronous Receiver-Transmitter).
Developing serial communication software applications
So, how can we design and implement a computer program to communicate with Arduino via serial? To answer this, let’s decompose serial communication into three high-level layers:
- Hardware layer: How is data communicated over hardware? How many wires are used? What does the voltage signal look like? Thankfully, Arduino handles this for us. And for
Computer ↔ Arduinoserial communication, serial data is transmitted via the USB cable.
- Serial protocol: What is the format of a serial transmission packet (e.g., the data and parity bits)? How do we compose this packet? Again, we do not really need to worry about this. Arduino uses the standard asynchronous serial protocol and includes the software library
Serialto support this. We just need to make sure that both communicating devices are using the same baud rate and data packet configuration.
- Application layer: How do applications communicate together using serial? Aha, this is the key question for this sub-section!
The answer—for better or worse—is completely up to you! If you’re writing serial communication code for both devices (the application on Arduino and the application on your computer), you get to decide how these applications communicate—you’re in complete control. There are, however, some important considerations, including: binary vs. ASCII-encoded data, message formatting, handshaking, and message acknowledgments (call-and-response).
Binary vs. ASCII-encoded data
With serial communication, we can either transmit/receive data as a series of bits (raw binary data) or as alphanumeric characters (ASCII-encoded data).
Reading and writing binary data
To read binary data with Arduino, use
Serial.readBytes() reads bytes from the serial port into a buffer and terminates if the determined length has been read or it times out (see
Serial.readBytesUntil() is similar but also has a terminator parameter—if the terminator byte is detected, the function returns all bytes up to the last byte before the terminator. Both functions return the number of bytes read.
To write binary data, we can use
Serial.write(), which is an overloaded function:
Serial.write() functions return the number of bytes written.
Reading and writing ASCII-encoded data
Reading and writing ASCII-encoded data should feel more familiar. Indeed, for our use-case of serial-based debugging, we’ve been using
Serial.println(), which transmits data as human-readable ASCII text.
To read ASCII data, we can use
Both functions read characters from the serial buffer and store them in a String, which is returned.
Serial.readString() terminates if it times out (see
Serial.readStringUntil() terminates either if a timeout is reached or if a terminator character is identified.
To write ASCII data, we use
Both functions return the number of bytes written. See their respective documentation pages for more details:
Why use binary vs. ASCII?
Why might we want to use binary vs. text encodings? Well, if we are trying to transmit binary data—like an image, video, or song—then communicating via binary is preferred. It’s also more bandwidth efficient (uses fewer bits). However, in our courses, we’re typically transmitting/receiving small amounts of data and it’s beneficial for debugging purposes (and human understanding) to use an ASCII-encoded format.
Binary vs. ASCII example
Let’s take a look at an example. Let’s say we want to transmit a signal that ranges between 0 - 255 from our Arduino to our computer. Because the value only ranges from 0 to 255, we can encode this with 8 bits or a single byte (
0000 0000 to
1111 1111 or
0xFF in hexadecimal). Sending via binary looks like:
So, for example, if
getSignal() returns 15, we would transmit
0000 1111 (or
getSignal() returns 127, we would transmit
0111 1111 (
0x7). If 255, then
1111 1111 (
0xFF). And so on.
However, we could also transmit this using ASCII-encoded data with
However, now if
getSignal() returns 15, we would need to transmit four bytes rather than just one byte. Using the ASCII-encoding chart, we can see that the ASCII encoding for ‘1’ is ASCII 49 or
0011 0001 (
0x31) and ‘5’ is ASCII 53 or
0011 0101 (
0x35). Then, unlike
Serial.println() adds in a carriage return character ‘\r’, which is ASCII 13 or
0000 1101 (
0x0D), and then a newline (or linefeed) character ‘\n’, which is ASCII 10 or
0000 1010 (
Similarly, if we wanted to transmit 127 or 255 using
Serial.println(), we would need five bytes. For example, with 127, we would transmit ‘1’ (ASCII 49 or
0011 0001), ‘2’ (ASCII 50 or
0011 0010), ‘7’ (ASCII 55 or
0011 0111), ‘\r’ (ASCII 13 or
0000 1101), ‘\n’ (ASCII 10 or
Both applications need to use same encoding
Note that the receiver needs to know whether data has been transmitted using binary or ASCII encodings. If the latter, the receiver can simply use a method like
Serial.readStringUntil('\n') and the data will be automatically transformed into an ASCII-encoded String. If the former, then a method like
Serial.readBytes() is necessary and the receiver must know how many bytes are being sent and how to decode them.
For our purposes, we almost always use the ASCII encoding because the benefit of human readability (e.g., sending and receiving text) outweighs efficiency. However, you should consider this on a case-by-case basis depending on your application context, communication medium (wireless vs. wired), and power requirements (e.g., low power applications should minimize transceiving).
The above example simply sent one value per transmission. For binary, we sent one byte per new signal read; for ASCII-encoding, we sent one line per new signal read. However, it’s likely that you’ll want to transmit and receive multiple values. How do we do this?
Again, it’s completely up to you! If you’re using ASCII-encoded transceiving, you could use a comma-separated value (CSV) format, JSON, or some other messaging format of your own design.
As you’ll commonly see in our demo code, we use a simple CSV format like this:
For example, if
sensorVal1 is 896,
sensorVal1 943, and
sensorVal3 is 349, then the above code would send a text string that looks like
896, 943, 349\r\n.
On the receiving end, we might use regex for parsing or write our own parsing code like this:
This example assumes that data is in the order of
sensorVal1, sensorVal2, sensorVal3 and that each received line is the same. To make this communication scheme more flexible, you could transmit a modified CSV with variable names (like key,value pairs) or use JSON. Towards the former, you could transmit the key,value pairs as: “
sensorVal1=896, sensorVal2=943, sensorVal3=349”. The receiver would then parse both the variable names and their values.
In all of our examples, we use very simple CSV formatting with ASCII-encoded transceiving. But feel free to do things differently!
When two devices begin communicating—whether via serial or some other protocol—it’s common to handshake. That is, to transmit and receive a small set of initial messages to establish parameters for communication and to synchronize statuses. For example, upon connection establishment, you might have your two devices exchange their current set of stored values.
Similarly, if you want to ensure that data has arrived and been parsed correctly. You might decide to transmit back an ‘OK’ message after each line of receive data along with a hash (this hash can be used by the original transmitter to verify data arrival).
Example serial programs
Computer → Arduino). That is, the computer will send data and the Arduino will receive data. Later, we will cover
Arduino → Computer and bidirectional (duplex) communication
Computer ↔ Arduino.
And actually, in all of our serial lessons—including this one—we will have the Arduino transmit something back to the computer to aid with debugging and ensure the Arduino received what we expected. We call this an echo message. You’ll see!
Simple Arduino serial receiver program
For our examples below, we will be running a simple program on our Arduino that reads ASCII-encoded data off of the serial port, parses that data into an integer, and uses
analogWrite to output that integer to an output pin. In this case, we have hooked up a red LED with a current limiting resistor to the
OUTPUT_PIN, which is set to
LED_BUILTIN (Pin 13 on the Arduino Uno and Leonardo). The entire program looks like this:
Code. This code is available as SimpleSerialIn.ino on GitHub. We will actually be using SimpleSerialInOLED.ino in our videos.
And here’s the corresponding circuit for the program above, which consists of a current-limiting resistor and LED attached to Pin 13. Of course, you could build almost any circuit to respond to serial input. But let’s keep things simple!
Figure. The corresponding circuit for SimpleSerialIn.ino. Made in Fritzing and PowerPoint.
Using Serial Monitor
Let’s begin by using our now familiar Arduino IDE Serial Monitor tool. With SimpleSerialIn.ino loaded on your Arduino and your Arduino connected to your computer, open the Serial Monitor and send data to our Arduino. Make sure you’ve selected the same baud rate used in
Figure An annotated screenshot the Arduino IDE’s Serial Monitor tool for sending and receiving serial data. The data “echoed” back to our Arduino is shown in the autoscrolling textfield (where it says “Arduino received…”).
Video demo using Serial Monitor
Here’s a video demonstration of sending ASCII-encoded text via Serial Monitor to the Arduino running SimpleSerialInOLED.ino.
Video. A video demonstrating using the Arduino IDE Serial Monitor tool to communicate with the Arduino running SimpleSerialIn.ino. For this video, we are using a slightly modified program called SimpleSerialInOLED.ino along with an OLED display. This allows you to more easily see the received values.
Notice how we are able to print out what the Arduino receives because the Arduino cpde echos the received data back over serial using
Serial.print. This is optional but helpful!
Command lines tools
While we’ve thus far emphasized the Arduino IDE’s Serial Monitor, there is nothing special or unique about that tool. We can use any application or programming language with serial support. Below, we’ll show how to use command line tools for both Windows and Mac/Linux before showing an example with Python (but C#, Objective C, Java, etc. would work too!)
On Windows, we can use the PowerShell terminal, which is built into Windows 10, to read and write data from the serial port. For this, we’ll follow the official PowerShell blog.
First, to find the available serial ports, we can use
PS> [System.IO.Ports.SerialPort]::getportnames() COM7
Then, we’ll create a
SerialPort object, which takes the COM port, the baud rate, serial configuration parameters (parity bit, data bit length, and stop bit).
PS> $port= new-Object System.IO.Ports.SerialPort COM7,9600,None,8,one
Now open this port.
Write to the port using ASCII-encoded text with
Similarly, to read from the port, use
PS> $port.ReadLine() Arduino received: 'Hello!'
Finally, to close the port, use
Thus, the full program is simply:
PS> $port= new-Object System.IO.Ports.SerialPort COM7,9600,None,8,one PS> $port.open() PS> $port.WriteLine("Hello!") PS> $port.ReadLine() PS> $port.Close()
Video demo using Windows PowerShell
Here’s a video demonstration:
Video. A video demonstrating using Windows PowerShell to communicate with the Arduino running SimpleSerialIn.ino. For this video, we are using a slightly modified program called SimpleSerialInOLED.ino along with an OLED display. This allows you to more easily see the received values.
Mac and Linux
On Mac and Linux, we can use the
screen command as described by this Sparkfun tutorial. Screen should be installed by default on Mac. If it’s not installed on Linux, install it with
sudo apt-get install screen.
First, we need to enumerate the available ports. Type:
> ls /dev/tty.* /dev/tty.Bluetooth-Incoming-Port /dev/tty.SLAB_USBtoUART /dev/tty.MALS /dev/tty.SOC
In this case, the Arduino is listed as
/dev/tty.SLAB_USBtoUART. We can connect to it via screen by typing
screen <port_name> <baud_rate>:
> screen /dev/tty.SLAB_USBtoUART 9600
You terminal should go blank with a flashing cursor. You are now connected to that port. Anything you write will be instantly sent to Arduino as ASCII-encoded text.
To disconnect, you must type
control-a followed by
control-\. The screen program will then ask if you want to exit. Type
For serial communication with Python, we’ll use the pySerial library. With Python3 installed, open your terminal and type:
> pip3 install pyserial
This will install the pySerial library. pySerial is quite straightforward and pySerial’s “fast intro” docs provide a number of examples.
Let’s write a quick Python program to communicate with SimpleSerialIn.ino.
First, import the required libraries and then create and initialize a pySerial
Now, write code to ask the user to input a number between 0 and 255:
Then encode this data as a string. You can force it to ASCII via
Now we’re ready to send the data using pySerial’s
Finally, read the response from the Arduino and print it out:
And that’s it! This code is available as serial_demo.py in our GitHub. Note, after creating the
Serial object, we wrap everything in a
While True: statement to infinitely loop and ask for new user data. See video below.
Video demo using Python
Video. A video demonstrating using Python3 with pySerial to communicate with the Arduino running SimpleSerialIn.ino. For this video, we are using a slightly modified program called SimpleSerialInOLED.ino along with an OLED display. This allows you to more easily see the received values.
Using Python for real-time gesture recognition
Of course, we can do significantly more interesting things using serial communication. In the video below, for example, we demonstrate a Python program that reads in real-time accelerometer data sent via the Arduino over serial and classifies gestures (using a template matching).
Video. A video demonstrating real-time gesture recognition using 3-axis accelerometer data sent via the Arduino over serial. We wrote the gesture recognizer in Python; however, we are not linking to the code because we use it as an assignment in some of our courses. You can learn more in our Signal Classification lesson series.
There is a world of possibilities here. And we’ll begin to explore them in this lesson series!
For your prototyping journals, run SimpleSerialIn.ino or SimpleSerialInOLED.ino with the appropriate circuit and choose one of the above approaches (or develop your own!) to communicate with the Arduino. Take a video and reflect on what you’ve learned in this lesson.
Intro to Asynchronous Serial Communications, NYU ITP Physical Computing course
Serial Communication, Sparkfun.com
Asynchronous Serial Communication: The Basics, NYU ITP Physical Computing course
Serial 1: Introduction, NYU ITP Physical Computing course video
Serial 2: Logic Analyzer and ASCII, NYU ITP Physical Computing course video
In the next lesson, we’ll apply our newfound serial knowledge to communicating with our Arduino via our web browsers using the Web Serial API.