Every embedded project needs to talk to something. A sensor, a display, a radio module, another microcontroller. And almost every time, the conversation happens over one of three protocols: I2C, SPI, or UART.
They've been around for decades. They're not glamorous. But picking the wrong one will haunt you through the entire development cycle. Here's an honest comparison from someone who's used all three in production hardware — including the trade-offs that datasheets don't emphasize.
The 30-Second Summary
| I2C | SPI | UART | |
|---|---|---|---|
| Wires | 2 (SDA, SCL) | 4+ (MOSI, MISO, SCK, CS) | 2 (TX, RX) |
| Topology | Bus (multiple devices) | Point-to-point with chip selects | Point-to-point |
| Speed | Up to 3.4 MHz | Tens of MHz | Up to ~5 Mbps typical |
| Duplex | Half | Full | Full |
| Complexity | Moderate | Low | Very low |
| Best for | Sensors, low-pin-count designs | Displays, flash memory, high-speed | Debug ports, GPS, modems |
Now let's go deeper on each one.
UART: The Old Reliable
UART (Universal Asynchronous Receiver-Transmitter) is the simplest of the three. Two wires: TX and RX. No clock signal. Both sides agree on a baud rate beforehand, and data flows.
Where UART Shines
- Debug consoles and boot logs. Every development board has a UART port. It's the first thing that works.
- GPS modules. Most GPS chips output NMEA sentences over UART at 9600 or 4800 baud.
- Bluetooth and Wi-Fi modules. The HC-05, ESP8266 (AT mode), and many cellular modems use UART.
- Simple MCU-to-MCU communication. Two microcontrollers talking to each other? UART is often the easiest path.
The Limitations
- No shared clock. Both devices must agree on the baud rate. If the clock frequencies drift apart (which happens with internal RC oscillators), you get framing errors.
- Point-to-point only. One TX to one RX. No bus topology. If you need to talk to multiple devices, you need multiple UART peripherals (or a creative solution with tri-state buffers).
- Relatively slow. 115200 baud is the most common "fast" speed, which gives you roughly 11.5 KB/s actual throughput. Some modern UARTs go to 3–5 Mbps, but at that point, you're probably better off with SPI.
Practical Tips for UART
- Always use hardware flow control (RTS/CTS) at speeds above 115200 baud. Without it, you'll drop bytes when the receiver's buffer fills.
- Use DMA for reception on your MCU. Interrupt-per-byte UART handling at 921600 baud will eat your CPU alive.
- Match baud rates carefully. At 115200 baud, a 2% clock mismatch is the maximum tolerance. Use crystals or precision oscillators for both sides, not internal RC oscillators.
- Add a protocol on top. Raw UART has no framing, no addressing, no error detection. For anything beyond debug output, use a lightweight framing protocol (HDLC, SLIP, or a simple length + CRC scheme).
SPI: The Speed Demon
SPI (Serial Peripheral Interface) uses four wires: MOSI (master out, slave in), MISO (master in, slave out), SCK (clock), and CS (chip select). It's synchronous — the master provides the clock, so there's no baud rate agreement needed.
Where SPI Shines
- Displays. TFT LCDs, OLED panels, e-ink displays — SPI is the dominant interface.
- Flash memory. SPI NOR flash (W25Q series, etc.) is everywhere. SPI NAND is growing too.
- High-speed ADCs and DACs. When you need to move samples fast, SPI's full-duplex operation and high clock rates deliver.
- SD cards. SPI mode is the universal fallback for SD card access.
The Speed Advantage
SPI doesn't have a defined maximum speed in a specification document (unlike I2C). The practical limit is set by:
- Master's maximum clock output (many MCUs can generate 20–50 MHz SPI clocks)
- Slave's maximum clock input (check the datasheet — a typical SPI flash supports 80–133 MHz)
- Signal integrity (at 20+ MHz, PCB layout matters — short traces, no stubs)
Real-world throughput: A 24 MHz SPI clock gives you 3 MB/s. A 50 MHz clock gives 6.25 MB/s. That's orders of magnitude faster than UART and significantly faster than I2C.
The Pin Count Problem
SPI's biggest disadvantage is pin count. Every additional slave device needs its own chip select line. With 4 SPI devices, you're using 7 pins (4 data + 3 shared + 4 CS). With I2C, the same 4 devices use just 2 pins.
On a pin-constrained MCU, this matters. A QFP-48 microcontroller might only have 30 available GPIOs. Four SPI devices with dedicated CS lines eats 13% of your I/O budget.
Workarounds:
- Daisy-chain SPI devices (some shift registers and ADCs support this)
- Use an I/O expander to generate CS lines (ironic — often an I2C expander)
- Use a 3-to-8 decoder (like 74HC138) to generate up to 8 CS lines from 3 GPIO pins
Practical Tips for SPI
- Use DMA. Always. SPI transfers at 20 MHz will overwhelm any CPU trying to handle them byte-by-byte.
- Keep CS asserted for the entire transaction. Don't release CS between bytes in a multi-byte transfer — many SPI devices reset their state machine when CS goes high.
- Check CPOL and CPHA settings. SPI has four clock polarity/phase modes (0–3). Both master and slave must match. This is the #1 source of "SPI doesn't work" issues.
- Route SPI signals carefully at speeds above 10 MHz. Keep traces short, add ground planes, and match lengths if you're pushing above 20 MHz.
I2C: The Pin Saver
I2C (Inter-Integrated Circuit) uses two wires: SDA (data) and SCL (clock). It supports multiple devices on the same bus, each with a unique address. The master provides the clock, but slaves can stretch it (hold SCL low) to slow things down.
Where I2C Shines
- Sensors. Temperature, pressure, humidity, IMUs, light sensors — the vast majority use I2C.
- EEPROMs. Small configuration memories (24C02, 24C08, etc.) are almost always I2C.
- Pin-constrained designs. Two pins for unlimited devices (within capacitance limits).
- Battery-powered designs. I2C devices typically have lower standby current than SPI equivalents.
Speed Tiers
| Mode | Max Speed | Max Bus Capacitance | Typical Use |
|---|---|---|---|
| Standard | 100 kHz | 400 pF | Slow sensors, EEPROM |
| Fast | 400 kHz | 400 pF | Most sensors |
| Fast Mode Plus | 1 MHz | 550 pF | High-throughput sensors |
| High Speed | 3.4 MHz | 100 pF | Specialized (rare in practice) |
In practice, 400 kHz is the sweet spot. Most MCUs and sensors support it, and it gives you enough throughput for typical sensor polling.
The Pull-Up Resistor Requirement
I2C is open-drain — neither the master nor the slaves drive the bus high. Pull-up resistors do that. This is the #1 source of I2C problems, and we covered it in detail in our I2C bus design mistakes article.
Quick version: calculate your pull-up values based on bus capacitance and desired speed. Don't just use 4.7 kΩ because a reference design did.
The Address Conflict Problem
Every I2C device has a 7-bit address (10-bit addressing exists but is rarely used). The problem: many popular sensors only support one or two address options. If you need two identical temperature sensors on the same bus, you might be stuck.
Solutions:
- Use an I2C multiplexer (PCA9548A gives you 8 separate bus segments)
- Pick sensor variants with different default addresses
- Use separate I2C peripherals on your MCU
Head-to-Head: Real Scenarios
Scenario 1: Reading a Temperature Sensor Every Second
Winner: I2C. One sensor, low data rate, minimal pins. I2C is the obvious choice. Use 100 kHz, add the right pull-ups, done.
Scenario 2: Driving a 320×240 TFT Display
Winner: SPI. A 320×240 display at 16-bit color needs 153.6 KB per frame. At 30 FPS, that's 4.6 MB/s. I2C can't touch that. UART would struggle. SPI at 40 MHz handles it easily.
Scenario 3: Adding Wi-Fi to a Battery-Powered Sensor
Winner: UART or SPI, depending on the module. An ESP32-C3 can use either. UART is simpler (AT command mode), but SPI gives you higher throughput for large data transfers. If you're using the Wi-Fi module's built-in TCP stack, UART with AT commands is usually sufficient.
Scenario 4: Connecting 6 Different Sensors to a Microcontroller
Winner: I2C. Six sensors on two pins. No contest. SPI would need 9 pins minimum (3 shared + 6 CS lines). The only caveat: make sure none of the sensors share an I2C address.
Scenario 5: Logging Data to an SD Card
Winner: SPI. SD cards support SPI mode, and the write speeds you need (hundreds of KB/s minimum for useful logging) require SPI's bandwidth. Some SD cards also support SDIO (4-bit parallel), which is even faster but requires an SDIO peripheral on your MCU.
What About Newer Protocols?
You might be wondering about I3C, CAN, 1-Wire, USB, or PCIe. These all have their uses, but for most embedded designs, I2C/SPI/UART cover 90% of your communication needs.
- I3C (MIPI) is the proposed successor to I2C, offering higher speeds and lower power. Adoption is growing but still limited. Most sensors are still I2C.
- CAN is for automotive and industrial networks — not a general-purpose MCU peripheral interface.
- 1-Wire (Dallas/Maxim) is used for temperature sensors (DS18B20) and iButtons. It works, but it's slow and the software stack is more complex than I2C.
Making the Call
Here's my decision flow:
- Is it a debug/console port? → UART
- Do you have limited pins and low-speed devices? → I2C
- Do you need speed (displays, flash, high-speed ADC)? → SPI
- Are you connecting to a module with a fixed interface? → Use whatever the module requires
- Multiple sensors, one bus? → I2C (watch for address conflicts)
- High-throughput data transfer? → SPI (or parallel, if available)
The best protocol is the one that matches your constraints — pin count, speed, board space, and what your devices actually support.
Working with I2C? The I2C Pull-Up Calculator helps you nail the resistor values for any bus configuration — standard mode through high-speed mode.