I2C, SPI, and UART

Almost every sensor, display, memory chip, GPS module, and radio talks to a microcontroller over one of three serial interfaces. UART sends bytes asynchronously over two wires with no shared clock. I²C shares two wires among many devices, each with an address. SPI uses a clock, two data lines, and a chip-select line per device to move data quickly. Each has its own failure modes, and most "the sensor doesn't work" problems are really interface problems: wrong address, missing pull-ups, mismatched baud rate, wrong SPI mode, or incompatible voltage levels.

Understanding how each bus frames data, who drives the clock, and what the electrical requirements are makes these problems quick to diagnose — especially with a logic analyzer to see what is actually on the wires.

TL;DR

Quick Example

Talking to devices from Linux (for example a Raspberry Pi) with standard userspace interfaces:

Core Concepts

UART

Common failures: wrong baud rate (garbage characters), TX and RX not crossed, inverted levels between TTL and RS-232, missing ground, and clock inaccuracy at high baud rates.

I²C

Pull-up values trade speed against power: too weak (large resistance) gives slow rising edges that fail at higher speeds or with long wires; too strong wastes current. Address confusion is common: datasheets list either the 7-bit address (0x76) or the 8-bit form including the read/write bit (0xEC/0xED). A device that resets mid-transaction can hold SDA low and lock the bus; recover by toggling SCL up to nine times and issuing a stop condition.

SPI

Common failures: wrong mode (data shifted by one bit or garbage), chip select not asserted or released too early, clock too fast for the wiring, and forgetting that reads require sending dummy bytes to clock data in.

Choosing an Interface

Electrical and Wiring Considerations

Software Considerations

Use DMA for large SPI and UART transfers to free the CPU, and interrupts rather than busy-waiting for UART reception, with ring buffers sized for bursts. Add timeouts to every bus operation so a stuck device cannot hang the firmware, and retry or reset the bus on errors. See Interrupts, Timers, and Peripheral Drivers. On Linux, use the kernel's i2c-dev, spidev, and serial (tty) interfaces or write kernel drivers for production devices.

Troubleshooting Method

  1. Power and ground: is the device powered at the right voltage, and is ground shared?
  2. Wiring: TX/RX crossed? Correct pins for SDA/SCL and SPI lines? Chip select connected?
  3. Configuration: baud rate and framing, I²C address (7-bit), SPI mode and clock speed.
  4. Known-good read: read a chip ID or version register before anything else.
  5. Look at the wires: a logic analyzer with I²C, SPI, and UART decoders shows addresses, ACKs, and data; an oscilloscope shows edges and levels. See Hardware Debugging with JTAG and Logic Analyzers.

Best Practices

Read a Known Register First

Verify communication with a chip ID or version register before implementing full drivers.

Put Timeouts on Every Transaction

Never let firmware wait forever for a bus; detect failures and recover.

Match Electrical Requirements

Check voltage levels, pull-ups, and wiring length against datasheets and bus specifications.

Keep a Logic Analyzer Nearby

Seeing actual traffic answers most interface questions in minutes.

Common Mistakes

Using the 8-Bit I2C Address

Missing Pull-Ups

Wrong SPI Mode

FAQ

What is the difference between I2C and SPI?

I²C uses two shared lines with addresses and acknowledgements, suited to many slower devices; SPI uses separate chip-select lines and a faster, full-duplex clocked transfer, suited to high-throughput devices.

Why does I2C need pull-up resistors?

Because I²C lines are open-drain: devices can only pull them low, so pull-up resistors are needed to return the lines to the high state.

What does 8N1 mean for UART?

Eight data bits, no parity bit, and one stop bit per character — the most common UART framing.

Can multiple devices share a UART?

Not with plain UART, which is point-to-point. RS-485 allows multiple devices on one differential bus with a protocol (such as Modbus RTU) to manage who talks.

Why do I get garbage characters on a serial console?

Usually a baud rate or framing mismatch, inverted logic levels, a missing ground connection, or noise on long cables.

Related Topics

References