← LOGBOOK LOG-354
EXPLORING · ELECTRONICS ·
UARTI2CSPICANUSB1-WIREPROTOCOLSSERIALEMBEDDEDSTM32

Communication Protocols — UART, I2C, SPI, and Beyond

A structured look at the embedded communication protocols that come up constantly: UART, I2C, SPI, 1-Wire, CAN, and USB. How each works electrically and logically, and when to reach for which.

Why Today

Nucleo is still in transit. The HAL entry covered how STM32 code wraps peripherals — but HAL calls like HAL_I2C_Master_Receive() and HAL_UART_Transmit() abstract away what’s actually happening on the wire. Before touching real hardware, worth understanding the protocols themselves: the electrical signals, the handshake sequences, the failure modes.

The i2c-vs-spi-oled entry covered the practical tradeoffs for a specific component. This is the broader picture.


The Taxonomy

Embedded protocols split on a few axes:

  • Synchronous vs asynchronous — does a shared clock line coordinate sender and receiver, or does each side agree on timing in advance?
  • Single-master vs multi-master — who controls the bus?
  • Simplex / half-duplex / full-duplex — one direction only, one direction at a time, or both directions simultaneously?
  • Point-to-point vs bus — one device pair, or multiple devices sharing wires?

UART — Universal Asynchronous Receiver/Transmitter

UART is the oldest and simplest. Two wires: TX (transmit) and RX (receive). No clock line — both sides agree on baud rate in advance. Asynchronous, full-duplex, point-to-point.

Frame format:

[Start bit][D0][D1][D2][D3][D4][D5][D6][D7][Parity?][Stop bit(s)]

The start bit is always a low pulse that tells the receiver “a byte is coming.” Data bits follow at the agreed baud rate. Stop bit returns the line high. The receiver samples each bit at the midpoint of its expected window — which is why baud rate must match on both ends. A mismatch of even a few percent accumulates across 10 bits and causes framing errors.

Common baud rates: 9600, 115200, 921600. 115200 is standard for debug serial.

What UART doesn’t have: no addressing, no acknowledgement, no built-in error correction beyond optional parity. It’s a raw byte pipe. If the receiver isn’t listening, the byte is lost silently.

RS-232 vs TTL UART: Logic-level UART (TTL) uses 0V/3.3V or 0V/5V. RS-232 uses ±12V with inverted logic — a leftover from teletype hardware. The CH340 and CP2102 USB-serial chips convert between USB and TTL UART. An RS-232 transceiver (MAX232) converts between TTL and RS-232.

On STM32: USART (Universal Synchronous/Asynchronous Receiver/Transmitter) — same as UART but with an optional clock line for synchronous mode. The Nucleo-G071RB’s USART2 is wired to the ST-LINK virtual COM port, which is how printf over USB serial works without extra hardware.


I2C — Inter-Integrated Circuit

Two wires: SCL (clock) and SDA (data). Synchronous, half-duplex, multi-device bus. One master drives the clock; up to 127 devices share the same two wires, each with a unique 7-bit address.

Bus transaction sequence:

  1. Master pulls SDA low while SCL is high → START condition
  2. Master clocks out 7-bit device address + R/W bit (8 bits total)
  3. Addressed slave pulls SDA low → ACK
  4. Data bytes transferred, each followed by an ACK
  5. Master releases SDA while SCL is high → STOP condition

The open-drain trick: SDA and SCL are open-drain lines — devices can only pull them low, never drive them high. Pull-up resistors (typically 4.7kΩ–10kΩ) return the lines to high. This is what makes the bus sharable: multiple devices can coexist without fighting over voltage levels.

Speed grades:

  • Standard mode: 100 kHz
  • Fast mode: 400 kHz
  • Fast-mode Plus: 1 MHz
  • High-speed mode: 3.4 MHz (rarely used in practice)

Repeated START: The master can issue a repeated START (Sr) to change direction (write→read) without releasing the bus — no other master can grab it mid-transaction. This is how register reads work: write the register address, Sr, then read the value.

ACK vs NACK: A missing ACK (NACK) means the slave didn’t respond — wrong address, not ready, or bus fault. HAL returns HAL_ERROR if it times out waiting for ACK.

Common failure modes: wrong pull-up value (too high → signal rise too slow at speed; too low → excessive current), address collision between two devices sharing a fixed address, bus lockup if SDA is held low by a crashed device (power cycle or clock-stretching workaround needed).


SPI — Serial Peripheral Interface

Four wires: SCLK (clock), MOSI (master out slave in), MISO (master in slave out), CS/SS (chip select, one per slave). Synchronous, full-duplex, point-to-point with multiple devices via multiple CS pins.

Transaction: Master asserts CS low, then simultaneously clocks data out on MOSI and reads data in on MISO. Every clock cycle exchanges one bit in each direction — it’s always full-duplex at the bit level, even if the application is logically one-directional.

Clock polarity and phase (CPOL/CPHA):

SPI has four modes depending on clock idle state and sample edge:

ModeCPOLCPHAClock idleSample on
000LowRising
101LowFalling
210HighFalling
311HighRising

Mode 0 is the most common. Getting this wrong produces garbage data — the datasheet will specify which mode the device expects.

No addressing: CS pins do the selection. Three SPI devices need three CS lines. This is a pin budget concern on small microcontrollers, but it also means no address conflicts.

Speed: SPI is fast. SD cards run at 25 MHz. Flash memories at 50–100 MHz. The SSD1306 OLED is fine at 8 MHz. I2C’s 400 kHz ceiling is nowhere near SPI territory.

Daisy-chaining: Some SPI devices support daisy-chain mode — MISO of device 1 connects to MOSI of device 2, one CS for all. Data shifts through the chain. Rare outside shift registers and LED drivers.


1-Wire

One signal wire (plus ground). Asynchronous, half-duplex. Each device has a unique 64-bit ROM code burned at factory, so multiple devices can share a single wire — the master addresses them by ROM code.

Most famous application: DS18B20 temperature sensor. You can put many of them on one GPIO pin and read each one individually.

How it works: Timing is everything. A logical 0 is a long pull-low (~60µs); a logical 1 is a short pull-low (~6µs) followed by release. The master initiates, the slave responds by pulling the line low for ACK. Reset pulse is a long low (~480µs); slave presence pulse is a medium low (~60µs).

Parasitic power: The DS18B20 can run parasitically — it charges a capacitor from the data line and doesn’t need a separate VCC wire. Two-wire operation (signal + GND) instead of three.

Limitation: It’s slow (~16 kbps) and timing-sensitive enough that it’s usually bit-banged rather than handled by a hardware peripheral. Fine for temperature sensors that update once per second. Not suitable for streaming data.


CAN — Controller Area Network

Originally designed for automotive wiring harnesses. Differential two-wire bus (CAN-H and CAN-L), multi-master, with hardware arbitration and built-in error detection. Up to 1 Mbit/s on classic CAN; CAN FD extends this to 8 Mbit/s for the data phase.

Why differential: CAN-H and CAN-L carry opposite signals. Interference hits both wires equally and cancels out at the receiver — this is why CAN works in electrically noisy environments like engine bays.

Arbitration without collision: Every node can transmit simultaneously. Each bit, the transmitter reads back the bus. A node transmitting a recessive bit (1) but seeing a dominant bit (0) knows it lost arbitration and backs off. The highest-priority message (lowest CAN ID numerically) always wins. No collision, no retry delay — deterministic latency.

Frame structure: CAN frames carry 0–8 bytes of data (classic CAN) with a CAN ID (11-bit standard or 29-bit extended). No point-to-point addressing — every node on the bus sees every frame. Nodes filter by ID in hardware.

Error handling: CAN has five error types and a hardware error counter. A node with persistent errors goes into error-passive then bus-off state, protecting the rest of the bus.

Where it shows up: Automotive ECUs, industrial automation (CANopen, DeviceNet), robotics (ODrive motor controllers use CAN). The STM32G0 series has a FDCAN peripheral — relevant once the Nucleo arrives.


USB

USB is a layered protocol built on a differential serial bus (D+ and D−), with a structured hierarchy: host, hubs, devices. Speed tiers: Low Speed (1.5 Mbps), Full Speed (12 Mbps), High Speed (480 Mbps), SuperSpeed (5 Gbps, USB 3.x).

Why it’s different from all the above: USB is a complete system. It has enumeration (devices announce their class and capabilities), endpoint-based communication (each endpoint is a unidirectional pipe), transfer types (control, bulk, interrupt, isochronous), and a class system that lets drivers handle devices generically (HID for keyboards/mice, CDC for serial ports, MSC for storage).

CDC-ACM: The virtual COM port class. When an STM32 enumerates as a USB CDC device, the host sees a serial port — no special driver needed on modern OSes. This is how the Nucleo’s ST-LINK exposes USART2 to the PC as /dev/ttyACM0 or COM3.

USB on microcontrollers: Full Speed (12 Mbps) is the most common on embedded chips — it requires a 48 MHz clock derived from a PLL (or external crystal) with exact frequency. The STM32G071 doesn’t have USB hardware, but the ST-LINK on the Nucleo board handles USB-to-UART bridging transparently.


Quick Comparison

ProtocolWiresSpeedTopologyDirectionAddressing
UART2 (TX, RX)Up to ~3 MbpsPoint-to-pointFull-duplexNone
I2C2 (SCL, SDA)Up to 3.4 MHzMulti-device busHalf-duplex7-bit address
SPI4 (+ 1 CS/device)10s of MHzPoint-to-pointFull-duplexCS pin
1-Wire1~16 kbpsMulti-device busHalf-duplex64-bit ROM code
CAN2 (differential)1–8 MbpsMulti-master busHalf-duplexMessage ID
USB FS2 (differential)12 MbpsHost-device treeHalf-duplexEndpoint

What This Means for the Nucleo Sessions

First sessions will be UART-heavy: printf over USART2 is the primary debugging tool. I2C comes next — the MPU-6050 is on I2C at 0x68. SPI once a faster peripheral shows up.

The HAL API names now make more sense in context:

  • HAL_UART_Transmit() → clocks bytes out with start/stop framing at agreed baud rate
  • HAL_I2C_Master_Receive() → generates START, sends address + R bit, reads ACK, clocks in bytes, generates STOP
  • HAL_SPI_TransmitReceive() → asserts CS, simultaneously clocks MOSI out and MISO in, releases CS

Next

Board should arrive in the next day or two. First real session: USART2 printf, then LED blink, then button input. Protocols stop being abstract once there’s a logic analyzer or oscilloscope on the wires.