Conslabs
Embedded Firmware Basics
intermediateMarch 3, 2025 · 3 min read

Communication Protocols: UART, SPI & I2C

How UART, SPI, and I2C move bytes between chips, the timing and framing rules of each, and the trade-offs that decide which one fits a given peripheral.

GPIO gets you one bit at a time. Most real peripherals — sensors, displays, memory chips, other microcontrollers — talk over one of three standard serial protocols.

UART: asynchronous, no shared clock

UART (Universal Asynchronous Receiver/Transmitter) sends data without a shared clock signal. Instead, both sides agree on a baud rate in advance, and each byte is wrapped in a fixed frame:

[start bit] [data bits, typically 8] [optional parity bit] [stop bit(s)]

The receiver synchronizes to the falling edge of the start bit, then samples the middle of each subsequent bit based on its own clock. If the two sides' baud rates differ by more than a couple of percent, accumulated timing error across the frame causes bit errors — which is why UART baud rates must match closely, even though there's no shared clock line.

SPI: synchronous, full-duplex

SPI (Serial Peripheral Interface) uses a shared clock (SCK) driven by a single master, plus separate data lines for each direction (MOSI, master-out-slave-in, and MISO, master-in-slave-out) — so data can flow both ways simultaneously. A dedicated chip-select (CS) line per device lets one master talk to multiple slaves on the same bus.

Two settings must match between master and slave:

  • CPOL (clock polarity): whether the clock idles high or low.
  • CPHA (clock phase): whether data is sampled on the leading or trailing clock edge.

Together these define the four standard "SPI modes" — getting them wrong is one of the most common reasons a new SPI peripheral simply doesn't respond.

I2C: synchronous, two-wire, multi-master

I2C (Inter-Integrated Circuit) uses just two lines — SDA (data) and SCL (clock) — shared by every device on the bus, both open-drain with external pull-up resistors. Each device has a 7-bit (or 10-bit) address, and the master initiates communication by sending that address followed by a read/write bit; the addressed slave responds with an ACK bit.

Because every device shares the same two wires, I2C scales to many peripherals with very little wiring — at the cost of lower speed than SPI and more protocol overhead per transaction.

Choosing a protocol

ProtocolWiresSpeedMulti-deviceTypical use
UART2 (TX/RX)Low–moderatePoint-to-pointDebug consoles, GPS modules, simple links
SPI4+ (1 CS per device)HighYes, via extra CS linesFlash memory, displays, high-speed sensors
I2C2 (shared)Low–moderateYes, via addressingLow-speed sensors, EEPROMs, many devices on one bus

Why this matters in practice

A datasheet rarely explains why a part uses the protocol it does — but the trade-offs above usually explain the choice: a camera module needing high throughput uses SPI; a temperature sensor that only needs to be polled occasionally uses I2C to save pins; a bootloader talking to a host PC uses UART because it needs no shared clock at all.

The next sub-lesson, Analog I/O (ADC/DAC), covers the fourth major way firmware talks to the outside world: continuous analog signals rather than digital bytes.