KevsRobots Learning Platform
84% Percent Complete
By Kevin McAleer, 9 Minutes
Youβve mastered both I2C and SPI. Now comes the important question: which should you use for your next project? This lesson provides practical guidance for choosing between protocols based on your specific needs.
| Feature | I2C | SPI |
|---|---|---|
| Wires | 2 (SDA, SCL) | 4+ (SCLK, MOSI, MISO, CS per device) |
| Speed | 100-400 kHz (standard/fast) | 1-60+ MHz |
| Device addressing | Software (0x76, etc.) | Hardware (chip select pins) |
| Max devices | 127 theoretical, ~20 practical | Limited by available CS pins (~10) |
| Communication | Half-duplex (one direction at a time) | Full-duplex (simultaneous send/receive) |
| Protocol overhead | More (addressing, ACK/NACK) | Less (just data transfer) |
| Wiring complexity | Simple (2 wires total) | Complex (1 CS per device) |
| Pull-up resistors | Required | Not required |
| Distance | ~1 meter max | ~10 cm max (very short) |
| Error detection | Built-in ACK/NACK | None (application level) |
| Multi-controller | Supported (but complex) | Not supported |
| Power consumption | Lower (slower switching) | Higher (fast switching) |
1. You need many devices on limited pins
# 10 sensors using just 2 GPIO pins
i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
sensors = [
(0x76, "BME280"),
(0x68, "MPU6050"),
(0x3C, "OLED"),
(0x48, "ADS1115"),
# ... 6 more devices
]
for addr, name in sensors:
data = i2c.readfrom(addr, 1)
print(f"{name}: {data}")
Why I2C wins: Only 2 wires for everything. SPI would need 10 chip select pins plus 3 shared signals (13 total GPIO).
2. Devices are spread across your project
Robot example:
- Front distance sensor (30cm away)
- Back distance sensor (30cm away)
- Main control board
- Display module
Why I2C wins: Longer wire tolerance (~1m) vs SPI (~10cm). You can run I2C buses across your robot chassis.
3. Speed isnβt critical
# Reading temperature sensor every second
while True:
temp = read_bme280() # Takes ~10ms via I2C
print(f"Temp: {temp}Β°C")
time.sleep(1) # Update rate: 1 Hz
Why I2C wins: 400 kHz I2C is plenty fast for 1 Hz updates. No need for SPIβs complexity.
4. You want simpler wiring
# I2C: Just 2 wires
i2c = I2C(0, scl=Pin(9), sda=Pin(8))
# vs SPI: 4+ wires per device
spi = SPI(0, sck=Pin(2), mosi=Pin(3), miso=Pin(4))
cs1 = Pin(5, Pin.OUT)
cs2 = Pin(6, Pin.OUT)
cs3 = Pin(7, Pin.OUT)
Why I2C wins: Fewer connections = less to wire, debug, and troubleshoot.
5. Battery life is important
# I2C runs slower, consumes less power
# Good for battery projects
i2c_device = BME280(i2c)
i2c_device.set_mode(MODE_FORCED) # Ultra-low power
Why I2C wins: Lower clock speeds = less current draw. Perfect for solar or battery-powered sensors.
1. You need high speed data transfer
# Updating TFT display at 60 FPS
spi = SPI(0, baudrate=40000000) # 40 MHz
for frame in range(60):
# Send 240x320 pixels x 16 bits = 153,600 bytes
display.write_frame(frame_buffer) # ~4ms with SPI
# Would take ~150ms with I2C!
Why SPI wins: 100x faster transfer rates. Essential for displays and video.
2. Full-duplex communication is needed
# Radio module: send and receive simultaneously
spi = SPI(0, baudrate=10000000)
tx_data = b'\x01\x02\x03'
rx_data = bytearray(3)
# Send and receive at the same time
spi.write_readinto(tx_data, rx_data)
Why SPI wins: True full-duplex. I2C can only send OR receive, not both simultaneously.
3. Youβre working with memory devices
# SD card: transfer large files quickly
spi = SPI(0, baudrate=25000000) # 25 MHz
# Read 512-byte block in ~200Β΅s
data = sd_card.read_block(block_num)
# Would take ~10ms with I2C
Why SPI wins: SD cards, flash memory, and EEPROMs need fast transfer. SPI is the standard.
4. Devices are close together
PCB design:
- All components within 5cm
- Controller in center
- Sensors around edges
Why SPI wins: When everything is close (PCB layout), SPIβs short-distance limitation doesnβt matter, and you get speed benefits.
5. You need guaranteed timing
# Precise timing for WS2812 LEDs
# SPI can generate exact timing signals
spi = SPI(0, baudrate=2500000) # Precise timing
# Send WS2812 data via SPI (timing-critical)
spi.write(led_data)
Why SPI wins: SPI provides deterministic timing. I2C has variable timing due to ACK/NACK overhead.
Requirements:
Best choice: Mixed approach
# I2C for sensors and display (close together, speed not critical)
i2c = I2C(0, scl=Pin(9), sda=Pin(8))
bme280 = BME280(i2c, 0x76)
rtc = DS3231(i2c, 0x68)
oled = SSD1306_I2C(i2c, 0x3C)
# SPI for SD card (needs speed for logging)
spi = SPI(0, baudrate=10000000, sck=Pin(2), mosi=Pin(3), miso=Pin(4))
sd_card = SDCard(spi, cs=Pin(5))
Why: Use I2C where it makes sense (simpler wiring for sensors), SPI where needed (fast SD card writes).
Requirements:
Best choice: I2C
i2c = I2C(0, scl=Pin(9), sda=Pin(8))
# All on same bus
sensors = {
'front': VL53L0X(i2c, 0x29),
'back': VL53L0X(i2c, 0x30),
# ... more sensors
'imu1': MPU6050(i2c, 0x68),
'imu2': MPU6050(i2c, 0x69),
'compass': QMC5883L(i2c, 0x0D)
}
Why: I2C handles many devices easily, tolerates longer wires across chassis, and sensors donβt need high speed.
Requirements:
Best choice: SPI
spi = SPI(0, baudrate=20000000) # 20 MHz
adc = ADS7953(spi, cs=Pin(5)) # 12-bit, 8-channel ADC
# Rapid sampling
for channel in range(8):
value = adc.read_channel(channel) # ~10Β΅s with SPI
process_data(value)
Why: I2C @ 400 kHz couldnβt keep up with 10 kHz sampling rate. SPIβs speed is essential.
Requirements:
Best choice: I2C
i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=100000) # Slow = low power
# All I2C devices in low-power modes
bme280 = BME280(i2c)
bme280.set_mode(MODE_SLEEP) # Wake only when needed
sgp30 = SGP30(i2c)
oled = SSD1306_I2C(i2c)
# Wake, read, sleep cycle
while True:
bme280.set_mode(MODE_FORCED)
temp = bme280.read()
bme280.set_mode(MODE_SLEEP)
time.sleep(60) # Sleep 1 minute
Why: I2Cβs lower power consumption extends battery life significantly.
Many real projects use both protocols:
from machine import I2C, SPI, Pin
# I2C bus for sensors (simple, many devices)
i2c = I2C(0, scl=Pin(9), sda=Pin(8), freq=400000)
temp_sensor = BME280(i2c, 0x76)
rtc = DS3231(i2c, 0x68)
# SPI bus for display and storage (speed critical)
spi = SPI(0, baudrate=40000000, sck=Pin(2), mosi=Pin(3), miso=Pin(4))
display = ILI9341(spi, cs=Pin(5), dc=Pin(6))
sd_card = SDCard(spi, cs=Pin(7))
# Use each protocol for its strengths
while True:
# Read sensors via I2C (slow is fine)
temp = temp_sensor.read()
# Update display via SPI (needs speed)
display.show_temperature(temp)
time.sleep(1)
Start
β
ββ Need >10 devices? ββYesββ> I2C
β No
β β
ββ Need >10 MHz speed? ββYesββ> SPI
β No
β β
ββ Devices >20cm apart? ββYesββ> I2C
β No
β β
ββ Memory/Display device? ββYesββ> SPI
β No
β β
ββ Battery powered? ββYesββ> I2C
β No
β β
ββ Either works! ββββββββββ> I2C (simpler wiring)
Most devices are available in both I2C and SPI versions. Hereβs whatβs typical:
Pro tip: If a device offers both, choose based on your system needs, not the device itself.
Practice protocol selection:
Analyze your current project: Which protocol does it use? Could the other work better?
Which protocol for each component?
# How fast do you really need?
# OLED 128x64 @ 10 FPS
pixels = 128 * 64
bytes = pixels / 8 # Monochrome
fps = 10
bytes_per_sec = bytes * fps
print(f"OLED needs: {bytes_per_sec} bytes/sec")
# Result: 10,240 bytes/sec = 81,920 bits/sec
# I2C @ 400 kHz can handle this!
# TFT 240x320 @ 30 FPS
pixels = 240 * 320
bytes = pixels * 2 # 16-bit color
fps = 30
bytes_per_sec = bytes * fps
print(f"TFT needs: {bytes_per_sec} bytes/sec")
# Result: 4,608,000 bytes/sec = 36,864,000 bits/sec
# Need SPI @ 40+ MHz
In this lesson, you mastered:
You can now confidently choose the right protocol for any project based on technical requirements, not guesswork.
Theory and examples are great, but real projects always have surprises. In the next lesson, weβll cover common issues with both I2C and SPI, how to diagnose them, and proven solutions to get your projects working.
You can use the arrows β β on your keyboard to navigate between lessons.
Comments