Raspberry Pi: Python Libraries for I2C, SPI, UART

The Raspberry Pi is one of the most popular single board computers for hobbyists. Its 40 Pins support the protocols I2C, SPI, and UART. If you followed the series, you are familiar with those protocols. But how can you use them with your Raspberry Pi?

This article shows you how to work with these protocols using Python. For each protocol, I will list the libraries, and show you a simple example so you can start easily.

This article originally appeared at my blog.

I2C

You have several choices when working with I2C devices. The ubiquitous and universal library luma offers a generic serial object which supports I2C connection. Usage is simple: Import the library, define the IC2 registers, then start to send and read data. Here is an example how to make an I2C connection

from  luma.core.interface.serial.i2c import i2c

i2c_connection = i2c(port=0, address=0x3c)

i2c_connection.data('Hello')

However, the library is intended to create connection objects that are used to instantiate concrete screens, so it might not be suitable if you use other I2C based sensors.

Another option is the Python smbus2 library. It supports the i2C protocol and several low-level read and write access methods. It accesses its host built-in smbus kernel module, from which it can get an I2C instance.

from smbus2 import SMBus, i2c_msg

i2c = SMBus(1)

msg = i2c_msg.write(80, 'Hello'.encode())
bus.i2c_rdwr(msg)

SPI

The first step in connecting an SPI device is to figure out to which SPI bus you connect a device. On the Raspberry Pi, different SPI bus systems exist: 3 SPI bus systems for Raspberry Pi up to version 3, and 7 SPI bus systems for the Raspberry Pi 4. Take a note of the concrete bus, because it will map to a device file in your system which you need to use for configuration.

The SPI protocol is supported by spidev, an actively maintained library. It makes it easy to connect any attached SPI device, configure this connection with the required specifications (speed in HZ, clock polarity and more), and then reading and writing data to it. Assuming you want to connect an SPI device on bus 5, the following example connects and writes data to the device.

from spidev import SpiDev

spi = SpiDev()
spi.open(5,1)

spi.max_speed_hz = 4000

msg = [0x01, 0x02, 0x03]
spi.xfer(msg)

answer = spi.readbytes(100)
print(answer)

spi.close()

Another option is the Adafruit_Blinka library, an universal library that wraps the CircuitPython API for devices running MicroPython or CPython. Since the Raspberry Pi works with CPython, you can use this library and its many functions. It provides abstractions to interface the SPI specific GPIO pins of the Pi, and an SPI class to create connections. However, you need to install this library following additional configuration, and then use the SPI bus 1. Once done, you can work with SPI devices as shown:

import time
import busio

spi = busio.SPI(board.SCK_1, MOSI=board.MOSI_1, MISO=board.MISO_1)

spi.configure(baudrate=400000)

spi.write(b'\x01')
spi.write(b'\x02')
spi.write(b'\x03')

time.sleep(0.120)

answer = spi.read(f)
print(answer)

spi.deinit()

UART

For making UART connections, several options exists. The Python library pyserial provides a simple, configurable object for making serial connection. You can configure the device file, the baud rate, and other communication aspects like the bitesize, parity, timeout and more. It implements Pythons context manager protocols and therefore automatically closes the connection at the end of its program block. Here is an example:

from serial import Serial


with Serial('/dev/ttyUSB0', 9600) as serial:
  serial.send('Hello to Arduino')
  answer = serial.readline()
  print(answer)

The second option is again Adafruit_Blinka library, specifically the busio UART API. Very similar to PySerial, you can create a connection with the default configuration, and then start to read and write data to it. However, it does not work with device files, but directly with pins.

from busio import UART

serial = UART(14,15,baudrate=9600)

serial.write('Hello to Arduino')
answer = serial.readline()
print(answer)

serial.deinit()

Conclusion

On the Raspberry Pi, several options for working with the protocols I2C, SPI and UART exists. This article is a not-exhaustive list of these libraries. On the one hand, there are very specific libraries, such as pyserial for UART, or spidev for I2C. On the other hand, universal libraries such as luma and busio support two or all three protocols. Considering this, I recommend to choose a universal library especially if you intend to connect to a sensor that these libraries already support.

13