L1: Intro to Serial
Table of Contents
- Serial communication with Arduino
- Developing serial communication software applications
- Example serial programs
- Activity
- Resources
- 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()
. The Serial.begin()
function has two overloaded options:
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.
Once Serial.begin()
is called, the Arduino Uno and Leonardo take over Pins 1 and 0 for serial transmission and reception, respectively. The RX and TX LEDs light up on the board corresponding to communication. 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!).
Baud rate
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.
Serial buffers
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 ↔ Arduino
serial 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
Serial
to 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 readBytes()
or readBytesUntil()
.
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.setTimeout()
). 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:
All three 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.print()
and Serial.println()
, which transmits data as human-readable ASCII text.
To read ASCII data, we can use Serial.readString()
and Serial.readStringUntil()
:
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.setTimeout()
). Serial.readStringUntil()
terminates either if a timeout is reached or if a terminator character is identified.
To write ASCII data, we use Serial.print()
and Serial.println()
:
Both functions return the number of bytes written. See their respective documentation pages for more details: Serial.print()
and Serial.println()
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 0x00
and 0xFF
in hexadecimal). Sending via binary looks like:
So, for example, if getSignal()
returns 15, we would transmit 0000 1111
(or 0x0F
). If 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 Serial.println()
:
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.print()
, 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
(0x0A
).
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 0000 1010
).
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).
Formatting messages
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!
Handshaking
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.
Acknowledging data
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
Below, we are going to show a few different examples using the command line, Python, and then JavaScript. To keep things simple, in this lesson, we are going to focus on unidirectional communication from the computer to the Arduino (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.
Demo circuit
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 Serial.begin(<baud rate>)
.
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!)
Windows
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 getportnames()
.
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.
PS> $port.open()
Write to the port using ASCII-encoded text with WriteLine(<str>)
:
PS> $port.WriteLine(“Hello!")
Similarly, to read from the port, use ReadLine()
:
PS> $port.ReadLine()
Arduino received: 'Hello!'
Finally, to close the port, use Close()
.
PS> $port.Close()
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 y
.
Python
Finally, let’s make a simple program in Python to write and read data off the serial port. This is simply to demonstrate the overall programming concepts before we dive more deeply into JavaScript solutions for our other lessons. Again, you can really use any programming language you like!
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 Serial
object.
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 num.encode("ascii", "ignore")
Now we’re ready to send the data using pySerial’s write(<data>)
function.
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!
Activity
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.
Resources
-
Intro to Asynchronous Serial Communications, NYU ITP Physical Computing course
-
Serial Communication, Sparkfun.com
-
Asynchronous Serial Communication: The Basics, NYU ITP Physical Computing course
Videos
-
Serial 1: Introduction, NYU ITP Physical Computing course video
-
Serial 2: Logic Analyzer and ASCII, NYU ITP Physical Computing course video
Next Lesson
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.