What SPI Stands For and How the Bus Actually Works
SPI stands for Serial Peripheral Interface. Originally developed by Motorola in the 1980s, it is a synchronous, full-duplex, four-wire communication bus used to connect microcontrollers to high-speed peripherals like flash memory, displays, and ADCs. Unlike asynchronous protocols, SPI relies on a shared clock signal to shift data in and out simultaneously, making it significantly faster than I2C or UART for short-distance board-level communication.
To understand where SPI fits on your workbench, here are the core mechanical specifications of the bus:
| Parameter | SPI Specification | Practical Workbench Reality |
|---|---|---|
| Wires | 4 (MOSI, MISO, SCK, CS) | Requires 4 shared lines plus individual CS for every target. |
| Speed | Up to 80 MHz (MCU dependent) | Peripherals usually max out at 10–20 MHz; trace capacitance limits real speed. |
| Addressing | None (Hardware Chip Select) | No 7-bit software addresses; routing scales poorly with many devices. |
| Distance | < 1 Meter (theoretical) | Reliably works up to ~30 cm on breadboards; >1 m requires RS-422 differential drivers. |
| Duplex | Full-Duplex | Master sends and receives on the exact same clock cycle. |
Physical Wiring, Pull-Ups, and the Classic Failures
When makers talk about the 'classic failures' in serial buses, they usually list address clashes, missing pull-ups, and baud mismatches. Here is how those translate to the physical layer of SPI, according to Analog Devices' interface guidelines:
- Baud & Mode Mismatch: SPI doesn't just have a baud rate; it has Clock Polarity (CPOL) and Clock Phase (CPHA). If your master is set to Mode 0 (CPOL=0, CPHA=0) but the sensor expects Mode 3, you will read complete garbage. Always check the peripheral datasheet timing diagram.
- Missing Pull-Up on CS: Unlike I2C, SPI data and clock lines are push-pull and do not need pull-up resistors. However, the Chip Select (CS) line must have a 10kΩ pull-up to VCC. If CS floats during MCU boot, the peripheral will interpret boot-time pin toggling as valid clock edges and corrupt its internal state machine.
- Address Clash vs. CS Clash: SPI does not use software addressing, so you cannot have an 'address clash' like in I2C. The SPI equivalent is a Chip Select routing clash—accidentally wiring two peripherals to the same CS pin, or forgetting to assert CS low before clocking data.
A note on ESP32 strapping pins: If you are wiring SPI on an ESP32, never use GPIO 12 for MISO if you have a pull-up on it. GPIO 12 is a strapping pin that sets the flash voltage; pulling it high on boot will cause the ESP32 to brownout or fail to boot.
Protocol Selection: Distance, Speed, and Device Count
Which protocol fits your project? Use this decision matrix to choose between SPI, I2C, and UART based on your physical constraints.
| Criteria | SPI | I2C | UART |
|---|---|---|---|
| Best For | High-speed, short-distance, single-master to few peripherals (Flash, TFT LCDs). | Low-speed, many sensors on the same bus, minimal wiring. | Point-to-point off-board communication, GPS modules, debugging. |
| Device Count | Low (1 CS pin per device eats up GPIOs fast). | High (up to 127 devices on 2 wires). | Very Low (1-to-1 without multiplexers). |
| Wiring Complexity | High (4 shared + N chip selects). | Low (2 shared wires). | Lowest (2 wires, TX/RX). |
Minimal Working Exchange: ESP32 to SPI Flash
Below is a complete, copy-pasteable example for the Arduino IDE. We will read the JEDEC Manufacturer ID from a Winbond W25Q32 SPI flash chip using an ESP32. This demonstrates proper transaction handling and pin definitions.
Wiring Table
| ESP32 Pin | W25Q32 Pin | Function |
|---|---|---|
| GPIO 23 | DI (Pin 5) | MOSI (Master Out, Slave In) |
| GPIO 19 | DO (Pin 2) | MISO (Master In, Slave Out) |
| GPIO 18 | CLK (Pin 6) | SCK (Clock) |
| GPIO 5 | CS (Pin 1) | Chip Select (Active LOW) |
| 3.3V | VCC (Pin 8) | Power |
| GND | GND (Pin 4) | Ground |
Arduino Code
#include <SPI.h>
// Define ESP32 SPI pins explicitly to avoid strapping pin issues
#define CS_PIN 5
#define MOSI_PIN 23
#define MISO_PIN 19
#define SCK_PIN 18
void setup() {
Serial.begin(115200);
delay(1000);
// Initialize SPI bus with custom pins
SPI.begin(SCK_PIN, MISO_PIN, MOSI_PIN, CS_PIN);
pinMode(CS_PIN, OUTPUT);
digitalWrite(CS_PIN, HIGH); // Deselect flash
Serial.println('Booted. Reading W25Q32 JEDEC ID...');
}
void loop() {
uint8_t manufacturer_id = 0;
uint8_t memory_type = 0;
uint8_t capacity = 0;
// Begin transaction: 20MHz, MSB first, SPI Mode 0 (CPOL=0, CPHA=0)
SPI.beginTransaction(SPISettings(20000000, MSBFIRST, SPI_MODE0));
digitalWrite(CS_PIN, LOW); // Assert Chip Select
SPI.transfer(0x9F); // JEDEC ID command
manufacturer_id = SPI.transfer(0x00);
memory_type = SPI.transfer(0x00);
capacity = SPI.transfer(0x00);
digitalWrite(CS_PIN, HIGH); // Deassert Chip Select
SPI.endTransaction();
Serial.printf('Manufacturer: 0x%02X (Winbond is 0xEF)\n', manufacturer_id);
Serial.printf('Memory Type: 0x%02X\n', memory_type);
Serial.printf('Capacity: 0x%02X (0x15 = 32Mbit)\n', capacity);
delay(5000); // Read every 5 seconds
}
Sniffing and Debugging the SPI Bus
Because SPI is synchronous and full-duplex, debugging it with a standard multimeter is useless. You need to see the timing relationship between the Clock (SCK) and the data lines. For this, a logic analyzer is mandatory.
Tools like the Saleae Logic Pro 8 or the budget-friendly DreamSourceLab DSLogic U3Pro16 are industry standards for bench debugging. When sniffing SPI, configure your analyzer software to decode the 'SPI' protocol, set the correct CPOL/CPHA mode, and trigger on the falling edge of the CS line.
For deeper electrical issues, such as signal ringing or slow rise times caused by excessive bus capacitance, you will need a digital storage oscilloscope (DSO) to inspect the analog shape of the SCK square wave. If the edges look like shark fins, lower your baud rate or shorten your jumper wires.
Frequently Asked Questions About SPI
What does SPI stand for in Arduino and Raspberry Pi?
In both Arduino and Raspberry Pi ecosystems, SPI stands for Serial Peripheral Interface. On the Raspberry Pi, it is exposed on the 40-pin GPIO header (typically SPI0 on pins 19, 21, 23, and 24) and is managed by the Linux spidev driver. On Arduino, it is accessed via the built-in SPI.h library, which abstracts the hardware registers of the ATmega or ARM microcontroller.
Does SPI need pull-up resistors like I2C?
No, the MOSI, MISO, and SCK lines on an SPI bus are driven by push-pull outputs, meaning they actively drive both HIGH and LOW. They do not require pull-up resistors. However, the Chip Select (CS) line should have a 10kΩ pull-up resistor to VCC to keep the peripheral deselected while the microcontroller is booting and its GPIO pins are floating.
What is the maximum distance for an SPI bus?
SPI is designed for on-board communication. At standard speeds (10-20 MHz), reliable communication is limited to about 30 cm (12 inches) using standard ribbon cables or breadboard wires due to parasitic capacitance and crosstalk. If you must run SPI over longer distances (up to several meters), you must drastically lower the clock speed (e.g., to 100 kHz) or use differential line drivers like the MAX485 to convert the signals to RS-422 levels.
How do I connect multiple SPI devices to one ESP32?
You share the MOSI, MISO, and SCK lines among all devices, but each device must have its own dedicated Chip Select (CS) pin connected to a unique GPIO on the ESP32. If you run out of GPIO pins, you can use a 3-to-8 line decoder (like the 74HC138) to expand your CS lines, or daisy-chain devices that support shift-register cascading (like WS2812-style SPI LED drivers), though daisy-chaining requires sending dummy bytes to push data through the chain.






