I²C vs SPI vs UART: Which Protocol Should Your Project Use?

Sensors, displays and modules talk to your microcontroller over UART, I²C or SPI. Learn how each one works, how many wires it needs, how fast it is, and how to debug the most common problems — with examples from real ESP32 projects.
Almost every module you connect to an Arduino or ESP32, whether it's a GPS receiver, an OLED display, an IMU or an SD card, uses one of three serial protocols: UART, I²C or SPI. You don't need to implement them yourself, because libraries do that. But understanding them is what lets you wire modules correctly, share pins, and debug when the display stays black.
The short answer
| UART | I²C | SPI | |
|---|---|---|---|
| Wires (besides GND) | 2 (TX, RX) | 2 (SDA, SCL) | 4 (SCK, MOSI, MISO, CS) + 1 CS per extra device |
| Clock | None (agreed baud rate) | Shared, from the controller | Shared, from the controller |
| Devices on the bus | 2 (point-to-point) | Many, selected by address | Many, selected by CS pin |
| Typical speed | 9600 – 921600 baud | 100 kHz / 400 kHz / 1 MHz | 1 – tens of MHz |
| Typical uses | GPS, Bluetooth modules, debug console | Sensors, small OLEDs, RTCs, EEPROMs | SD cards, TFT displays, fast ADCs, radios |
UART: two devices, no clock
UART (Universal Asynchronous Receiver-Transmitter) is the "Serial" in Serial.begin(115200). Each side has a TX (transmit) and an RX (receive) pin, and they're crossed: TX of one goes to RX of the other.
There's no clock wire. Both sides agree in advance on a baud rate, and each one times the bits using its own clock. A common frame format is 8N1: one start bit, 8 data bits, no parity, one stop bit. That's 10 bit-times per byte, so 115 200 baud carries at most about 11.5 kilobytes per second.
Common problems:
- Garbage characters: the baud rates don't match. Check both ends.
- Nothing at all: TX and RX not crossed (TX→TX), or no shared ground.
- Voltage levels: an "RS-232" port on old equipment uses roughly ±12 V and inverted logic, which will damage a microcontroller pin. "TTL serial" on modules uses 3.3 V or 5 V logic. 5 V TX into a 3.3 V ESP32 RX needs level shifting. A voltage divider works for this one-way signal.
Classic Bluetooth modules such as the HC-05 are UART devices too: whatever you send over Bluetooth appears as serial bytes on their TX pin.
I²C: many devices, two wires, addresses
I²C (Inter-Integrated Circuit) uses two shared lines: SDA (data) and SCL (clock). One controller (your microcontroller) talks to many target devices on the same two wires. Each device has a 7-bit address, and the controller starts every transaction by sending the address of the device it wants.
Two details make I²C different:
1. Open-drain lines need pull-up resistors. Devices can only pull SDA and SCL low. They never drive them high. Resistors to VCC pull the lines back up. Without pull-ups, nothing works. Typical values are 2.2 kΩ to 10 kΩ. Lower values give faster edges, which you need for 400 kHz or for long wires with more capacitance. Most breakout boards include pull-ups, and several boards on one bus put their pull-ups in parallel, which lowers the total resistance. TI's application note SLVA689 shows how to calculate the limits.
2. Address conflicts. Two identical sensors usually have the same address. Many parts have an address-select pin (for example, the SSD1306 OLED is commonly 0x3C, and 0x3D with a jumper changed). If not, you need an I²C multiplexer.
Real example: our ESP32 Flappy Bird game drives a 128×64 SSD1306 OLED over I²C with SDA on GPIO 21 and SCL on GPIO 22, the ESP32's default I²C pins, at address 0x3C:
Wire.begin(21, 22); // SDA, SCL
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
for (;;) delay(100); // display not found: halt
}
The first debugging step for I²C is always an I²C scanner sketch. It tries every address and prints which ones respond. If your device doesn't show up, check wiring, power and pull-ups before touching your code.
Standard-mode I²C runs at 100 kHz, Fast-mode at 400 kHz, and Fast-mode Plus at 1 MHz. For small sensors that's plenty. For a full-screen display refresh it can be the bottleneck.
SPI: fast, simple, more wires
SPI (Serial Peripheral Interface) uses four signals:
- SCK: clock from the controller
- MOSI: controller → device data
- MISO: device → controller data
- CS (chip select, also called SS): one per device, active-low
Newer documentation uses the names COPI/CIPO or SDO/SDI instead of MOSI/MISO. They mean the same signals.
The controller pulls one device's CS low, clocks data out on MOSI and simultaneously in on MISO, then releases CS. There are no addresses. The CS pin is the address. Because the lines are actively driven, not pulled up, SPI can run much faster than I²C: several MHz is routine, and tens of MHz is possible on short traces.
Common problems:
- Wrong SPI mode: devices differ in clock polarity and phase (modes 0–3). The datasheet says which one, and the library usually handles it.
- A device that doesn't release MISO: some poorly behaved modules (notoriously some SD card modules) keep driving MISO even when not selected, which breaks other devices on the bus.
- Floating CS pins: an unselected device whose CS floats may respond randomly. Pull every CS high at startup.
How to choose
- Talking to a PC, GPS or Bluetooth/Wi-Fi module? UART. It's often the only option.
- Several slow sensors with few pins to spare? I²C. Two wires for everything.
- Lots of data, fast: a colour display, SD card or high-speed ADC? SPI.
- Long cables (more than a metre)? None of these three are designed for it. Look at RS-485 (UART over differential pairs) or CAN.
Many sensors, such as the MPU-6050 and BMP280, support more than one option. In those cases pick by your pin budget and speed needs.
Try it
UART, SPI and I²C are compared with animated timing diagrams on Day 5 of the ElectroLab course.
References
- NXP, UM10204 — I²C-bus specification and user manual: nxp.com
- Texas Instruments, SLVA689 — I²C Bus Pullup Resistor Calculation: ti.com
- SparkFun, Serial Communication: learn.sparkfun.com
- SparkFun, Serial Peripheral Interface (SPI): learn.sparkfun.com
- Solomon Systech, SSD1306 Datasheet: cdn-shop.adafruit.com