Chapters

21 Serial Bus Interface Contracts

electronics-controller-design
i2c
spi
uart

21.1 Start Simple

Write the Rules for One Sensor Link

Picture a light sensor and a display sharing a small controller board. Both work alone, yet the display freezes when the sensor speaks. The fix begins by naming the electrical, timing, ownership, and failure rules for the shared link.

A microcontroller means the small computer on the board. GPIO means general purpose input and output pins. Inter-integrated circuit (I2C) means a short shared wired link between chips. Serial Peripheral Interface (SPI) means a clocked wired link with separate data paths. Universal asynchronous receiver-transmitter (UART) means a serial link that agrees on timing without a shared clock.

Read the sensor while the display updates. Then hold a line, change the clock, repeat a byte, disconnect one part, and restart the controller. Set a time limit and one recovery owner. A stuck part must not block the whole product forever.

This runway does not choose pull-up values, cable length, or a bus speed. The deeper sections turn each link into a contract for voltage, timing, framing, recovery, and release evidence.

Imagine adding one sensor module to a microcontroller and needing the reading to arrive every time. The bus choice is a promise about wires, voltage levels, addresses or chip selects, timing, and recovery when the device misbehaves. Start with distance, speed, device count, and the failure mode you can debug in the field.

21.2 Learning Objectives

After this page, you should be able to:

  • Select I2C, SPI, or UART from pin count, speed, addressing, and wiring constraints.
  • Explain why I2C uses open-drain outputs and pull-up resistors on shared wires.
  • Calculate UART byte timing from baud rate and frame format.
  • Diagnose I2C rise-time limits from bus capacitance, pull-up strength, and sink-current limits.
  • Explain how SPI chip-selects and CPOL/CPHA modes affect multi-device links.

21.3 From Electricity to Electronics

From Electricity to Electronics maps the electronics building blocks behind IoT hardware: semiconductors, diodes, transistors, GPIO limits, drivers, and protection components. This page turns those blocks into the bus-level contract that connects sensors and peripherals to the microcontroller.

Use it when a board must choose between I2C, SPI, and UART, a sensor bus fails after adding cable length or more devices, SPI returns garbage because of mode mismatch, or a UART link drops frames because the baud and framing assumptions are not identical.

The mathematical gist. An open-drain I2C HIGH rises through (t_r\approx0.847RC). Reserving 10% of a clock period for that rise gives (R=k/(0.847fC)): on the chapter’s illustrative 100 pF bus the limit falls from about 11.8 kohms at 100 kHz to 2.95 kohms at 400 kHz. Higher clock rate therefore demands a stronger pull-up or less capacitance, and the stronger resistor also raises LOW-state sink current.

Math Bridge · guided foundationsWhy does 400 kHz I2C need a stronger pull-up?Let Eddie connect clock period, RC rise time, bus capacitance, pull-up resistance, and sink current.

21.4 Overview: How the Electronics Reach the Microcontroller

The building blocks in this module — open-drain outputs, pull-ups, push-pull drivers, logic levels — come together in the three serial buses that connect almost every IoT sensor and peripheral to its microcontroller. Each bus's electrical style follows directly from those blocks.

Inspect this evidence before proceeding: choosing a serial bus begins with topology because topology fixes both the pin budget and the likely electrical failure modes. Compare the three connection patterns in Figure 21.1 before treating speed or software-library availability as the deciding factor.

UART uses point-to-point TX/RX, I2C shares an addressed SDA/SCL bus and SPI shares clock/data with a separate chip select per slave. The panels compare wires and duplex modes.
Figure 21.1: UART, I2C, and SPI compared by their physical connection topology.

Trace the three branches in Figure 21.1 by their labelled conductors. UART Point-to-Point crosses TX and RX between Device A and Device B with no clock. I2C Multi-Drop Bus instead shares SDA and SCL from one Master to addressed slaves such as 0x48 and 0x50. SPI Multi-Point Star shares MOSI, MISO, and SCK but adds CS1, CS2, and CS3 to select individual slaves. Those differences lead directly into the chapter’s contract: record baud and frame for UART, pull-ups and addresses for I2C, or clock mode and chip-select timing for SPI.

Think of the bus as a contract between the board schematic, the firmware driver, and every device on the connector. The schematic promises which pins exist, whether a line is shared, where pull-up resistors live, and how much capacitance the trace and cable add. The firmware promises the bus speed, address, chip-select timing, SPI mode, UART frame, retry behavior, and timeout policy. The peripheral promises the electrical levels it can tolerate and the protocol timing it will obey. A design is reliable only when all three promises match; a sensor can be listed as "I2C compatible" and still fail if the board used weak pull-ups across a long harness or if the driver never handles clock stretching.

The practical decision is therefore not "which bus is modern?" but "which failure mode can this product afford?" I2C saves pins and keeps multi-sensor boards tidy, but shared open-drain wires make every device and every centimeter of copper part of the same RC edge. SPI gives deterministic high-speed transfers to displays, radios, flash, or ADCs, but each added peripheral consumes routing and chip-select discipline. UART is simple and debuggable with a USB adapter or logic analyzer, yet it is best treated as point-to-point unless another layer adds addressing, framing, and collision control. Pick the bus whose electrical contract matches the product's distance, speed, device count, and service access.

I2C

Two shared wires (SDA, SCL), open-drain with pull-ups, devices selected by a 7-bit address.

SPI

Four wires (SCLK, MOSI, MISO, CS), push-pull, full-duplex, fast, one chip-select per device.

UART

Two wires, no shared clock; both ends agree a baud rate and a frame format.

The trade

Pins vs speed vs number of devices — each bus optimizes a different corner.

21.4.1 Overview Knowledge Check

21.5 Practitioner: The Numbers That Define Each Bus

BusWiresSelectionKey parameters
I2C2 (SDA, SCL), shared7-bit address (2^7 = 128, ~112 usable after reserved)Standard 100 kHz / Fast 400 kHz; a slow device holds SCL low to clock-stretch
SPI4 (SCLK, MOSI, MISO, CS)One chip-select line per deviceFull-duplex, tens of MHz; master and slave must match one of 4 modes (CPOL/CPHA)
UART2 (TX, RX), point-to-pointNone (one-to-one link)Agreed baud rate and frame, e.g., 9600 8N1

Worked example — UART framing at 9600 8N1. "8N1" means 8 data bits, no parity, 1 stop bit, wrapped by 1 start bit — 10 bit-times per byte. At 9600 baud each bit lasts 1 / 9600 = 104 µs, so one byte takes about 10 × 104 µs = 1.04 ms, giving roughly 960 bytes/s. Because there is no clock wire, both ends must be set to the same baud and frame or the receiver samples the bits at the wrong instants.

I2C's 2^7 address space is why you can hang a dozen sensors on two wires, while SPI trades that for speed by giving every device its own chip-select line.

Use a quick sizing pass before committing the schematic. For I2C, count every device input pin, connector stub, cable, and ESD part as bus capacitance; then choose pull-ups that meet rise-time at the selected clock while keeping LOW-level sink current inside the weakest device rating. If two sensors have the same fixed address, the design needs a different variant, a bus switch, a second controller bus, or SPI/UART instead of hoping firmware can separate them. For SPI, count one chip-select per peripheral plus any reset or data-ready lines, then verify the controller and peripheral sample on the same CPOL/CPHA edge before board bring-up. For UART, write the exact frame in the interface record: baud, data bits, parity, stop bits, voltage level, connector pinout, and whether the link is idle-high TTL, RS-232, RS-485, or another physical layer.

Bring-up evidence should match the contract. An I2C scan proves addresses acknowledge, but it does not prove the rise time is legal under worst-case temperature and cable length. A successful SPI register read proves chip-select and mode for one command, but it does not prove long bursts, shared MISO tri-state behavior, or maximum clock rate. A UART terminal prompt proves baud alignment, but it does not prove binary framing, flow control, or recovery after a reset. Capture one short logic-analyzer trace for the selected bus early; it turns abstract settings into visible edges, addresses, chip-select windows, and start/stop bits that other engineers can review.

The table distinguishes I2C by wire count and addressing, but a logic trace is needed to recognize its framing. Use Figure 21.2 to see the one SDA transition rule that separates bus-control events from ordinary address and data bits.

I2C START and STOP condition waveform diagram: SDA falls while SCL is high to mark START, and SDA rises while SCL is high to mark STOP, with address and data bits in between.
Figure 21.2: I2C START and STOP conditions distinguished from address and data transitions.

At S — START condition in Figure 21.2, SDA falls while SCL remains high, marking the bus busy. Across the middle address and data bits, SDA changes only while SCL is low so the receiver can sample a stable value during the high phase. At P — STOP condition, SDA rises while SCL is high and releases the bus. This framing rule gives the bring-up trace a precise diagnostic meaning: an unexpected SDA edge during high SCL is a control event or integrity fault, not another payload bit.

Once START and STOP are recognizable, inspect Figure 21.3 to account for every clock in one transaction. This is the evidence needed to distinguish “an address appeared in a scan” from a correctly framed byte exchange with valid acknowledgement behaviour.

SDA and SCL waveforms for one I2C byte transfer: seven address bits, a read/write bit, an acknowledge bit, eight data bits, a second acknowledge bit, and start and stop conditions.
Figure 21.3: Bit-level timing of an I2C address byte, data byte, and their acknowledgement slots.

After the START condition in Figure 21.3, SDA carries A6 through A0 and then the R/W bit; the first ACK occupies the ninth SCL pulse. The next eight clocks carry D7 through D0, followed by a second ACK, before the STOP condition releases the bus. A high value in either ACK slot is a NACK, so the trace reveals whether the addressed device accepted the request and the data phase—not merely whether seven address bits were present. Rise time, voltage levels, and any SCL clock stretching remain separate measurements in the interface record.

Worked example — two real display-driver ICs on these buses. A MAX7219 LED matrix driver is wired with just four signals — DIN, CS, CLK, and a DOUT pass-through — and is loaded with 16-bit words (an address byte selecting a register such as digit, intensity, or scan-limit, plus a data byte) shifted in on the clock while chip-select is held low. Chaining a second LED matrix is as simple as feeding the first chip's DOUT into the next chip's DIN: each 16-bit word ripples down the chain, so writing to the third matrix means shifting three words through the first two chips first. A shutdown-mode register blanks the whole chain instantly for power saving, and an intensity register sets brightness without touching pixel data. This is the same push-pull, master-paced shifting this chapter's SPI row describes, even though the datasheet names the pins DIN/CLK/CS instead of MOSI/SCLK/CS.

An IS31FL3731 LED matrix driver takes the opposite bus contract: it answers a 7-bit I2C address, so it is wired with just SDA and SCL alongside power and ground, and it holds eight full frames of pixel RAM on-chip so a microcontroller can animate through them with a single command instead of re-sending every pixel each frame. Choosing between these two real parts is the same choice this chapter's overview makes in the abstract: MAX7219's clocked shift-register style trades pins for simple, fast, deterministic chaining, while the I2C driver trades a little speed for sharing just two wires and buffered, addressed frame memory.

21.5.1 Practitioner Knowledge Check

21.6 Under the Hood: Each Bus’s Electrical Catch

The buses inherit both the strengths and the limits of their output style. I2C is open-drain, so every rising edge is driven only by the passive pull-up charging the bus capacitance (an RC curve). Add many devices or long traces and the total capacitance grows, the rising edge slows, and you must lower the clock or use a stronger pull-up (within each device's sink limit) to keep 400 kHz reliable. Clock stretching — a slave holding SCL low — lets a busy device pause the master rather than lose data.

SPI is push-pull, so it drives both edges hard and reaches far higher clock rates, but it pays in pins (a chip-select per device) and demands that master and slave agree on the clock polarity and phase (CPOL/CPHA); pick the wrong mode and every bit is sampled on the wrong edge, returning garbage. UART has no clock line at all, so its Achilles heel is the baud rate: if the two ends differ by more than a few percent, the accumulated timing error walks the sampling point off the bits and the frame decodes as noise.

The deeper reason these failures look different is that the receiver decides truth at a specific voltage and instant. On I2C, a controller samples SDA while SCL is HIGH, so a slow SDA rise can be read as LOW even though the firmware "released" the line. Lower-value pull-ups sharpen that edge, but the trade is higher sink current whenever any device pulls LOW; the smallest peripheral on the bus sets the safe limit. On SPI, edges are actively driven, so capacitance is less often the first limit on a short board, but ringing, skew, and chip-select setup/hold time matter as clocks climb. A MISO line that is not high impedance when its device is deselected can corrupt every other peripheral, even if each part works alone on the bench.

UART hides a different timing budget inside the frame. The receiver detects the falling start bit, waits toward the middle of each bit cell, and then samples data without seeing another clock edge. Any baud mismatch, oscillator drift, interrupt latency, or long cable distortion accumulates across the byte until the stop bit is sampled too early or too late. That is why a forgiving console link may work at 9600 baud but fail at 921600 baud, and why field products often use explicit packet framing, checksums, and reset recovery around UART streams. The bus choice is therefore a system reliability choice: each protocol moves the risk to a different place in the electrical and firmware stack.

21.6.1 Under-the-Hood Knowledge Check