Friday, September 22, 2023
Google search engine
HomeIoTHow To Interface UART Communication Using MicroPython

How To Interface UART Communication Using MicroPython

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.       # read 10 characters, returns a bytes object         # 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, ...)



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)



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)


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.RTSUART.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.


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:


This function returns the number of bytes waiting (usually 0).

Reading The Characters

You can use the following function to read characters.[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.


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.


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


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. 


Write a single character on the bus. char is an integer to write. 



It sends a condition on the bus. The duration is updated to 13 bits which drives the bus slow. 



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
    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

Sample OutPut

===============CNT 1===============

Send: hello 1

Send Byte :


Echo Byte: b’hello 1\n’

Echo String: hello 1


===============CNT 2===============

Send: hello 2

Send Byte :


Echo Byte: b’hello 2\n’

Echo String: hello 2


===============CNT 3===============

Send: hello 3

Send Byte :


Echo Byte: b’hello 3\n’

Echo String: hello 3



AbbreviationUniversal Asynchronous Receiver/TransmitterInter-Integrated CircuitSerial Peripheral Interface
Interface DiagramUART
Pin DesignationsTxD: 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 RateSince 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.
RangeNearly 50 feetsHigherHighest
Communication TypeAsynchronousSynchronousSynchronous
Number of MastersNot ApplicableOne or more than OneOne
ClockThere 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 complexityLesserMoreLess
ProtocolOne 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 AddressingAddressing 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.



Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments