Serial protocols#
Serial Protocols#
Many of the reTerminal’s General Purpose Input and Output (GPIO) pins also have specialized functions. These specialized functions typically include specific digital communication protocols:
Serial
SPI (serial peripheral interface)
I2C (inter-integrated circuit)
PWM (pulse-width modulation)
PCM (pulse-code modulation)
Below is the pin out diagram illustrating the specialized pins:

GPIO and pin diagram of the reTerminal - reTerminal Official Wiki, Seeed.
Serial Communication#
In order for two devices to exchange information, they must share a common communication protocol.
Serial interfaces stream their data, one bit at a time. These interfaces can operate with as little as one wire (for unidirectional communication), however they typically use 2 to 4 wires.
Serial communication can be either: synchronous and asynchronous.
Synchronous Serial#
A synchronous serial interface always pairs its data line(s) with a clock signal. Therefore, all devices on the same data bus share a common clock.

Serial interface unidirectionally transmitting one bit at every clock pulse Serial Communication, Sparkfun.
How many wires are used in the image above? Can information flow in both directions?
Having a line dedicated to a clock makes for faster serial transfer, however, it also requires an extra wire between communicating devices.
Below are examples of synchronous digital protocols:
SPI
I2C
USB (uses clock-synchronization)
Asynchronous Serial#
Asynchronous means that data is transferred without support from an external clock signal.
This transmission method minimizes wires and I/O pins, however, extra effort is put into reliably transferring and receiving data.
Asynchronous serial communication is typically intended for only two devices to communicate

Wiring diagram for two devices communicating with the Serial protocol Serial Communication, Sparkfun.
This is the most common type of serial communication between devices.
The term “Serial” is commonly used to refer to Asynchronous Serial
In order to communicate reliably, both devices have to adhere to a number of rules such as: Data bits, Synchronization bits, Parity bits, and Baud rate.
For example, when using the serial monitor of the Arduino IDE, it’s necessary to properly select the Baud Rate so that both the client device (the Arduino) and the host (your PC) know the exactly clock frequency of the serial communication.

Selecting the baud rate of the Arduino IDE's serial monitor.
UARTs#
A universal asynchronous receiver/transmitter (UART) is a block of circuitry responsible for implementing serial communication.
A UART converts multiple parallel digital lines into two serial lines: Transmission (Tx) and Receiving (Rx) lines.
The Raspberry Pi has a built-in UART on GPIO14 (Tx) and GPIO15 (Rx).
The python library pySerial can be used to send and receive serial data to and from the Raspberry Pi:
import serial
ser = serial.Serial('/dev/ttyS0') # open serial port (9600 default baud rate)
print(ser.name) # check which port was really used
ser.write(b'hello') # write a string
ser.close() # close port
Serial Demo#
The demo below will show a reTerminal device devices communicating with a Raspberry Pi Pico over the asynchronous serial protocol.
The Rx port of the reTerminal is connected to the Tx port of the Pico and vice-versa.
Below is the code being run on the Pico using MicroPython:
from time import sleep
from machine import UART, Pin
uart0 = UART(0, baudrate=9600, tx=Pin(0), rx=Pin(1))
uart0.write('hello\n')
sleep(0.01)
line = uart0.read()
print(f'My line: {line}')
And this is the code running inside the reTerminal:
import serial
with serial.Serial('/dev/serial0', 9600) as ser:
while True:
line = ser.readline() # read a '\n' terminated line
print(f'Line: {line}')
if line == b'hello\n':
break
ser.write(b'there\n')
Once both scripts are run simultaneously, the signal observed would look the following:

Serial Peripheral Interface (SPI)#
Serial Peripheral Interface (SPI) is commonly used to send data between microcontrollers and small peripherals (ei. shift registers, sensors, SD cards).
It uses separate clock and data lines, along with a select line to choose the device you wish to talk to.
The communication can happen between a controller device (controlling the terms of the communication) and one or multiple peripheral devices.
The following nomenclature is typically used:
SCK: Clock Signal. Generated by the controller device.
COPI: Controller-Out Peripheral-In. Information flows from controller to peripheral device.
CIPO: Controller-In Peripheral-Out. Information flows from peripheral to controller.
CS: Chip Select. Every peripheral device has a unique connection to the controller. The controller uses this line to enable (wake-up) the peripheral when it wants to communicate by setting it low.

Wiring diagram for two devices communicating over SPI Serial Peripheral Interface (SPI), Sparkfun.
Each peripheral device connected to the controller will need a separate CS line. To talk to a particular peripheral, the controller makes that peripheral’s CS line low and keep the rest of them high.

Multiple peripheral devices with unique CS lines talking to the same controller Serial Peripheral Interface (SPI), Sparkfun.
Problematic Technical Terminology#
Historically, the relationship between the Controller and Peripheral devices used to be called Master and Slave. This is incredibly problematic since the Master-Slave analogy is based on an extreme and violent power relationship between two individuals.
The Open Source Hardware Association (OSHA) has passed a resolution asking hardware manufactures to redefine SPI signal names. However, some legacy documentation and references still include the outdated terminology below.
Deprecated signal names:
MOSI – Master Out Slave In
MISO – Master In Slave Out
SS – Slave Select
MOMI – Master Out Master In
SOSI – Slave Out Slave In
Problematic technical terminology is not unique to the SPI protocol and exists in many other technical domains. In 2022, Wire magazine published a nuanced article presenting multiple sides of the terminology debate: Tech Confronts Its Use of the Labels ‘Master’ and ‘Slave’.
I2C#
The Inter-Integrated Circuit (I2C) Protocol is a protocol intended to allow multiple “peripheral” digital devices (chips) to communicate with one or more “controller” chip.
I2C requires only two wires, however, those two wires can support up to 1008 peripheral devices.
Hardware required to implement I2C is more complex than SPI, but less than asynchronous serial. Data speeds are also faster than asynchronous serial but slower than SPI.
![]()
Example wiring diagram for one controller and 3 peripheral devices I2C, Wikipedia.
In I2C, each device has a unique identifier address (a hexadecimal number). When communication is initiated, the controller must announce the address of the target device.
You can check what are the I2C addresses of the peripherals attached to the reTerminal with the following bash command:
pi@raspberrypi:~ $ i2cdetect -y 1
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- UU -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- UU -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- UU -- -- -- -- -- -- --
40: -- -- -- -- -- UU -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
The output above shows peripheral devices with the hexadecimal addresses 0x19, 0x29, 0x38, 0x40.
When communicating, messages are broken up into two types of frame:
An address frame, where the controller indicates the peripheral to which the message is being sent.
One or more data frames, which are 8-bit data messages passed from controller to peripheral or vice versa.

Clock and data lines for I2C, showing address and data frames I2C, Sparkfun.
Examples of I2C devices used in this course:
LCD driver of the reTerminal
reTerminal’s accelerometer.
reTerminal’s light sensor
I2C Python Library#
The python library smbus2 supports I2C protocol.
from smbus2 import SMBus
# Open i2c bus 1
with SMBus(1) as bus:
# read one byte from address 80, offset 0
b = bus.read_byte_data(80, 0)
print(b)
# Write a byte to address 80, offset 0
data = 45
bus.write_byte_data(80, 0, data)
Each peripheral device might require a series of initialization messages to be written to it. Typically you will use python libraries made to communicate with a specific peripheral device.
As an example, see the library for the AHT20 temperature sensor.
Pulse Width Modulation (PWM)#
Pulse Width Modulation (PWM) is a type of digital signal. With PWM it’s possible to vary how much time the signal is high in an analog fashion.
While the signal can only be high (usually 3.3V or 5V) or low (ground) at any time, we can change the proportion of time the signal is high compared to when it is low over a consistent time interval.
The duty cycle describes the amount of “ON time” as a percentage over an interval or period of time.

Examples of duty cycles as a percentage of the total "High" signal Pulse Width Modulation, Wikipedia.
Signal with a constant amplitude (voltage) but duty cycle changing from 10% to
100%.
MakerPortal.com
Common PWM Applications#
Servo Motors#
For servo motor positioning the width of the pulse indicates the position where the servo arm should be.
Servo arm position changing according to the duty cycle of a PWM signal.
14Core.com
LED Dimming#
LEDs are make to work with constant voltage (approximately 2 volts). It is not possible to dim their brightness by lowering the voltage (like in incandescent light bulb).
However, with PWM, it is possible to change the amount of “ON time” to give the illusion that the LED is dimmer.
Signal duty cycle affecting LED brightness.
Pyroelectro.com
In reality, the LED is simply blinking so fast that the human eye cannot notice it.
PWM Python Library#
The python library GPIO Zero offers good support more a number of PWM devices:
# LED intensity modulated with PWM
from gpiozero import PWMLED
from time import sleep
led = PWMLED(17)
while True:
led.value = 0 # off
sleep(1)
led.value = 0.5 # half brightness
sleep(1)
led.value = 1 # full brightness
sleep(1)
# Servo position set with PWM
from gpiozero import Servo
from time import sleep
servo = Servo(17)
while True:
servo.min()
sleep(2)
servo.mid()
sleep(2)
servo.max()
sleep(2)
References#
Serial Communication Tutorial, Sparkfun.
Serial Peripheral Interface (SPI), Sparkfun
Pulse Width Modulation, Sparkfun
Servo Control, Wikipedia
Raspberry Pi: Python Libraries for I2C, SPI, UART by Sebastian via medium.com
Raspberry Pi UART Communication using Python and C by ElectronicWings.com
Diving Deeper#
If you want to know more about how USB uses clock synchronization even thought there is no clock wire: