37  Serial Communication Protocols

In 60 Seconds

Three serial protocols serve different IoT needs: I2C (2 wires, up to 127 devices at 400kHz, best for sensor clusters within 1m), SPI (4+ wires, up to 10MHz+, best for flash/displays needing speed), and UART (2 wires, point-to-point at 9600-115200 baud, best for GPS/Bluetooth modules). Most field failures stem from I2C address conflicts, bus capacitance on long wires, or missing pull-up resistors – issues that rarely appear on the bench.

Minimum Viable Understanding
  • Three serial protocols serve different needs: I2C (2 wires, 127 devices, best for sensor clusters), SPI (4+ wires, highest speed, best for flash/displays), and UART (2 wires, point-to-point, best for GPS/Bluetooth modules).
  • I2C minimizes wiring but has distance limitations (~1m) and requires pull-up resistors; SPI maximizes throughput but uses more GPIO pins; UART provides simplicity for single-device connections.
  • Common deployment failures stem from I2C address conflicts, bus capacitance exceeding limits with long wires, and missing pull-up resistors – all issues that rarely appear during bench testing but cause intermittent failures in the field.

Max the Microcontroller needed to connect to lots of friends, and each highway worked differently!

I2C Avenue (2 lanes): Sammy the Sensor, the humidity sensor, and the pressure sensor all shared this quiet road. Max shouted an address number and the right sensor answered. “It’s cheap to build – only 2 wires!” said Sammy. “But we can’t talk super fast, and if the road gets too long, messages get garbled.”

SPI Expressway (4 lanes): Lila the LED Display and the flash memory chip had their own fast highway. “I need SPEED!” said Lila. “SPI gives me 4 lanes – data going both ways at the same time!” The downside? Each friend needed their own exit ramp (chip select pin), using up Max’s limited connection pins.

UART Lane (2 lanes, private): The GPS module had a simple, direct road to Max. “I don’t need fancy addresses or high speed,” said the GPS. “Just a straightforward conversation between the two of us, and I can work over longer distances too!”

Bella the Battery added: “Remember, I2C uses the least energy for talking to many sensors. That’s why I love it for our battery-powered projects!”

37.1 Learning Objectives

By the end of this chapter, you will be able to:

  • Compare Serial Protocols: Evaluate I2C, SPI, and UART for different sensor integration scenarios
  • Apply Protocol Selection Criteria: Use pin count, speed, and distance factors to choose appropriate interfaces
  • Implement Multi-Sensor Systems: Design reliable bus architectures for connecting multiple sensors
  • Debug Protocol Issues: Identify and resolve common serial communication problems

37.2 Prerequisites

Before diving into this chapter, you should be familiar with:

Cross-Hub Connections

Enhance your learning by exploring related resources:

  • Simulations Hub: Try interactive tools for protocol comparison and signal timing analysis
  • Hands-On Labs Hub: Practice I2C and SPI communication with ESP32 simulations
  • Knowledge Gaps Hub: Clarify common misconceptions about serial protocol trade-offs

Think of serial communication like sending a message through a tube - one letter at a time. Different protocols are like different types of tubes with different rules.

Everyday Analogy: Imagine three ways to pass notes in class:

  • I2C is like passing notes through a single row with seat numbers - many people can share one path, but you need to call out who the note is for
  • SPI is like having a private messenger for each friend - faster delivery, but you need more messengers
  • UART is like two tin cans connected by string - simple, works over longer distances, but only connects two people
Term Simple Explanation
I2C (I-squared-C) A two-wire bus where many devices share the same wires, using addresses to know who’s talking
SPI A four-wire system that’s very fast, with a dedicated “select” wire for each device
UART The simplest two-wire connection between just two devices, like a phone call
Bus A shared communication path, like a road many cars can use
Pull-up Resistor A component that keeps I2C wires at the correct voltage when not actively being used

Why This Matters for IoT: Your temperature sensor uses I2C (2 wires, simple). Your display uses SPI (fast updates). Your GPS module uses UART (comes with built-in protocol). Understanding these helps you wire things correctly and debug problems when sensors don’t respond.

Pitfall: Ignoring Serial Protocol Timing in Multi-Sensor Systems

The Mistake: Connecting multiple I2C sensors assuming they will “just work,” then experiencing intermittent data corruption, missed readings, or bus lockups in deployment.

Why It Happens: I2C address conflicts, bus capacitance exceeding limits with long wires, clock stretching incompatibilities between sensors, and missing pull-up resistors all manifest as intermittent failures that rarely appear during bench testing with short jumper wires.

The Fix: Map all I2C addresses before wiring - use a bus scanner script first. Calculate total bus capacitance (each sensor + wire length × 100pF/m) and keep under 400pF for 100kHz I2C. Use appropriate pull-up resistors (2.2k-4.7k for 3.3V systems). Add I2C bus recovery code (clock pulse sequence) for stuck-bus conditions. Consider I2C multiplexers (TCA9548A) for address conflicts. For long runs (>30cm), switch to RS-485 or CAN bus instead.

Key Takeaway

In one sentence: Choose I2C for multiple low-speed sensors (2 wires, 127 devices), SPI for high-speed peripherals like displays and flash (fastest, more pins), and UART for GPS/Bluetooth modules (simplest, point-to-point).

Remember this: I2C = many sensors, few wires. SPI = fast data, more wires. UART = simple modules, two wires.

37.3 Serial Protocol Comparison

Serial protocol comparison showing three protocols: I2C uses 2 wires (SDA, SCL) for multi-master communication at 100kHz-3.4MHz with up to 127 devices, ideal for multiple sensors and short distances; SPI uses 4+ wires (MOSI, MISO, SCK, CS) for full-duplex point-to-point communication at 10-100+ MHz, ideal for high-speed data, displays, flash memory; UART uses 2 wires (TX, RX) for asynchronous point-to-point communication at 9600-921600 baud, ideal for GPS modules, Bluetooth/Wi-Fi, and debug console
Figure 37.1: Serial Protocol Comparison: I2C, SPI, and UART Characteristics and Use Cases

37.4 Protocol Selection Matrix

This variant presents I2C, SPI, and UART selection as a multi-criteria decision matrix, helping engineers systematically evaluate trade-offs.

Serial protocol decision matrix showing I2C, SPI, and UART across five criteria (pin count, speed, distance, multi-device support, and complexity) with radar-style scoring where I2C scores high on multi-device and low pins, SPI scores high on speed and distance, and UART scores high on simplicity and legacy support
Figure 37.2: Alternative view: Each serial protocol excels in different dimensions. I2C minimizes wiring for sensor clusters. SPI maximizes throughput for displays and flash. UART provides simplicity and long-distance capability. Selection depends on the dominant constraint in your application.

37.5 Protocol Technical Details

37.5.1 I2C (Inter-Integrated Circuit)

I2C uses a shared bus architecture with just two wires:

Property Value
Wires 2 (SDA data, SCL clock)
Speed Standard 100kHz, Fast 400kHz, Fast+ 1MHz, High-speed 3.4MHz
Addressing 7-bit (127 devices) or 10-bit (1024 devices)
Distance 1-3 meters typical (capacitance limited)
Pull-ups Required (2.2k-10k depending on bus speed)

Bus capacitance limit: I2C standard mode (100 kHz) allows maximum \(400\) pF total bus capacitance.

\[ C_{\text{total}} = \sum C_{\text{devices}} + (L_{\text{wire}} \times 100\text{ pF/m}) \]

With 5 sensors at 15 pF each + 0.5m wire: \(C = (5 \times 15) + (0.5 \times 100) = 125\) pF ✓

At 2m wire length: \(C = 75 + 200 = 275\) pF (still safe). At 4m: \(C = 75 + 400 = 475\) pF ✗ (exceeds limit, signal degrades).

Pull-up resistor sizing: For 3.3V VCC, 400 pF load, fast mode (400 kHz):

\[ R_{\text{pull-up}} = \frac{t_r}{0.8473 \times C_{\text{bus}}} \approx \frac{300\text{ns}}{0.8473 \times 400\text{pF}} \approx 1\text{k}\Omega \]

Use 2.2 kΩ for margin.

Best for: Multiple sensors, EEPROMs, RTCs, small displays, low pin count

Common I2C Addresses:

  • BME280 (temp/humidity): 0x76 or 0x77
  • SSD1306 OLED: 0x3C or 0x3D
  • MPU6050 IMU: 0x68 or 0x69
  • DS3231 RTC: 0x68

37.5.2 SPI (Serial Peripheral Interface)

SPI uses a master-slave architecture with dedicated select lines:

Property Value
Wires 4 minimum (MOSI, MISO, SCK, CS per device)
Speed 1-100+ MHz (device dependent)
Topology Master-slave, one CS per slave
Duplex Full-duplex (simultaneous read/write)
Distance 1-3 meters (signal integrity limited)

Best for: Flash memory, SD cards, displays, ADCs, high-speed sensors

37.5.3 UART (Universal Asynchronous Receiver/Transmitter)

UART provides simple point-to-point asynchronous communication:

Property Value
Wires 2 (TX, RX) + ground
Speed 9600-921600 baud typical
Topology Point-to-point only
Distance 15+ meters (with RS-232/RS-485)
Handshaking Optional (RTS/CTS)

Best for: GPS modules, Bluetooth/Wi-Fi modules, debug console, legacy devices

37.6 Hands-On Lab: I2C vs SPI Protocol Selection

Objective: Understand trade-offs between I2C and SPI protocols.

Scenario: Design a weather station with: - 1x Temperature/Humidity sensor - 1x Barometric pressure sensor - 1x Real-time clock (RTC) - 1x EEPROM for data logging - 1x Flash memory (4MB) for firmware updates

Tasks:

  1. Design two versions:
    • Version A: All devices on I2C bus
    • Version B: Sensors on I2C, Flash on SPI
  2. Calculate for each version:
    • Total pin count required
    • Maximum theoretical data throughput
    • Firmware complexity (protocol handling)
  3. Simulate I2C bus:
    • Create I2C devices for all sensors
    • Scan bus and verify addressing
    • Read from all devices
    • Calculate total transaction time
  4. Simulate SPI version:
    • Create SPI flash device
    • Simulate firmware read (1KB)
    • Compare speed with I2C EEPROM

Expected Results:

  • I2C version: Simpler wiring, lower pin count, adequate for sensor data rates
  • SPI version: Faster firmware updates, slightly more complex

37.7 Hands-On Lab: ADC Resolution Determination

Objective: Calculate required ADC resolution for various sensors.

Scenario: Design precision sensors for:

  1. Medical thermometer: 35-42°C range, 0.1°C accuracy
    • Sensor: TMP117 (10mV/°C)
    • ADC reference: 3.3V
  2. Industrial pressure sensor: 0-100 PSI, 0.5 PSI accuracy
    • Sensor: 0.5-4.5V output for 0-100 PSI
    • ADC reference: 5.0V
  3. pH sensor: pH 0-14, 0.01 pH accuracy
    • Sensor: 414mV per pH unit
    • ADC reference: 3.3V

Tasks:

  1. For each sensor, calculate:

    • Required voltage resolution
    • Required ADC bits
    • Actual achievable resolution with standard ADC (10-bit, 12-bit, 16-bit)
  2. Select minimum ADC resolution for each application

  3. Calculate total system cost with different ADC options:

    • 10-bit ADC: $3
    • 12-bit ADC: $5
    • 16-bit ADC: $12
  4. Determine if a single high-resolution ADC can serve all sensors or if multiple ADCs are needed

Deliverables:

  • ADC requirement table for each sensor
  • Cost-benefit analysis
  • Recommendation for ADC configuration

37.8 Knowledge Check

Common Mistake: I2C Address Conflicts Causing Silent Data Corruption

The Mistake: A weather station project connects two sensors to the same I2C bus: BME280 (temperature/humidity/pressure) and BMP280 (temperature/pressure). Both sensors use the default I2C address 0x76. The system compiles and runs without errors, but temperature readings are erratic and nonsensical (e.g., alternating between 24.5°C and 67.8°C every second).

Why It Happens: When two I2C devices share the same address on the same bus, both respond simultaneously to read commands. The microcontroller receives corrupted data – a mix of bits from both sensors. Unlike a compile error or exception, this manifests as “garbage data” that looks valid but is wrong. Developers often spend days debugging sensor drivers, power supplies, and wiring before discovering the address conflict.

Symptoms of I2C Address Conflict:

  • Readings that alternate between two distinct value ranges
  • Data that changes depending on which sensor was read last
  • CRC/checksum failures (if sensor protocol includes them)
  • Intermittent “sensor not found” errors when scanning the bus
  • No compile errors, runtime errors, or obvious failures

The Fix - Use I2C Address Scanner First:

Step 1 - Scan the bus before connecting sensors:

#include <Wire.h>

void scanI2C() {
    Serial.println("Scanning I2C bus...");
    byte error, address;
    int nDevices = 0;

    for(address = 1; address < 127; address++) {
        Wire.beginTransmission(address);
        error = Wire.endTransmission();

        if (error == 0) {
            Serial.print("Device found at address 0x");
            if (address < 16) Serial.print("0");
            Serial.println(address, HEX);
            nDevices++;
        }
    }

    if (nDevices == 0)
        Serial.println("No I2C devices found");
    else
        Serial.println("Scan complete");
}

Step 2 - Check sensor datasheets for address options:

Sensor Default Address Alternate Address How to Change
BME280 0x76 0x77 Connect SDO pin to VCC instead of GND
BMP280 0x76 0x77 Connect SDO pin to VCC
MPU6050 0x68 0x69 Connect AD0 pin to VCC
SSD1306 OLED 0x3C 0x3D Solder jumper or address pin

Step 3 - Change one sensor’s address:

  • BME280: Keep at 0x76 (SDO → GND)
  • BMP280: Change to 0x77 (SDO → VCC)

Step 4 - Verify in code:

#define BME280_ADDR 0x76
#define BMP280_ADDR 0x77

BME280 bme(BME280_ADDR);
BMP280 bmp(BMP280_ADDR);

void setup() {
    if (!bme.begin(BME280_ADDR)) {
        Serial.println("BME280 not found at 0x76!");
    }
    if (!bmp.begin(BMP280_ADDR)) {
        Serial.println("BMP280 not found at 0x77!");
    }
}

Step 5 - Use I2C Multiplexer for Unavoidable Conflicts:

If sensors cannot change addresses (or you need multiple identical sensors):

// TCA9548A I2C multiplexer allows 8 I2C buses from one master
#define TCAADDR 0x70

void tcaSelect(uint8_t channel) {
    if (channel > 7) return;
    Wire.beginTransmission(TCAADDR);
    Wire.write(1 << channel);
    Wire.endTransmission();
}

// Read from sensor on channel 0
tcaSelect(0);
float temp1 = bme1.readTemperature();

// Read from sensor on channel 1 (same address, different bus)
tcaSelect(1);
float temp2 = bme2.readTemperature();

Prevention Checklist:

Real-World Cost: A commercial product shipped 5,000 units with this bug. Field returns cost $180K in shipping/labor before the address conflict was discovered. Always scan the bus first!

Key Concepts

  • UART: Universal Asynchronous Receiver-Transmitter — an asynchronous serial protocol using TX/RX lines with configurable baud rate and data framing, the simplest interface for sensor modules, GPS, and cellular modems
  • SPI (Serial Peripheral Interface): A synchronous full-duplex 4-wire protocol (SCLK, MOSI, MISO, CS) supporting clock speeds up to 50+ MHz for high-speed sensors and display controllers
  • I2C (Inter-Integrated Circuit): A synchronous 2-wire half-duplex protocol (SDA, SCL) supporting up to 127 devices per bus with 7-bit addresses, ideal for low-speed sensors sharing a common bus
  • Baud Rate: The number of signal changes per second in a UART interface (equal to bits/second for standard UART); mismatched baud rates between sender and receiver produce garbled output with no error indication
  • Pull-up Resistor: A resistor connecting an open-drain I2C line to VCC, required because I2C devices only pull lines low — missing or incorrectly sized pull-ups (typically 4.7 kΩ) cause communication failures
  • Logic Level: The voltage standard (3.3V or 5V) defining logic high and low thresholds; mismatched levels between a 5V Arduino and a 3.3V sensor can damage pins or cause protocol failures requiring a level shifter
  • Protocol Overhead: The non-payload bytes required by a serial protocol — I2C adds address and ACK bytes, SPI requires CS toggling, UART requires start/stop bits — affecting effective data throughput at a given clock rate

Common Pitfalls

UART is point-to-point full-duplex (best for debugging and GPS modules), SPI is high-speed multi-device (best for displays and SD cards), and I2C is multi-device on just 2 wires (best for sensors). Mixing these up leads to wiring complexity or performance problems. Choose based on speed requirements, number of devices, and wire count constraints.

Both ends of a UART connection must use the exact same baud rate — there is no auto-negotiation. Using 9600 baud when the sensor expects 115200 produces garbage data. Always check datasheets for default baud rates, and verify with a logic analyzer if you see corrupted output.

I2C SDA and SCL lines are open-drain and require pull-up resistors (typically 4.7kΩ) to function. Without them, the bus never returns to high, causing all reads to fail or return 0. Many development boards include pull-ups, but custom PCBs often omit them — always verify.

Each SPI device needs its own Chip Select (CS) line, and CS must be asserted low BEFORE the first clock edge. Sharing CS lines between devices or using software timing that is too fast causes data corruption and device conflicts. Use hardware SPI with dedicated CS pins for reliable operation.

37.9 Summary

This chapter explored serial communication protocols for IoT sensor integration:

  • I2C: A two-wire bus protocol supporting up to 127 devices on shared SDA/SCL lines. Ideal for multiple low-speed sensors (temperature, humidity, IMU) with minimal wiring. Requires pull-up resistors and has distance limitations (~1m).
  • SPI: A high-speed four-wire protocol with dedicated chip-select per device. Best for flash memory, displays, and high-bandwidth peripherals. Faster than I2C but requires more GPIO pins.
  • UART: Simple asynchronous two-wire point-to-point communication. Standard interface for GPS modules, Bluetooth/Wi-Fi modules, and debug consoles. Works over longer distances but connects only two devices.
  • Protocol Selection: Match protocol to requirements - I2C for sensor clusters, SPI for high-speed peripherals, UART for module interfaces. Consider pin count, speed, distance, and device count.
  • Common Pitfalls: I2C address conflicts, bus capacitance limits, missing pull-ups, and clock stretching issues can cause intermittent failures that are hard to debug in deployment.

Deep Dives:

Comparisons:

37.10 What’s Next

Direction Chapter Description
Next Development Workflow and Tooling IDEs, CI/CD pipelines, debugging, and OTA update strategies
Back Hardware Platform Selection MCU vs SBC selection and power budget calculations
Related Communication and Protocol Bridging Higher-level protocol concepts and gateway functions