I2C, SPI, UART, serial communication, sensor interface, ESP32, Arduino, bus protocol
Start With the Measurement Story
A sensor bus is a conversation with timing, addressing, voltage, and error rules. Before wiring I2C, SPI, or UART, decide who talks, who listens, how conflicts are avoided, and how a failed read will be detected.
13.1 In 60 Seconds
Sensor communication protocols (I2C, SPI, UART) are the “languages” sensors and microcontrollers use to exchange data. I2C uses just two wires and supports dozens of sensors on one bus, while SPI trades extra wires for much faster data transfer. Choosing the right protocol depends on your speed needs, available GPIO pins, and how many sensors you are connecting.
Phoebe’s Field Notes: Why the Same Pull-Up Needs a Smaller Value at 400 kHz
Phoebe’s Why
I2C’s SDA and SCL lines are open-drain: a device can only pull the line low, never drive it high. The pull-up resistor and the bus’s own parasitic capacitance – wire, pins, ESD structures – are what pull the line back up, and that recovery is not instant. It is a first-order RC charging transient, the same time constant \(\tau=RC\) that governs any resistor charging a capacitor. The I2C specification cares whether the line crosses from its low threshold to its high threshold before the next clock edge needs it there, which is exactly why this chapter’s own key-concepts line hedges its pull-up value with a clock speed – “4.7 kohm at 100 kHz” – instead of naming one resistor for every mode. The wire itself is a separate story: at I2C’s clock frequencies, and even its fast edge-rate harmonics, the bus is only a tiny fraction of a wavelength long, so it behaves as a lumped RC network rather than a transmission line – which is why I2C design worries about capacitance and pull-up value, not the cable matching an RF antenna feed line must have.
The Derivation
Open-drain rise through a pull-up into bus capacitance:
A bus stays a lumped element, not a transmission line, while its physical length is much shorter than the signal wavelength:
\[\ell \ll \lambda = \frac{c}{f}\]
Worked Numbers: This Chapter’s Own Bus Numbers
Worst case per spec: \(R=4.7\text{k}\Omega\), \(C=400\) pF (the standard/fast-mode ceiling this chapter’s own bus-capacitance estimator checks against): \(\tau=RC=1.88\ \mu\text{s}\), \(t_r=0.847\times1.88\ \mu\text{s}=1.59\ \mu\text{s}\) – slower than even the 1000 ns Standard-mode rise-time limit. A bus that actually reaches 400 pF cannot run reliably at 4.7 kohm even at 100 kHz, which is why 400 pF is a hard ceiling, not a target.
Realistic multi-sensor bus (this chapter’s own calculator defaults: 30 cm wire + 3 sensors at 10 pF each \(\approx60\) pF): \(\tau=4700\times60\text{pF}=282\) ns, \(t_r=0.847\times282\text{ns}=239\) ns – inside the 300 ns Fast-mode limit, which is why the “typically 0.5-2 meters” wiring guidance above works in practice.
Solving the Fast-mode limit backwards at the 400 pF ceiling: \(R\le300\text{ns}/(0.847\times400\text{pF})=885\ \Omega\) – far below 4.7 kohm, which is exactly why fast, heavily-loaded buses use smaller pull-ups (often 1-2.2 kohm) instead of the beginner-friendly 4.7 kohm value.
Electrically-short check: even a 10 MHz edge-rate harmonic has \(\lambda=c/f=30.0\) m in free space; a 2 m bus is \(2/30.0=6.67\%\) of that wavelength – short enough for the lumped RC model above to hold. An RF feedline anywhere near a wavelength long would need transmission-line matching instead; a bus this short never does.
Key Concepts
I2C (Inter-Integrated Circuit): A two-wire serial protocol (SDA data, SCL clock) supporting multiple devices on one bus using 7-bit addresses; standard speed 100 kHz, fast mode 400 kHz, fast-plus 1 MHz
SPI (Serial Peripheral Interface): A four-wire full-duplex protocol (MOSI, MISO, SCK, CS) offering higher speed than I2C (up to tens of MHz) but requiring a dedicated chip-select line per device
UART (Universal Asynchronous Receiver/Transmitter): An asynchronous two-wire protocol (TX, RX) using agreed baud rates; simple and universal but limited to point-to-point connections with no inherent bus topology
1-Wire: A single-wire protocol (plus ground) supporting multiple addressable devices; commonly used by DS18B20 temperature sensors; slow but minimal wiring
Pull-Up Resistors on I2C: I2C SDA and SCL lines use open-drain signaling and require external pull-up resistors (typically 4.7 kohm at 100 kHz) to define the HIGH state between transactions
Address Conflicts: Two I2C devices with the same address on the same bus cause data corruption. Check all sensor addresses before designing a multi-sensor board; some sensors offer address-select pins
Clock Stretching: An I2C feature where a slow slave holds SCL low to pause the master while preparing data; not all masters support it — check the microcontroller documentation before relying on this feature
Logic Level Compatibility: Mixing 3.3 V and 5 V devices on the same I2C or SPI bus can damage 3.3 V inputs. Use bidirectional level shifters or verified series-resistor approaches when mixing voltage domains
13.2 Learning Objectives
By the end of this chapter, you will be able to:
Differentiate between I2C, SPI, and UART communication protocols based on wiring, speed, and topology
Configure an I2C bus with correct pull-up resistors and address assignments for multi-sensor applications
Implement SPI communication with proper mode selection for high-speed sensor data transfer
Diagnose common protocol issues including address conflicts, missing pull-ups, and SPI mode mismatches
Evaluate and justify the appropriate protocol for specific sensor requirements and constraints
13.3 Introduction
Sensor communication protocols are the foundation of IoT data acquisition. Every sensor reading must travel from the physical sensor to your microcontroller through a defined communication interface. Understanding these protocols enables you to design reliable, efficient sensor networks.
Communication Protocol Basics
Think of communication protocols like different languages. Just as people need to speak the same language to understand each other, sensors and microcontrollers need to “speak” the same protocol to exchange data. The most common “languages” are I2C (pronounced “eye-squared-see” or “eye-two-see”), SPI (“spy”), and UART (“you-art”). Each has different rules about how many wires to use, how fast to talk, and how to address specific devices.
13.4 I2C Communication Protocol
15 min | Intermediate | P06.C09.U01a
I2C (Inter-Integrated Circuit) is a two-wire synchronous protocol perfect for connecting multiple sensors using minimal GPIO pins. It uses:
SDA (Serial Data): Bidirectional data line
SCL (Serial Clock): Clock signal from master
7-bit addressing: Up to 112 usable device addresses (128 total, 16 reserved)
Pull-up resistors: Required on both lines
13.4.1 I2C Protocol Sequence
Figure 13.1: I2C Protocol Sequence: Reading Temperature from BMP280 Sensor
Figure 13.2: I2C bus architecture for multi-sensor networks
13.4.2 Key I2C Protocol Elements
START Condition: SDA goes LOW while SCL is HIGH (signals transaction start)
STOP Condition: SDA goes HIGH while SCL is HIGH (signals transaction end)
ACK (Acknowledge): Receiver pulls SDA LOW during 9th clock pulse (data received successfully)
NACK (Not Acknowledge): Receiver leaves SDA HIGH (last byte or error)
Repeated START: START without preceding STOP (direction change from write to read)
13.4.3 I2C Implementation Example
Reading sensor data over I2C follows a two-phase pattern: first write the register address you want to read, then request the data bytes. The endTransmission(false) sends a Repeated START instead of a STOP, keeping the bus locked for the subsequent read.
For learning, trace the transaction before reading code:
Estimate total bus capacitance to determine if your I2C bus will work reliably. The I2C specification limits bus capacitance to 400 pF for standard mode and fast mode.
html`<div style="background: var(--bs-light, #f8f9fa); padding: 1rem; border-radius: 8px; border-left: 4px solid ${i2cCapOk ?'#16A085':'#E74C3C'}; margin-top: 0.5rem;"><p><strong>Wire capacitance:</strong> ${i2cWireCap.toFixed(0)} pF (${i2cWireLength} cm at ~100 pF/m)</p><p><strong>Sensor capacitance:</strong> ${i2cSensorsTotalCap.toFixed(0)} pF (${i2cNumSensors} sensors x ${i2cSensorCap} pF)</p>${i2cHasOled ?html`<p><strong>OLED display:</strong> ~400 pF (worst-case estimate with long flex cables; typical breakout boards are 100-150 pF)</p>`:''}<p><strong>Total bus capacitance:</strong> <span style="color: ${i2cCapOk ?'#0b6b5d':'#E74C3C'}; font-weight: bold;">${i2cTotalCap.toFixed(0)} pF</span> (limit: 400 pF for standard/fast mode)</p><p><strong>Status:</strong> ${i2cCapOk ?'Within spec - '+ i2cCapMargin.toFixed(0) +' pF margin remaining':'EXCEEDS LIMIT - reduce wire length, remove devices, or use I2C buffer'}</p></div>`
Interactive: I2C Bus Scanner
This simulation demonstrates how I2C bus scanning works. Add devices to the bus, then click “Scan Bus” to see the master controller query each address and detect ACK/NACK responses.
SPI (Serial Peripheral Interface) is a synchronous, full-duplex communication protocol using four lines: MISO (Master In Slave Out), MOSI (Master Out Slave In), SCK (Serial Clock), and CS (Chip Select). Unlike I2C, SPI supports simultaneous bidirectional data transfer.
Moderate cable length (limited by 400 pF bus capacitance; typically 0.5-2 meters depending on number of devices and wire type)
Choose SPI when:
High-speed data transfer required (>1 MB/s)
Large data blocks (SD cards, displays)
Real-time requirements (<1 ms latency)
Plenty of GPIO pins available
Try It: GPIO Pin Budget Calculator
Plan your wiring by entering how many I2C, SPI, and UART devices you need. The calculator shows total GPIO pin usage and whether your microcontroller has enough pins.
Option A: I2C bus (BME280 + BH1750 + MPU6050): Wire count 2 (SDA, SCL shared), GPIO usage 2 pins total for 3+ sensors, max speed 400kHz (~44kB/s), read latency ~100-200us per sensor at 400kHz (address + register overhead), power during transfer ~1mA, cable length up to 1-3 meters with 4.7k pull-ups
Option B: SPI bus (BME280 + SD card + TFT display): Wire count 3 shared + 1 CS per device = 6 pins for 3 devices, max speed 10-40MHz (1-5MB/s), read latency ~10us per sensor (direct register access), power during transfer ~5-10mA (higher clock), cable length 10-30cm max at high speeds
Decision Factors: For battery-powered environmental monitoring nodes with 3-5 slow sensors, I2C saves pins and power while 400kHz is adequate for 100Hz sensor reads. For data logging to SD card (500kB/s sustained), displays (30fps video), or high-speed ADCs (1MSPS), SPI is mandatory. Hybrid approach: use I2C for slow sensors, SPI for SD/display. Watch for I2C address conflicts - BME280 has only 2 addresses (0x76, 0x77), limiting you to 2 per bus without multiplexer.
13.6 UART Serial Communication
10 min | Intermediate | P06.C09.U01c
UART (Universal Asynchronous Receiver/Transmitter) is the simplest serial protocol, using just two wires for point-to-point communication without a clock signal. Unlike I2C and SPI, UART is asynchronous – both sides must agree on the baud rate (bits per second) beforehand.
13.6.1 UART Key Characteristics
TX (Transmit) and RX (Receive): Two unidirectional lines (cross-connected between devices)
Asynchronous: No shared clock; both devices must use the same baud rate
Point-to-point only: Connects exactly two devices (no bus topology)
Common baud rates: 9600, 19200, 38400, 57600, 115200 bps
Frame format: Start bit + 8 data bits + optional parity + 1-2 stop bits
13.6.2 When to Use UART
UART is commonly used for GPS modules (NMEA sentences at 9600 bps), Bluetooth modules (HC-05 AT commands), GSM/cellular modems, and debug/logging output. Many sensors provide UART as a simpler alternative to I2C/SPI, though at lower data rates.
13.6.3 Optional ESP32 UART Example
Open this only after you understand the wiring rule: sensor TX connects to MCU RX, sensor RX connects to MCU TX, and both devices must use the same baud rate.
// Reading GPS data over UART (Serial2 on ESP32)
#define GPS_RX 16
#define GPS_TX 17
void setup() {
Serial.begin(115200); // USB debug output
Serial2.begin(9600, SERIAL_8N1, GPS_RX, GPS_TX); // GPS at 9600 baud
}
void loop() {
while (Serial2.available()) {
char c = Serial2.read();
Serial.print(c); // Forward GPS NMEA sentences to USB
}
}
Interactive: UART Data Rate Calculator
Calculate the effective data throughput for a UART connection based on baud rate and frame configuration.
uartParityBits = uartParity ==="None"?0:1uartFrameBits =1+ uartDataBits + uartParityBits + uartStopBits// Effective data rateuartBytesPerSec = uartBaud / uartFrameBitsuartEfficiency = (uartDataBits / uartFrameBits) *100// Time per byteuartTimePerByte = (uartFrameBits / uartBaud) *1e6
Show code
html`<div style="background: var(--bs-light, #f8f9fa); padding: 1rem; border-radius: 8px; border-left: 4px solid #E67E22; margin-top: 0.5rem;"><p><strong>Frame format:</strong> 1 start + ${uartDataBits} data + ${uartParityBits} parity + ${uartStopBits} stop = <strong>${uartFrameBits} bits/frame</strong></p><p><strong>Effective data rate:</strong> ${uartBytesPerSec.toFixed(0)} bytes/sec (${(uartBytesPerSec /1024).toFixed(2)} KB/s)</p><p><strong>Time per byte:</strong> ${uartTimePerByte.toFixed(1)} us</p><p><strong>Protocol efficiency:</strong> ${uartEfficiency.toFixed(1)}% (${uartDataBits} data bits out of ${uartFrameBits} total)</p><p style="font-size: 0.85em; color: #7F8C8D;">Common config "${uartDataBits}${uartParity.charAt(0)}${uartStopBits}" at ${uartBaud} baud. GPS modules typically use 9600 8N1 (960 bytes/sec).</p></div>`
13.6.4 I2C vs SPI vs UART Quick Reference
Feature
I2C
SPI
UART
Wires
2
3 + 1/device
2
Topology
Bus (multi-device)
Bus (multi-device)
Point-to-point
Clock
Synchronous (shared)
Synchronous (shared)
Asynchronous (agreed)
Speed
100 kHz - 3.4 MHz
1-100 MHz
9600 - 921600 bps
Duplex
Half-duplex
Full-duplex
Full-duplex
Best For
Many slow sensors
High-speed data
Simple serial devices
Protocol Selector
Select your project requirements and see which protocol is the best fit. The tool scores each protocol across your priorities and highlights the recommended choice.
I2C saves pins - Use it when many low-to-moderate-speed sensors can share SDA and SCL.
SPI buys speed and determinism - Use it when bandwidth, full-duplex transfer, or device-specific chip-select timing matters.
UART is point-to-point and simple - Use it for modules that stream text or binary frames over TX/RX.
Diagnostics deserve their own pass - Move to the child chapter when the design depends on pull-up sizing, bus capacitance, SPI modes, or address conflicts.