Getting Started With UART
Universal asynchronous receiver/transmitter often shortened to UART is a device used for serial communication. It takes 1 byte of data and adds start and stop bits as well as parity bits (optional) before transmitting them over the TX pin on microcontrollers. The Rx pin receives serial data which it will check against any errors using the given number of Parity Bits in order to ensure accuracy; converting each packet into bytes if needed.
Advanced UARTs use a buffer to store the received data till the stop bit received before sending to microcontroller. The UART releases the buffer data on a first-in-first-out (FIFO) basis where the buffer value can be ranging anywhere between a few bits to thousands of bits.
What Is Serial Communication?
Serial Communication is the process of transferring data one bit at a time over a communication channel. It creates a symbiotic system by interlinking circuits and the peripheral components. There are two types of Serial interfaces:
- Synchronous Serial Interface: In this communication, we always pair the data lines with a clock signal. Therefore all the devices present on the same synchronous serial bus share a common clock. This makes the transfer much faster but also requires an additional wire between the communicating devices. Common examples of synchronous serial interfaces are I2C and SPI.
- Asynchronous Serial Interface: Here, the data is transferred without the use of an external clock signal. There is no need for any additional wires for communication between devices, but the reliability and speed of data transmission decrease. This kind of communication is also feasible in IP-telephony and IP-TV
In Serial Communication, there are multiple mechanisms to maintain strong communication between the devices. These mechanisms are:
- Data bits
- Synchronization bits
- Parity bits
- And baud rate
Data transfer can happen in multiple ways in serial communication, making it a highly configurable protocol. But always make sure that you configure both the devices on the serial bus using the same protocols.
UART Data Frame
A block of data(bytes in most cases) is transmitted in the form of a packet or frame of bits. These frames are created by appending synchronization and parity bits in our data.
Wiring And Hardware
A serial bus comprises just two wires, one for sending data and one to receive. The receiver pin of a serial device is RX, whereas the transmitter pin is TX.
You should always connect the RX of one device to the TX of the second device.
The connection between sender and receiver in a serial interface is either half-duplex or full-duplex. In Full-duplex, both the devices can send and receive data simultaneously, whereas in half-duplex, the communication is only one way at a time. There is also a type of connection that requires just a singular wire from the master device’s TX to the slave’s RX line. We generally call this type of connection – simplex serial communication.
Programming A Duplex Serial Communication Bus
In this program, the UART implements standard UART/USART duplex serial communication that consists of the lines RX and TX.
You can create and initialize the UART objects by the following command:
from machine import UART
uart = UART(1, 9600) # init with given baudrate
uart.init(9600, bits=8, parity=None, stop=1) # init with given parameters
The supported parameters (uart.init) for a Pyboard are: Bits should be at 7, 8, or 9. Along with parity= None, only 8 and 9 bits are supported. If parity is enabled, then only 7 and 8 bits are supported.
A UART object acts like a stream object where we use the standard stream method for reading and writing.
uart.read(10) # read 10 characters, returns a bytes object
uart.read() # read all available characters
uart.readline() # read a line
uart.readinto(buf) # read and store into the given buffer
uart.write('abc') # write the 3 characters
Individual characters can be read/written by:
uart.readchar() # read 1 character and returns it as an integer
uart.writechar(42) # write 1 character
To check whether anything is to be read, use:
uart.any() # returns the number of characters waiting
NOTE: UART can communicate with only one peripheral device, unlike I2C and SPI, where a single master can communicate with multiple slave devices.
NOTE: The stream functions were earlier used as uart.recv and uart.send instead of read, write, etc., used in the MicroPython v1.3.4.
Constructors in UART
Construct a UART object on the given bus. The bus value may vary across different boards. For a Pyboard bus values can be 1-4, 6, ‘XA’, ‘XB’, ‘YA’, or ‘YB’.
For Pybord lite, you need to take the bus values as 1, 2, 6, ‘XB’, or ‘YA’. No need to add any additional parameters. It will initialize the bus if you will give any extra parameters.
class pyb.UART(bus, ...)
PyBoard
There are 6 UART interfaces present in the PyBoard.
Pyboard’s physical pins on the UART buses are:
- UART(4) is on XA: (TX, RX) = (X1, X2) = (PA0, PA1)
- UART(1) is on XB: (TX, RX) = (X9, X10) = (PB6, PB7)
- UART(6) is on YA: (TX, RX) = (Y1, Y2) = (PC6, PC7)
- UART(3) is on YB: (TX, RX) = (Y9, Y10) = (PB10, PB11)
- UART(2) is on: (TX, RX) = (X3, X4) = (PA2, PA3)
The UART pins supported by Pyboard lite are:
- UART(1) is on XB: (TX, RX) = (X9, X10) = (PB6, PB7)
- UART(6) is on YA: (TX, RX) = (Y1, Y2) = (PC6, PC7)
- UART(2) is on: (TX, RX) = (X1, X2) = (PA2, PA3)
ESP32
There are 3 UART interfaces present in the ESP32.
ESP32’s physical pins on the UART buses are:
- UART(0) is on: (TX, RX) = (38,39,40,41)
- UART(2) is on: (TX, RX) = (25,27)
Raspberry Pi Pico
There are 2 UART interfaces present in the Raspberry Pi Pico.
Raspberry Pico’s physical pins on the UART buses are:
- UART(0) is on: (TX, RX) = (16,17,21,22)
- UART(1) is on: (TX, RX) = (6,7,11,12)
ESP8266
There are 2 UART interfaces present in the ESP8266.
Initializing The UART Bus
Initialize the UART bus with the given parameters:
- Baudrate is the clock speed (baud rate).
- bits is the number of bits per character, 7, 8 or 9.
- parity is the parity, None, 0 (even) or 1 (odd).
- stop is the number of stop bits, 1 or 2.
- flow sets the flow control type. Can be 0, UART.RTS, UART.CTS or UART.RTS | UART.CTS.
- timeout is the timeout in milliseconds to wait for writing/reading the first character.
- timeout_char is the timeout in milliseconds to wait between characters while writing or reading.
- read_buf_len is the character length of the read buffer (0 to disable).
UART.init(baudrate, bits=8, parity=None, stop=1, *, timeout=0, flow=0, timeout_char=0, read_buf_len=64)
NOTE: if parity=None, only 8 and 9 bits are supported. And with parity enabled, only 7 and 8 bits are supported.
Exception
It will raise an exception if the baud rate is not set to less than 5% of the value that it needs. The frequency of the bus on which the UART is present decides the minimum baud rate. The default bus frequency gives a minimum bus frequency of 1300 for UART(1) and 650 for UART(6). To reduce the frequency the bus frequency to lower the baud rate used:
pyb.freq([sysclk[, hclk[, pclk1[, pclk2]]]])
Arguments correspond to:
- sysclk: frequency of the CPU
- hclk: frequency of the AHB bus, core memory and DMA
- pclk1: frequency of the APB1 bus
- pclk2: frequency of the APB2 bus
Turning Off The UART Bus
To turn off the UART bus, use the following function:
UART.deinit()
UART.any()
This function returns the number of bytes waiting (usually 0).
Reading The Characters
You can use the following function to read characters.
UART.read([nbytes])
If the nbytes is specified, use that many bytes. The nbytes are returned immediately if available in the buffer, else return when the sufficient characters arrive, or timeout elapses. If the nbytes is given, the function reads all the available data it can and returns after the time elapses.
NOTE: For a 9-bit character, the nbytes should be even since each character takes 2 bytes and the number of characters is nbytes/2.
RETURN VALUE: A byte object containing byte read in. will return none on timeout.
UART.readchar()
This function returns a single character on the bus.
RETURN VALUE: The character read as an integer returns -1 on timeout.
UART.readinto(buf[, nbytes])
The above function read bytes into the buf. If the values of nbytes are specified, then use that value. If not, then use len(buf) bytes.
RETURN VALUE: Returns a number of bytes read and stored into buf or None on timeout.
UART.readline()
This function reads a line, and if the line consists of a newline character at the end, the return is immediate. And if the timeout elapses, the entire data is returned regardless of the existence of a newline.
RETURN VALUE: Returns line read. If no data is available and the time elapses, it returns None.
Writing The Characters
UART.write(buf)
Write the buffer of bytes to the bus. Each byte is considered to be one character if the characters are 7 or 8 bits wide. For 9 bits wide character, two bytes are for each character (little-endian). In such a case, the buf must have an even number of bytes.
RETURN VALUE: Returns the number of bytes written. It also returns None if the time elapses and no data is available.
UART.writechar(char)
Write a single character on the bus. char is an integer to write.
RETURN VALUE: None
UART.sendbreak()
It sends a condition on the bus. The duration is updated to 13 bits which drives the bus slow.
RETURN VALUE: None
Constants
Let’s take a look at the constants that we generally use to select the flow control type.
UART.RTS (Request To Send)
UART.CTS (Clear To Send)
Sample Program
Now, let’s take a look at a sample program.
from machine import UART,Pin
import utime
# Initialize a UART objects
uart = UART(2, baudrate=115200, rx=13,tx=12,timeout=10)
count = 1
while True:
print('\n\n===============CNT {}==============='.format(count))
# Send a message
print('Send: {}'.format('hello {}\n'.format(count)))
print('Send Byte :') # Number of bytes sent
uart.write('hello {}\n'.format(count))
# Wait 1s bell
utime.sleep_ms(1000)
if uart.any():
# If the data type byte read return data for the row of data
# E.g. b'hello 1 \ n '
bin_data = uart.readline()
# Hand to the information printed on the terminal
print('Echo Byte: {}'.format(bin_data))
# Converted to a string of data bytes default byte UTF-8 encoding
print('Echo String: {}'.format(bin_data.decode()))
# Counter +1
count += 1
print('---------------------------------------')
Sample OutPut
===============CNT 1===============
Send: hello 1
Send Byte :
8
Echo Byte: b’hello 1\n’
Echo String: hello 1
—————————————
===============CNT 2===============
Send: hello 2
Send Byte :
8
Echo Byte: b’hello 2\n’
Echo String: hello 2
—————————————
===============CNT 3===============
Send: hello 3
Send Byte :
8
Echo Byte: b’hello 3\n’
Echo String: hello 3
—————————————
UART VS I2C VS SPI

Specifications | UART | I2C | SPI |
Abbreviation | Universal Asynchronous Receiver/Transmitter | Inter-Integrated Circuit | Serial Peripheral Interface |
Interface Diagram | ![]() | ![]() | ![]() |
Pin Designations | TxD: Transmit Data RxD: Receive Data | SDA: Serial Data SCL: Serial Clock | SCLK: Serial Clock MOSI: Master Output, Slave Input MISO: Master Input, Slave Output SS: Slave Select |
Data Rate | Since this involves asynchronous communication, the data rate between two communicating devices should be set to the same value. The maximum data rate supported ranges from 230 Kbps to 460 Kbps. | I2C offers data rates of 100 kbps, 400 kbps, and 3.4 Mbps. Some versions support 10 Kbps and 1 Mbps as well. | Typically supports 10 Mbps to 20 Mbps. |
Range | Nearly 50 feets | Higher | Highest |
Communication Type | Asynchronous | Synchronous | Synchronous |
Number of Masters | Not Applicable | One or more than One | One |
Clock | There is no usage of the Common Clock signal. Both devices will operate on their own clocks. | There is a common clock signal between numerous masters and slaves. | Amongst master and slave devices, there is a single serial clock signal. |
Hardware complexity | Lesser | More | Less |
Protocol | One start bit and one stop bit are utilized for every 8 bits of data. | It employs an ACK bit for every 8 bits of data to indicate whether or not data has been received. | Every company or manufacturer has its own set of protocols for communicating with peripherals. As a result, in order to establish an SPI connection, it is necessary to understand the read/write protocol. |
Software Addressing | Addressing is not required because this is a one-to-one connection between two devices. | There will be numerous slaves and masters, and all masters will be able to speak with all slaves. | Slave select lines are used to address any slave that is linked to the master. |
Wrapping It Up
This was all about what UART actually is and how you can interface UART communication using Micropython on multiple tools like Pyboard, Raspberry Pico, ESP32, and many more. Go through all the code and attributes carefully and try to implement them. In case of any doubts or queries, don’t forget to mention it in the comments.