37 Serial Communication Protocols
Sensor Squad: The Three Communication Highways
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:
- Hardware Platform Selection: Understanding microcontroller and SBC capabilities helps you match serial protocols to available hardware interfaces
- Communication and Protocol Bridging: Knowledge of higher-level protocol concepts provides context for understanding low-level serial interfaces
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
For Beginners: Serial Communication Basics
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
37.4 Protocol Selection Matrix
Alternative View: Serial Protocol Selection Matrix
This variant presents I2C, SPI, and UART selection as a multi-criteria decision matrix, helping engineers systematically evaluate trade-offs.
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) |
Putting Numbers to It
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:
- Design two versions:
- Version A: All devices on I2C bus
- Version B: Sensors on I2C, Flash on SPI
- Calculate for each version:
- Total pin count required
- Maximum theoretical data throughput
- Firmware complexity (protocol handling)
- Simulate I2C bus:
- Create I2C devices for all sensors
- Scan bus and verify addressing
- Read from all devices
- Calculate total transaction time
- 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:
- Medical thermometer: 35-42°C range, 0.1°C accuracy
- Sensor: TMP117 (10mV/°C)
- ADC reference: 3.3V
- Industrial pressure sensor: 0-100 PSI, 0.5 PSI accuracy
- Sensor: 0.5-4.5V output for 0-100 PSI
- ADC reference: 5.0V
- pH sensor: pH 0-14, 0.01 pH accuracy
- Sensor: 414mV per pH unit
- ADC reference: 3.3V
Tasks:
For each sensor, calculate:
- Required voltage resolution
- Required ADC bits
- Actual achievable resolution with standard ADC (10-bit, 12-bit, 16-bit)
Select minimum ADC resolution for each application
Calculate total system cost with different ADC options:
- 10-bit ADC: $3
- 12-bit ADC: $5
- 16-bit ADC: $12
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
1. Confusing UART, SPI, and I2C Use Cases
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.
2. Assuming Default Baud Rates Will Work
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.
3. Forgetting Pull-Up Resistors on I2C Lines
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.
4. Ignoring SPI Chip-Select Timing
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.
Related Chapters
Deep Dives:
- Communication and Protocol Bridging - Higher-level protocol concepts
- Sensor Fundamentals - ADC and sensor interfaces
- Prototyping Hardware - Development board interfaces
Comparisons:
- Hardware Platform Selection - MCU interface availability
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 |