Serial, I2C, and SPI Compared: Which Communication Protocol to Use

Every microcontroller project reaches the same fork: how do you connect a sensor, display, memory chip, or second MCU? UART (Serial), I2C, and SPI are the three workhorse protocols for chip-to-chip communication. Each trades wiring simplicity, speed, and scalability differently. Pick wrong, and you fight bus contention, slow refresh rates, or a pin count your MCU cannot support.

Quick Takeaways

  • UART needs 2 wires (TX, RX), has no clock, and suits point-to-point links such as GPS modules, Bluetooth modules, and debug consoles.
  • I2C needs 2 wires (SDA, SCL) and supports up to 127 addressable devices on one bus. It suits slow-to-medium sensors and small displays.
  • SPI needs 4 wires plus one CS line per device. It is the fastest of the three (often 10-80 MHz), so use it for displays, SD cards, and flash memory.
  • Choose by constraint: pin count favors I2C, throughput favors SPI, and simple two-device links favor UART.
Feature UART I2C SPI
Wires 2 (TX, RX) 2 (SDA, SCL) 4+ (MOSI, MISO, SCK, CS)
Clock Asynchronous Synchronous (shared) Synchronous (shared)
Typical speed 9600-115200 baud (up to ~3 Mbps) 100 kHz / 400 kHz / 1 MHz 1-80+ MHz
Topology Point-to-point Multi-master, multi-slave Single master, multi-slave
Addressing None 7-bit or 10-bit Hardware CS pin
Duplex Full Half Full
Typical range Meters (RS-232/RS-485 for more) Centimeters to ~1 m Centimeters
Complexity Low Medium Low-Medium

How Serial Communication Works

Digital devices exchange bits in one of two ways. Parallel sends many bits at once over many wires. Serial sends bits one after another over few wires. All three protocols here are serial. They differ in how sender and receiver agree on bit timing.

Two categories matter:

  • Asynchronous: No shared clock. Both sides agree on a baud rate in advance (UART).
  • Synchronous: A clock line shares timing between devices (I2C, SPI).

UART (Universal Asynchronous Receiver-Transmitter)

How It Works

UART links exactly two devices. Cross the lines: one device’s TX connects to the other’s RX, and a GND wire is shared. Idle state is logic high. Each frame contains:

  1. One start bit (logic low)
  2. 5-9 data bits (usually 8, LSB first)
  3. Optional parity bit
  4. 1 or 2 stop bits (logic high)

The common setting is 8N1: 8 data bits, no parity, 1 stop bit. Both devices must match baud rate within about 2-3%, or you get framing errors and garbage characters.

Strengths and Limits

  • No clock wire and simple hardware.
  • Full duplex: TX and RX operate simultaneously.
  • Only two devices per port without extra hardware.
  • Frame overhead (start/stop bits) wastes about 20% of bandwidth in 8N1.
  • Clock drift between two independent oscillators limits reliable baud rate.

Voltage Levels Matter

Logic-level UART runs at 3.3V or 5V. RS-232 uses ±3V to ±15V and is not directly compatible with MCU pins. A level shifter such as the MAX3232 is required. For long industrial runs, RS-485 (differential, up to ~1200 m) is the standard upgrade.

UART Code Example (Arduino C++)

// Read GPS NMEA sentences from a module on a second UART (ESP32 example)
#define GPS_RX 16   // ESP32 GPIO16 connects to GPS TX
#define GPS_TX 17   // ESP32 GPIO17 connects to GPS RX

void setup() {
  Serial.begin(115200);                          // USB debug console
  Serial2.begin(9600, SERIAL_8N1, GPS_RX, GPS_TX); // GPS default: 9600 baud, 8N1
}

void loop() {
  while (Serial2.available()) {                  // Bytes waiting in RX buffer?
    char c = Serial2.read();                     // Read one byte
    Serial.write(c);                             // Forward to the PC terminal
  }
}

I2C (Inter-Integrated Circuit)

How It Works

Philips (now NXP) created I2C for short on-board links. Two open-drain lines carry everything:

  • SDA: Serial data (bidirectional)
  • SCL: Serial clock (driven by the master)

Both lines need pull-up resistors to VCC because devices only pull lines low. Typical values: 4.7 kΩ at 100 kHz and 2.2 kΩ to 4.7 kΩ at 400 kHz on 3.3V or 5V systems.

A transaction follows this sequence:

  1. Master sends a START condition (SDA falls while SCL is high).
  2. Master sends a 7-bit address plus a R/W bit.
  3. The addressed slave pulls SDA low for an ACK.
  4. Data bytes move in the chosen direction, each followed by an ACK/NACK.
  5. Master sends a STOP condition (SDA rises while SCL is high).

Speed Modes

Mode Max Speed
Standard 100 kbit/s
Fast 400 kbit/s
Fast-mode Plus 1 Mbit/s
High-speed 3.4 Mbit/s

Strengths and Limits

  • Two wires serve many devices, which saves GPIO pins.
  • Built-in addressing and acknowledgment.
  • Multi-master capable, with arbitration.
  • Slower than SPI, and half duplex.
  • Bus capacitance is limited to 400 pF, which caps cable length and device count.
  • Address conflicts happen when two identical modules share a fixed address (for example, two MPU6050 boards at 0x68).

Scanning the Bus (Arduino C++)

#include <Wire.h>

void setup() {
  Serial.begin(115200);
  Wire.begin();               // Join bus as master; ESP32 defaults SDA=GPIO21, SCL=GPIO22
  Wire.setClock(400000);      // Fast mode: 400 kHz
}

void loop() {
  Serial.println("Scanning I2C bus...");
  for (byte addr = 1; addr < 127; addr++) {
    Wire.beginTransmission(addr);
    if (Wire.endTransmission() == 0) {   // 0 = ACK received, device present
      Serial.print("Found device at 0x");
      Serial.println(addr, HEX);
    }
  }
  delay(3000);
}

Common I2C Failures

Symptom Likely Cause Fix
Scanner finds nothing Missing pull-ups, swapped SDA/SCL Add 4.7 kΩ pull-ups; check wiring
Random NACKs Bus too long or too much capacitance Shorten wires, lower clock, reduce pull-up value
Two devices clash Identical fixed address Use address-select pins or an TCA9548A multiplexer
Works at 3.3V, fails at 5V Level mismatch Use a bidirectional level shifter (BSS138-based)

SPI (Serial Peripheral Interface)

How It Works

Motorola introduced SPI as a fast, simple, full-duplex bus. One master controls the clock, and slaves are selected by dedicated lines:

  • MOSI: Master Out, Slave In
  • MISO: Master In, Slave Out
  • SCK: Serial clock
  • CS/SS: Chip Select (active low, one per slave)

Because MOSI and MISO are separate, data moves in both directions on every clock cycle. No start/stop framing and no addressing overhead make SPI very efficient.

SPI Modes

The two settings CPOL (clock idle polarity) and CPHA (sampling edge) give four modes. Mismatched modes are the top cause of SPI failures.

Mode CPOL CPHA Clock Idle Data Sampled On
0 0 0 Low Rising edge
1 0 1 Low Falling edge
2 1 0 High Falling edge
3 1 1 High Rising edge

Check the device datasheet. Most sensors and SD cards use Mode 0 or Mode 3.

Strengths and Limits

  • Highest throughput of the three, often 10-80 MHz.
  • Full duplex and no protocol overhead.
  • Simple slave hardware, since slaves need no address logic.
  • Each added slave costs one extra CS pin.
  • No acknowledgment, so the master cannot tell whether a slave exists.
  • No standard error checking.
  • Short range, typically on-board only.

SPI Code Example (Arduino C++)

#include <SPI.h>

const int CS_PIN = 5;   // Chip select for the target device

void setup() {
  Serial.begin(115200);
  pinMode(CS_PIN, OUTPUT);
  digitalWrite(CS_PIN, HIGH);    // Deselect device (CS idles high)
  SPI.begin();                   // Default ESP32 VSPI: SCK=18, MISO=19, MOSI=23
}

byte readRegister(byte reg) {
  // 8 MHz clock, MSB first, SPI Mode 0
  SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0));
  digitalWrite(CS_PIN, LOW);     // Select device
  SPI.transfer(reg | 0x80);      // Many chips set bit 7 to signal a read
  byte value = SPI.transfer(0x00); // Send dummy byte, clock in the reply
  digitalWrite(CS_PIN, HIGH);    // Deselect device
  SPI.endTransaction();
  return value;
}

void loop() {
  Serial.println(readRegister(0x0F), HEX);  // Example: WHO_AM_I register
  delay(1000);
}

Head-to-Head: Choosing the Right Protocol

Criterion Winner Why
Fewest wires for many devices I2C 2 wires, up to 127 devices
Maximum speed SPI Tens of MHz, no overhead
Simplest two-device link UART No clock, no addressing
Longest distance UART (with RS-485) Differential signaling reaches ~1200 m
Lowest power on idle bus I2C / UART No active clock when idle
Easiest to debug UART Readable on any serial terminal
Full duplex streaming SPI / UART Separate data lines
Hot-add devices I2C Address-based, no extra CS pins

Decision Guide

  1. Need to stream high-bandwidth data (TFT display, SD card, camera, flash)? Use SPI.
  2. Reading several low-speed sensors (temperature, IMU, RTC, OLED)? Use I2C.
  3. Talking to a GPS, Bluetooth/Wi-Fi AT module, or another MCU? Use UART.
  4. Running short of GPIO? Use I2C, or an I/O expander.
  5. Crossing a long cable in a noisy environment? Use UART over RS-485 (or CAN).

Real-World Project: Multi-Protocol Environmental Logger

One ESP32 can use all three protocols at once. This build logs air data to an SD card and shows it on a display.

Component Protocol Pins (ESP32)
BME280 (temp/humidity/pressure) I2C, addr 0x76 SDA GPIO21, SCL GPIO22
SSD1306 OLED display I2C, addr 0x3C Shared with BME280
MicroSD card module SPI SCK GPIO18, MISO GPIO19, MOSI GPIO23, CS GPIO5
NEO-6M GPS UART2 RX GPIO16, TX GPIO17

Wiring Notes

  • Power the BME280, SSD1306, and NEO-6M from 3.3V. Many SD modules include a 5V regulator and level shifter, so check yours before connecting.
  • Both I2C devices share one bus because their addresses differ.
  • Add a 100 nF decoupling capacitor near each IC’s VCC pin.
  • Keep SPI wires short (under about 15 cm) to avoid ringing at higher clock rates.

Firmware Skeleton

#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <Adafruit_BME280.h>

Adafruit_BME280 bme;
const int SD_CS = 5;

void setup() {
  Serial.begin(115200);
  Serial2.begin(9600, SERIAL_8N1, 16, 17);   // UART: GPS

  Wire.begin(21, 22);                         // I2C: sensor + display
  if (!bme.begin(0x76)) Serial.println("BME280 not found");

  if (!SD.begin(SD_CS)) Serial.println("SD init failed"); // SPI: storage
}

void loop() {
  float t = bme.readTemperature();            // I2C read
  File f = SD.open("/log.csv", FILE_APPEND);  // SPI write
  if (f) {
    f.printf("%.2f\n", t);
    f.close();
  }
  delay(5000);
}

Troubleshooting Checklist

UART

  • Cross TX to RX. Straight TX-to-TX gives silence.
  • Confirm matching baud rate and 8N1 settings.
  • Join grounds between devices.
  • Never connect a 5V TX directly to a 3.3V RX pin.

I2C

  • Verify pull-up resistors exist (one pair per bus, not per device).
  • Run a bus scanner to confirm addresses.
  • Drop to 100 kHz while debugging.
  • Check a logic analyzer for a stuck-low SDA after a crashed transaction.

SPI

  • Match CPOL/CPHA to the datasheet.
  • Make sure only one CS is low at a time.
  • Keep CS high during boot to prevent bus contention.
  • Lower the clock speed if reads are intermittent.

A cheap 8-channel logic analyzer with Sigrok/PulseView decodes all three protocols and saves hours of guesswork.

FAQ

Is I2C faster than SPI?

No. SPI is faster. Standard I2C tops out at 100-400 kHz (3.4 MHz in High-speed mode), while SPI commonly runs 10-80 MHz with no addressing or acknowledgment overhead.

Can I use I2C, SPI, and UART together on the same microcontroller?

Yes. They use separate peripherals and pins, so they run concurrently. Most MCUs, including the ESP32, STM32, and ATmega328P-class boards, have all three built in.

How many devices can I connect to each protocol?

I2C supports up to 127 devices with 7-bit addressing, limited in practice by 400 pF bus capacitance. SPI supports as many devices as you have CS pins. UART supports one device per port, or several using RS-485 multi-drop.

Which protocol is best for beginners?

UART is the easiest, since you can read traffic in any serial monitor. I2C comes next because the libraries handle addressing for you. SPI takes more care with modes and chip-select wiring.

Hot this week

Agricultural Robots Explained: Weeding, Harvesting, and Monitoring

Learn how agricultural robots weed, harvest, and monitor crops, with ROS2 code, kinematics, sensor tables, and tuning tips. Build your stack today.

Industrial Robots Explained: Arms, SCARA, Delta, and Gantry Types Compared

Compare articulated, SCARA, Delta, and gantry robots by speed, payload, and kinematics. Includes IK math and ROS2 code. Pick the right robot today.

Robot Grippers and End Effectors: Types and How to Choose

Compare parallel, vacuum, soft, and magnetic robot grippers. Learn the force math, payload sizing, and ROS2 control steps to pick the right end effector.

Robot Actuators Explained: Motors, Servos, and What Drives Movement

Compare DC, stepper, servo and BLDC robot actuators with torque math, wiring specs and ROS2 code. Pick the right motor for your robot today.

Robot Simulation Explained: Gazebo, Isaac Sim, MuJoCo, and Webots Compared

Compare Gazebo, Isaac Sim, MuJoCo, and Webots on physics, sensors, ROS 2 support, and cost. Pick the right simulator for your robot today.

Topics

Agricultural Robots Explained: Weeding, Harvesting, and Monitoring

Learn how agricultural robots weed, harvest, and monitor crops, with ROS2 code, kinematics, sensor tables, and tuning tips. Build your stack today.

Industrial Robots Explained: Arms, SCARA, Delta, and Gantry Types Compared

Compare articulated, SCARA, Delta, and gantry robots by speed, payload, and kinematics. Includes IK math and ROS2 code. Pick the right robot today.

Robot Grippers and End Effectors: Types and How to Choose

Compare parallel, vacuum, soft, and magnetic robot grippers. Learn the force math, payload sizing, and ROS2 control steps to pick the right end effector.

Robot Actuators Explained: Motors, Servos, and What Drives Movement

Compare DC, stepper, servo and BLDC robot actuators with torque math, wiring specs and ROS2 code. Pick the right motor for your robot today.

Robot Simulation Explained: Gazebo, Isaac Sim, MuJoCo, and Webots Compared

Compare Gazebo, Isaac Sim, MuJoCo, and Webots on physics, sensors, ROS 2 support, and cost. Pick the right simulator for your robot today.

How Robots Navigate: Localization, Mapping, and Planning Explained

Learn how robot navigation works: EKF localization, SLAM mapping, A* and DWB planning, plus copy-paste ROS2 Nav2 configs. Build yours today.

Kalman Filters Explained: The Gold Standard for Sensor Fusion

Master Kalman filter sensor fusion: math, Python code, ROS2 EKF configs, and Q/R tuning for robots. Build a reliable state estimator today.

Control Theory for Robotics: A Learning Roadmap

Master control theory for robotics: PID, state-space, LQR, and MPC with ROS2 code, tuning tables, and a staged study path. Start the roadmap.

Related Articles

Popular Categories