Home / Arduino / Serial Communication with Microcontrollers

Serial Communication with Microcontrollers

When working on projects, there'll be lots of times when your microcontroller needs serial communication to talk to other devices or other micros. The most common example of this is adding IoT capabilities to your Arduino board or PIC using ESP8266 or GSM module.  A number of sensors and LCD displays also send and receive data serially.

What is Serial Communication?

Serial communication is the process of transmitting data one bit at a time. In contrast, parallel communication is where data bits are sent as a whole. Parallel data transmission is faster than serial transmission but with a number of disadvantages:

  • It needs more wires and therefore can be more expensive to implement
  • The greater number of wires limit it to shorter transmission distances
  • It is susceptible to clock skew, which limits the speed of transmission to the slowest of the links
  • Crosstalk is also an issue due to the proximity of the wires

Serial data transmission answers all the above problems, most especially the first one, as cost and limited pins are common issues in microcontroller system design.

There are generally two types of serial transmission: asynchronous and synchronous which literally means "not synced" and "synced" respectively. In asynchronous data transfer, there is no clock signal transmitted and received. The common UART (also known as SCI) device found in microcontrollers is asynchronous. When the communication is synchronous, there is an extra line for the clock signal. I2C and SPI are both considered synchronous.

The clock signal is used to ensure that the correct data bits are received. In SCI, the effect of the absence of a clock signal is mitigated by adding overhead. The added overhead affects throughput (the rate of production) since they don't carry useful information. However, the absence of the clock signal also means that SCI is simpler to setup and implement as compared  to the complex hardware needed by synchronous protocols.

For the rest of this post, serial will now refer to asynchronous data transmission used by UART devices found in Arduino and PIC microcontrollers. Synchronous data transmission protocols like I2C and SPI is discussed in a separate article.

The Serial Protocol

Serial communication follows a simple protocol (rules to follow) to ensure correct transmission. A serial data consists of

  • Data Bits
  • Synchronization Bits
  • Parity Bits

The added overhead I was referring to are the synchronization (or sync) and parity bits. The sync bits are start and stop bits to indicate the start and end of transmission. When the serial bus (or data line) is idle, the voltage level is pulled high. The sending device sends a logic 0 (the start bit) to kickstart the transmission.

It is then followed by the data bits which can range from 5 to 9 bits. The order by which the data bits are sent is from the least significant bit (LSB) to the most significant bit (MSB).

The parity bit, used for error checking, is then sent next. Parity can either be even or odd. If even parity is chosen, the parity bit is added to make the number of 1's even. For odd parity, the added parity bit must make the number of 1's odd.

Example: The data bits 1010001 have three 1's. For even parity, the parity bit should be 1 to make the total number of 1's equal to 4 (an even number). For odd parity, the parity bit is 0 since there is already an odd number of 1's.

The serial data is tailed by stop bits, which is can one or two logic 1's, to signal the end of transmission.

The baud rate, or speed of transmission, is also part of the protocol. It is important that the sender and the receiver should have the same baud rate. The rates have been standardized to any of these values:

  • 1200
  • 2400
  • 4800
  • 9600
  • 19200
  • 38400
  • 57600
  • 115200

9600 is the most common and is the default baud rate for most serial terminals and simulators. I'm not sure why is that but I know that 9600 is the maximum serial port speed of old IBM 5150 computers (popularly know as the first "personal computer").

If you connect a serial output pin which sends a "A" (ascii 41 or 01000001 in binary) character to the oscilloscope, this is what you would see on the screen:

Using UART

A microcontrollers UART device has a transmit (Tx) and receive (Rx) line. If you want to interface your micro to another device which uses serial communication, you need to connect the micro's Tx to the device's Rx and vice versa.

The number of UART device varies among microcontrollers. For example, the Arduino UNO's ATMega 328p has one while the Arduino Mega's ATMega 2560 has four.

The PIC16F84A has no UART device. The only way for it to communicate serially is through bit-banging --- a method of directly sending bits by pulling a pin high or low at a specific length of time.

Example: If you want to send the binary data 00110010 serially at a speed of 9,600 baud, you could toggle the state of the Tx pin you assigned based on the binary sequence. The length for which the pin remains on the specified state (1 or 0) would be 104.167 us. 

Bit banging is the method used in Arduino's software serial library, and PBP's SEROUT command.

By experience, software serial is more susceptible to transmission errors than using hardware UART. The only reason I use software serial is when I need to use multiple serial devices at once. I highly recommend using the hardware UART if you are only using one serial device and your microcontroller supports one.

PICs like the PIC16F877A or PIC16F628A each has one UART module. The Beaglebone Black and Raspberry Pi also contain serial communication hardware.

Serial with Arduino

Hardware Serial

Arduino boards communicate with the PC via USB cable or to other serial devices using the serial pins. Each arduino board has at least one serial port and is commonly assigned to digital pins 0 and 1 (Tx and Rx respectively). Using these pins while loading a program to the board causes an error because it's the same pins that are used for USB connection.

The arduino serial port uses TTL signal levels, which means the logic 1 is +5V and logic 0 is 0 V. A computer's serial port uses different voltage levels: the logic 1 is -3 to -25 V while the logic 0 is +3 to +25 V. This is why it's a bad idea to connect the arduino's serial pins directly to a PC's serial port.

The USB port (which is also serial) also uses different voltage levels and protocol. Arduino boards have USB to TTL chips included to interface the board to the computer via USB. Some ATMega micros have bootloaders that doesn't require a USB to TTL chip.

The AnalogReadSerial example is one of the best starting points in understanding how to use the arduino's serial port. Open it using the arduino IDE: File -> Examples -> Basics -> AnalogReadSerial

The Serial.begin() function specifies the baud rate. Here it is set to 9600 baud.

This example reads analog voltage at pin A0 and stores it to the variable sensorValue. The sensorValue is then printed into the serial terminal via the Serial.println() function.

If you want your arduino to receive data, you'll be using the Serial.read() function like how it's use below:

For more information on arduino's serial functions, check out the official documentation Arduino Serial.

Arduino Software Serial

Running out of pins to use is common in microcontroller programming. This is especially true for the Arduino UNO which has only one serial port. The GSM module in fact uses software serial to communicate witht the arduino board.  Arduino's software serial library is demonstrated by the included built-in example File -> Examples -> Software Serial -> SoftwareSerialExample:

You can assign whichever pins are available for use as Tx and Rx pins! (The above code assigns the serial pins to digital pins 10 and 11). Once the software serial object is defined, you can now use the functions of the library which are the basically the same functions as with hardware serial.

Serial with PICs

Software Serial

As mentioned, the PIC16F84A has no serial port. The only way to implement it is through software. In PBP, software serial is done by the SEROUT command. The syntax of the SEROUT command is as follows:

SEROUT Pin, Mode, [Item{,Item...}]

Pin is the name of the Tx pin (e.g. PORTB.0) and Mode is based on this table:

Item would be the data you would send.

PBP allows string to be sent so SEROUT PORTB.0, 2, ["Hello"] sends Hello to the serial terminal. A numeric item will send the ascii representation of that number. For example, SEROUT PORTB.0,2,[65] will print the character "A" in the serial terminal because 65 is the ascii decimal number for that character. If you want to print the number 65, you need to place a pound sign before the number: SEROUT PORTB.0,2,[#65].

For more info about the SEROUT command, read "Serial with PBP" or the official PBP compiler manual.

Software serial in assembly is longer work. Here's how to send a single character "A" using a single pin as transmitter:

Here I created a delay routine to keep the state of the pin for about 105 us (approx. 1/9600). I also needed to keep the line idle for a longer time so that the receiving device can distinguish the start bit and the consequently the rest of the data bits. And so I created another delay (delay2) which lasts for around 65 ms.

The character "A" is 41 in hexadecimal. The LSB is sent first and then the rest of the bits follow. The waveform of the serial data (as seen from an oscilloscope) would look like the image above.

If you want to send a string, then you need to know the binary equivalent of each character in the string and just sent each one after the other like what I did here. But if you want to save time and effort then you probably need to use...

PIC Hardware Serial Using PBP

PBP has the HSEROUT command for sending serial data using the PICs UART device. Naturally, this function is unusable for devices that don't have UART module such as the PIC16F84A. The syntax for this command is simple:

HSEROUT [Item{,Item...}]

Where Item is the data to be sent. Before you can use this command, you need three DEFINES:


RCSTA is short for Receive Status and Control while TXSTA is short for Transmit Status and Control. These two are the primary registers you need to manipulate in order to use the hardware UART. The individual bits of these two registers are shown below:

When we did "DEFINE HSER_TXSTA 20h", we set the TXSTA register equal to 20h which is 00100000 in binary. This corresponds to bit 5 (TXEN) being set while the rest of the bits are cleared. This is needed to enable serial transmission from the PIC to another serial device.

Doing "DEFINE HSER_RCSTA 90h" gives the RCSTA register a value of 90h or 10010000 in binary. This sets bit 7 (SPEN) and bit 4 (CREN). The SPEN bit is the master switch of the entire UART module while CREN allows for continuous receiving of bits on the RX pin.

Lastly, "DEFINE HSER_BAUD 2400" sets the baud rate to 2400.

As usual, doing serial using assembly takes time even if we'll now be using the hardware UART. I'll go straight to the example code (I used PIC16F628A btw):

Baud Rate Calculations

The baud rate is set by manipulating the SPBRG register and the BRGH bit on TXSTA. The baud rate formulas are as follows:

You can choose between low speed and high speed baud rate by setting or clearing the BRGH bit. The baud rate number doesn't indicate if its high speed or low speed so 9600 baud can be used in both speeds. However, the datasheet recommends using the high speed setting because the baud rate error is said to be smaller. (Notice that you can also choose between asynchronous and synchronous transmission by setting or clearing the SYNC bit --- bit 4 of TXSTA).

Using the formulas above, the datasheet provided a table for the common baud rates using the usual oscillator values. This is for the 4 MHz oscillator low speed:

This is for the same oscillator at high speed setting:

Look at the %ERROR column for the 9,600 baud rate at both tables. That's more than 40x of difference in error!

Now back to the code. I placed the value 25 to the SPBRG register as suggested by the table above. Then I set the TXEN and BRGH bits on the TXSTA register, followed by setting the SPEN bit on the RCSTA register. The next part is placing 21 hex (! character) in the empty register char0.

To send the data, the contents of char0 must be moved to TXREG register. Checking bit TRMT of the TXSTA register ensures that the data is now out from the buffer and is on its way to the other device.

Again if you need to send a string of characters, you just need to move one character to the TXREG register at a time and wait for the buffer to be cleared by checking if the TRMT bit of the TXSTA register is set every after you sent a character to TXREG.

Serial with Embedded Linux (Beaglebone Black/Raspberry Pi)

Just like any other peripheral, using serial with the Beaglebone Black and Raspberry Pi involves the use of device tree overlays. I will consider these two as one device since both are inherently Linux computers. If you need a more specific approach, a tutorial on serial for beaglebone black can be found here while a tutorial for raspberry pi serial communication is presented here.

The debug serial ports for both Beaglebone Black and Raspberry Pi are enabled by default. The debug serial port (which is the J1 pins) for the BBB is found in /dev/ttyO0 while the Raspberry Pi serial port is at /dev/ttyAMA0. To use the serial port, you need an app such as minicom. You can install minicom by doing:

Once installed, you can now run the minicom app:


After that, anything you'll type on the terminal will be sent out to the serial port. Easy!

The best way to learn more about serial communication with microcontrollers is to code them yourselves! So I suggest you try on the example codes here and then start building projects. I hope you have fun as much as I did!

Check Also

L298N Motor Controller Tutorial

How to Use L298N Motor Driver

You can drive a LED on or off using a microcontroller like Arduino or PIC. …

Leave a Reply

Your email address will not be published. Required fields are marked *