Microcontroller Serial Interfaces: UART, SPI & I²C

UART, SPI, and I²C move framed state over pins under different clock, ownership, addressing, and electrical contracts. Reliable firmware follows each transfer from clock generation and pin routing through shift state, flags, buffering, errors, and acknowledgement.

UART, SPI, and I²C are all called serial interfaces because bits cross pins over time. That is where the similarity ends. They differ in clock ownership, topology, electrical signaling, framing, addressing, arbitration, and what hardware considers a completed transfer.

UARTno clock wire · point-to-point · start/data/parity/stop receiver reconstructs sample time from agreed baud and the start edge SPIcontroller clock · chip select · simultaneous shift in both directions SCK CS I²Copen-drain shared bus · address · ACK/NACK · arbitration SCL SDA Pins and electrical behavior are part of the protocol, not transport below it.
The protocol engine, pin route, electrical layer, and software buffer lifecycle must all agree before bytes can move reliably.

Contract comparison #

Property UART/USART asynchronous mode SPI I²C/SMBus-style two-wire
Clock on wire No; endpoints agree on nominal baud Controller drives clock Controller drives clock; participants may stretch on supported systems
Typical wires TX, RX, optional flow control SCK, controller-out, controller-in, chip select(s) SDA and SCL, both open drain with pull-ups
Framing Start bit, data bits, optional parity, stop bit(s) Chip select and configured clock edges START, address, direction, bytes, ACK/NACK, STOP/repeated START
Topology Usually point-to-point One controller with selected peripherals; variants exist Addressed multi-drop bus, possibly multi-controller
Principal hazards Baud mismatch, framing/overrun Mode mismatch, chip-select timing, full-duplex discard Pull-up/rise time, arbitration, stuck-low bus, address/ACK state

UART: recover time from edges #

An asynchronous receiver detects a start transition, then samples data according to its baud clock. Error comes from both endpoints' clock error and from sampling quantization. A link can pass at room temperature and fail across oscillator tolerance, voltage, or temperature.

Receiving a byte involves at least two stages: the shift register acquires the frame, then a holding register or FIFO makes it available to software. If a new frame completes before the old one is consumed, overrun occurs. Reading data without first preserving the associated error status can lose framing, parity, or overrun evidence on devices with ordered read semantics.

Transmitter “data register empty” means software may queue another byte. “Transmit complete” means the final stop bit left the shifter. They are different events; half-duplex driver enable must normally follow the latter.

SPI: every clock shifts both directions #

SPI has no universal packet format. Clock polarity and phase determine which edge changes data and which edge samples it. Bit order, word width, chip-select polarity, maximum clock, setup/hold time, and inter-word gaps come from the selected peripheral.

Each transmitted bit simultaneously receives a bit. Even a write-only command fills the receive path, and even a read requires the controller to transmit dummy bits to generate clock. Firmware must drain or intentionally discard received state to avoid stale flags and overruns.

A chip-select line is often part of the peripheral's state machine: it can delimit a command, reset bit count, latch a result, or require setup/hold time around SCK. Toggling it between bytes may change the meaning of the transfer.

I²C: open-drain shared ownership #

I²C participants pull SDA/SCL low and release them for high. The pull-up and bus capacitance determine rise time. A controller that releases high must sample the actual line because another device may hold it low for arbitration or clock stretching.

A transaction is a state sequence:

  1. bus idle with both lines released high;
  2. START while SCL is high;
  3. address and direction bit;
  4. receiver ACK/NACK;
  5. data bytes, each followed by ACK/NACK;
  6. repeated START for a direction or target change, or STOP to release the bus.

The ACK after an address reports that a participant recognized the address and was able to respond; it does not prove the higher-level command was valid. The ACK after a data byte likewise has device-specific meaning.

Rate calculations are necessary but insufficient #

UART baud divisors and SPI/I²C prescalers are integer approximations derived from a peripheral clock. Calculate the actual rate and error, not only the requested rate. Then check pin rise/fall time, receiver tolerance, cable/trace loading, and target limits.

For I²C, lowering the nominal clock does not repair a pull-up that cannot meet required rise time or sink-current limits. For SPI, a clock below the maximum can still fail setup/hold at the board due to routing, level shifting, or wrong sampling edge.

Interrupt and buffer ownership #

The safe transmit lifecycle is typically:

software queue → holding register → shift engine → wire-complete state.

The receive lifecycle is the reverse. Interrupts should transfer ownership between adjacent stages, record errors, and apply a defined overflow policy. A ring buffer needs single-writer rules for head and tail and a deliberate full condition; volatile alone does not create those rules.

Bring-up method #

  1. Verify logic levels, grounds, pull-ups, and any transceiver direction.
  2. Confirm peripheral clock and calculate the realized rate.
  3. Confirm package pins and mux route.
  4. Configure frame/mode before enabling the engine.
  5. Clear stale status and drain stale receive data as documented.
  6. Exercise one polled transfer while observing registers and pins.
  7. Decode the waveform with the exact mode and check every acknowledgement/error.
  8. Add interrupt buffering and timeout/recovery paths only after the single transaction is proven.

On a failure, capture both the physical transaction and the peripheral's raw status. Either alone leaves half the contract invisible.

Sources

  1. docsATmega8A Data Sheetpdfnotes
  2. docsATmega128A Data Sheetpdfnotes
  3. docsEFM8BB51 Reference Manualpdfnotes

Searches titles, aliases, headings, summaries, and full text.↑↓ move ↵ open esc close