SPI (Serial Peripheral Interface) is the go-to synchronous protocol for high-speed, short-distance chip-to-chip communication. Unlike I2C, it lacks a formal software addressing scheme, relying instead on individual Chip Select (CS) lines. If you are integrating high-throughput sensors, TFT displays, or external flash memory, SPI is usually your best option. However, its simplicity at the software level masks physical layer traps that routinely brick communication on the bench. This guide covers the exact physical wiring, pull-up nuances, and debugging workflows you need to get your bus running reliably.
The Physical Layer: Wiring, Pull-Ups, and Bus Mechanics
Before writing a single line of code, you must understand the physical constraints of the SPI bus. It is a four-wire, full-duplex, synchronous interface. Modern engineering bodies (including OSHA and the IEEE) are transitioning from the legacy MOSI/MISO naming to COPI/CIPO to use more inclusive terminology, though legacy datasheets still dominate.
| Parameter | SPI Standard | Notes & Constraints |
|---|---|---|
| Wires Required | 4 shared + 1 per device (CS) | SCK, COPI (MOSI), CIPO (MISO) shared. CS is individual. |
| Max Speed | 10 MHz to 80+ MHz | Highly dependent on trace length and capacitive load. 80MHz requires PCB-level routing, not breadboards. |
| Addressing | Hardware (Chip Select) | No software addresses. Each target needs a dedicated MCU GPIO for CS. |
| Max Distance | < 1 meter (typically < 30cm) | Signal integrity degrades rapidly due to high clock edge rates. Use RS-422 transceivers for longer runs. |
| Duplex Mode | Full-Duplex | Data shifts in and out simultaneously on COPI and CIPO lines. |
The Pull-Up Trap
The most common mistake hobbyists make is treating SPI like I2C and placing 4.7kΩ pull-up resistors on the clock and data lines. Do not do this. SPI is a push-pull architecture, not open-drain. Pull-ups on SCK or COPI will fight the MCU's GPIO drivers, causing excessive current draw and rounded signal edges.
However, you must use pull-ups in two specific scenarios:
- Chip Select (CS) Lines: Place a 10kΩ pull-up to VCC on every CS line. During MCU boot or reset, GPIOs float. Without a pull-up, a floating CS pin can accidentally activate a peripheral, causing bus contention or corrupting external flash memory.
- CIPO (MISO) with Unpowered Devices: If you have multiple devices on the same CIPO line and one device is powered down, its internal ESD diodes can leak current onto the bus, pulling the line high. A 10kΩ pull-down on CIPO (or a dedicated tri-state buffer like the 74LVC125A) prevents this ghosting.
Protocol Selection: When SPI Beats I2C and UART
Choosing the right protocol comes down to balancing distance, speed, and device count. Here is how SPI stacks up against the other embedded heavyweights.
| Criteria | SPI | I2C | UART |
|---|---|---|---|
| Best Use Case | High-speed, single-board data streaming (displays, ADCs) | Low-speed sensor polling, multi-device configuration | Point-to-point debugging, GPS modules, long-distance (RS-485) |
| Speed | Very High (10-80 MHz) | Low/Med (100 kHz - 3.4 MHz) | Medium (9600 bps - 3 Mbps) |
| Device Count | Low (Limited by MCU GPIO pins for CS) | High (Up to 127 via software addressing) | 1-to-1 (Requires multiplexers for more) |
| Wiring Complexity | High (4 shared + N chip selects) | Low (2 shared wires for all devices) | Lowest (2 wires: TX/RX) |
The Verdict: Choose SPI when you need raw bandwidth (e.g., driving an ILI9341 TFT display or reading a 24-bit ADC). Choose I2C when you have 10 low-speed environmental sensors and only have two GPIO pins available. Choose UART for off-board communication or when interacting with modems and GPS receivers.
Minimal Working Exchange: Hardware Map and Code
Below is a complete, minimal working exchange using an ESP32 and a BMP280 SPI pressure sensor. This demonstrates explicit pin mapping and proper SPI transaction handling.
| ESP32 GPIO (VSPI Default) | BMP280 SPI Pin | Function |
|---|---|---|
| GPIO 18 | SCK | Serial Clock |
| GPIO 23 | SDI (MOSI) | Master Out, Slave In |
| GPIO 19 | SDO (MISO) | Master In, Slave Out |
| GPIO 5 | CSB | Chip Select (Active LOW) |
| 3V3 | VCC & SDO (if not using MISO) | Power & I2C disable |
Note: The BMP280 has an I2C/SPI mode select pin (CSB). Ensure it is pulled to VCC via a 10k resistor if you want it to default to I2C, but for SPI, the CS line handles activation. Always tie the unused SDO pin appropriately per the Bosch BMP280 datasheet.
#include <SPI.h>
#include <Adafruit_BMP280.h>
// Explicit pin definitions for ESP32 VSPI
#define BMP_SCK 18
#define BMP_MISO 19
#define BMP_MOSI 23
#define BMP_CS 5
Adafruit_BMP280 bmp(BMP_CS, BMP_MOSI, BMP_MISO, BMP_SCK);
void setup() {
Serial.begin(115200);
while(!Serial); // Wait for serial monitor
// Initialize hardware SPI with explicit settings
if (!bmp.begin()) {
Serial.println("Could not find a valid BMP280 sensor, check wiring!");
while (1) delay(10);
}
// Configure sensor sampling
bmp.setSampling(Adafruit_BMP280::MODE_NORMAL,
Adafruit_BMP280::SAMPLING_X2, // Temp oversampling
Adafruit_BMP280::SAMPLING_X16, // Pressure oversampling
Adafruit_BMP280::FILTER_X16,
Adafruit_BMP280::STANDBY_MS_500);
}
void loop() {
Serial.print("Temperature = ");
Serial.print(bmp.readTemperature());
Serial.println(" *C");
Serial.print("Pressure = ");
Serial.print(bmp.readPressure() / 100.0F); // Convert Pa to hPa
Serial.println(" hPa");
delay(2000);
}
Debugging the Bus: Sniffing and Classic Failures
When your SPI device returns garbage data or hangs the MCU, guessing is a waste of time. You need to sniff the bus. Use a logic analyzer like the Saleae Logic Pro 8 or a modern oscilloscope with serial decoding (like the Siglent SDS1204X-E). Hook up all four lines, trigger on the falling edge of CS, and decode the hex bytes.
The Classic Failures
- Baud and Mode Mismatch (CPOL/CPHA): SPI defines four modes (0 through 3) based on Clock Polarity (CPOL) and Clock Phase (CPHA). Mode 0 (CPOL=0, CPHA=0) is the most common, meaning the clock idles LOW and data is sampled on the rising edge. If your MCU is set to Mode 0 but the sensor expects Mode 3, every byte will be shifted by one bit, resulting in complete data corruption. Always check the sensor datasheet's timing diagram.
- The "Address Clash" (CS Contention): Because SPI lacks software addressing, an "address clash" means you wired two devices to the same CS pin, or you forgot to drive the CS pin HIGH on an inactive device before talking to a second device. If two devices drive the CIPO (MISO) line simultaneously, you create a short circuit between their output drivers, which can permanently silicon-damage the sensors.
- Missing Pull-Up Glitching: If your peripheral initializes randomly after a power cycle, check your CS line on a scope during the MCU boot sequence. The ESP32 and Arduino bootloaders toggle GPIOs rapidly before your
setup()function runs. Without a 10kΩ hardware pull-up on CS, the peripheral sees these toggles as valid clock/select commands and enters an undefined state.
SPI Guidelines FAQ
Can I use SPI over long distances like RS-485?
Standard CMOS SPI is strictly for short distances (under 30cm on a breadboard, up to 1 meter on a well-designed PCB). The high-frequency clock edges suffer from capacitive loading and reflections over long wires. If you must run SPI over several meters (e.g., to a remote weather station), you cannot use raw GPIO pins. You must use differential line drivers like the MAX490 (RS-422) to convert the single-ended SPI signals into differential pairs, which reject common-mode noise and preserve signal integrity over long cable runs.
What happens if I connect two SPI masters to the same bus?
Standard SPI does not support multi-master arbitration like I2C does. If two microcontrollers attempt to drive the SCK and COPI lines simultaneously, you will cause a bus collision, potentially damaging the GPIO drivers due to shoot-through current. If you absolutely need two masters to access the same SPI peripheral (e.g., an ESP32 and a Raspberry Pi sharing an external SPI flash chip), you must use an external hardware multiplexer or tri-state buffers (like the 74HC125) controlled by a dedicated "bus request" handshake line to ensure only one master drives the bus at a time.
Why is my SPI device returning all 0xFF or 0x00?
Reading all 0xFF usually means the CIPO (MISO) line is floating or being pulled high, and the peripheral is not actually driving the bus. This happens if the CS line is not being pulled LOW correctly, or if the peripheral is unpowered. Conversely, reading all 0x00 typically means the CIPO line is being shorted to ground, or you are sampling the data on the wrong clock edge (CPHA mismatch). Hook a logic analyzer to the CS and CIPO lines; if CS goes LOW but CIPO remains flat, the peripheral is either dead, unpowered, or wired to the wrong MISO pin.






