SPI (Serial Peripheral Interface) is a synchronous, full-duplex, four-wire serial communication bus originally developed by Motorola. In microcontroller applications, it serves as the primary high-speed backbone for talking to local peripherals like flash memory (W25Q series), TFT displays (ILI9341), and high-resolution ADCs. Unlike asynchronous protocols, SPI uses a shared clock line to keep the master and slave perfectly synchronized, allowing throughput to easily exceed 20 MHz on short PCB traces.
If you are asking what is SPI in microcontroller contexts compared to other buses: it is the undisputed choice when you need to move large blocks of data quickly over short distances, trading pin count for raw bandwidth. Below is a complete breakdown of the physical layer, classic failure modes, and a working ESP32 implementation.
SPI Bus Mechanics and Physical Layer Specs
SPI operates on a master-slave architecture. The master (your microcontroller) generates the clock and initiates all transfers. Because it is full-duplex, data is sent and received simultaneously on every clock edge. According to the All About Circuits SPI guide, the protocol is not strictly standardized by a single governing body like I2C is, which means you must always verify the clock polarity and phase in your specific peripheral's datasheet.
| Parameter | SPI Specification | Practical Notes for Makers |
|---|---|---|
| Wires | 4 shared (SCK, MOSI, MISO) + 1 individual (CS/SS) per slave | MOSI = Master Out Slave In; MISO = Master In Slave Out. |
| Speed | 1 MHz to 50+ MHz | Limited by trace capacitance and slave IC limits. SD cards typically max at 25 MHz in standard SPI mode. |
| Addressing | None (Hardware Chip Select) | Every slave requires its own dedicated CS (Chip Select) GPIO pin from the master. |
| Distance | < 1 meter (typically < 30 cm for >10 MHz) | High-frequency clock edges degrade over long wires due to parasitic capacitance and crosstalk. |
| Duplex | Full-Duplex | Master and slave shift bits simultaneously via internal shift registers. |
Physical Wiring and Pull-Up Requirements
A common misconception is that SPI requires pull-up resistors on the data and clock lines like I2C does. It does not. SPI drivers are push-pull, meaning they actively drive the lines both HIGH and LOW. Adding pull-ups to SCK, MOSI, or MISO will only increase rise/fall times and limit your maximum clock speed.
While data lines don't need pull-ups, the Chip Select (CS) line absolutely does. You must place a 10kΩ pull-up resistor from the CS line to VCC on the slave side. When an ESP32 or Arduino boots, its GPIO pins float before the SPI peripheral initializes. Without a pull-up, the floating CS pin can glitch LOW, causing the slave to latch onto random boot noise and corrupt its internal state machine before your code even starts running.
Wiring the Bus: Pinouts and Classic Failures
When debugging a non-responsive SPI peripheral, the issue almost always traces back to the physical layer or a misunderstanding of the clock modes. Here are the classic failures and how to resolve them.
- The "Address Clash" (CS Overlap): SPI doesn't use software addresses; it uses hardware Chip Select lines. If you wire two slave CS pins to the same master GPIO, or forget to configure a CS pin as an OUTPUT, both slaves will attempt to drive the MISO line simultaneously when selected. This causes a short circuit, resulting in corrupted data and potentially damaging the slave IC's output buffers.
- Missing Pull-Up on MISO (Multi-Slave Buses): If you have multiple devices on one SPI bus, unselected slaves must put their MISO pin into a high-impedance (High-Z) state. If a slave IC is unpowered or lacks proper High-Z tri-state logic, it will drag the MISO line low. In multi-slave setups, a weak 47kΩ pull-up on MISO can sometimes help stabilize the bus, though a proper tri-state buffer (like the 74LVC125) is the correct engineering fix.
- Baud Mismatch and Clock Modes: SPI does not auto-negotiate speed or phase. If your master sends at 8 MHz but the slave's datasheet specifies a 4 MHz maximum, you will read garbage. Furthermore, you must match the CPOL (Clock Polarity) and CPHA (Clock Phase). Mode 0 (CPOL=0, CPHA=0) is standard, but many sensors and SD cards require Mode 3. Sending Mode 0 commands to a Mode 3 device will shift your data by one bit, ruining the exchange.
How to Sniff and Debug the Bus
Do not rely on a standard multimeter for SPI debugging; the clock toggles too fast. You need a logic analyzer. A Saleae Logic Analyzer (or a $15 FX2LP clone running the open-source Sigrok/PulseView software) is mandatory. Connect the probes to SCK, MOSI, MISO, and CS. Set the sample rate to at least 4x your SPI clock speed (e.g., 40 MS/s for a 10 MHz bus). Use the software's SPI decoder to verify that the MOSI hex bytes match your code, and check if the slave is actually pulling MISO low to respond.
Minimal Working Exchange: ESP32 to SPI Flash
Below is a complete, copy-pasteable example of reading the JEDEC Manufacturer ID from a ubiquitous W25Q32 SPI flash chip using an ESP32-WROOM-32 DevKit V1.
| ESP32 GPIO | W25Q32 Pin | Function |
|---|---|---|
| GPIO 18 | Pin 6 (CLK) | SCK (Clock) |
| GPIO 23 | Pin 5 (DI) | MOSI (Master Out) |
| GPIO 19 | Pin 2 (DO) | MISO (Master In) |
| GPIO 5 | Pin 1 (CS) | Chip Select (Add 10k pull-up to 3.3V) |
| 3.3V | Pin 8 (VCC), Pin 3 (/WP), Pin 7 (/HOLD) | Power and tie control pins high |
| GND | Pin 4 (GND) | Common Ground |
#include <SPI.h>
// Pin definitions for ESP32 DevKit V1
#define CS_PIN 5
#define SPI_CLK_SPEED 4000000 // 4 MHz safe speed for breadboards
void setup() {
Serial.begin(115200);
pinMode(CS_PIN, OUTPUT);
digitalWrite(CS_PIN, HIGH); // Deselect slave immediately
// Initialize VSPI bus (Default for ESP32 Arduino core)
SPI.begin(18, 19, 23, CS_PIN); // SCK, MISO, MOSI, CS
Serial.println("SPI Initialized. Reading JEDEC ID...");
}
void loop() {
// Command 0x9F reads the JEDEC Manufacturer and Device ID
uint8_t tx_buffer[4] = {0x9F, 0x00, 0x00, 0x00};
uint8_t rx_buffer[4] = {0};
SPI.beginTransaction(SPISettings(SPI_CLK_SPEED, MSBFIRST, SPI_MODE0));
digitalWrite(CS_PIN, LOW); // Assert Chip Select
for (int i = 0; i < 4; i++) {
rx_buffer[i] = SPI.transfer(tx_buffer[i]);
}
digitalWrite(CS_PIN, HIGH); // Deassert Chip Select
SPI.endTransaction();
Serial.printf("Manufacturer ID: 0x%02X\n", rx_buffer[1]);
Serial.printf("Memory Type: 0x%02X\n", rx_buffer[2]);
Serial.printf("Capacity: 0x%02X\n", rx_buffer[3]);
delay(2000); // Wait 2 seconds before next read
}
Protocol Selection: When to Use SPI vs. I2C vs. UART
Choosing the right protocol depends entirely on your constraints regarding distance, speed, and device count. Here is the decision framework:
| Criteria | SPI | I2C | UART |
|---|---|---|---|
| Best For | High-speed local data (Displays, Flash, ADCs) | Low-speed sensor arrays, saving GPIO pins | Point-to-point comms, GPS modules, PC logging |
| Speed | Very High (10 - 50+ MHz) | Low/Med (100 kHz, 400 kHz, 3.4 MHz) | Medium (9600 bps to 3 Mbps) |
| Device Count | Limited by available CS GPIO pins | Up to 127 via software addressing | 1-to-1 (Requires multiplexers for more) |
| Distance Limit | < 1 meter (Strict) | < 1 meter (Capacitance limited) | ~15 meters (at lower baud rates) |
| Wiring Complexity | 4 shared + 1 per device | 2 shared wires total | 2 wires (TX/RX) per pair |
Choose SPI when: You are moving bulk data (like rendering graphics to an ILI9341 TFT screen or logging data to an SD card) and have plenty of GPIO pins available.
Choose I2C when: You are reading slow environmental sensors (BME280, MPU6050) and want to minimize wiring and pin count.
Choose UART when: You need to communicate with a PC serial monitor, a GPS receiver, or an ESP8266 AT-command module over slightly longer distances.
Frequently Asked Questions
What is SPI in microcontroller used for compared to I2C?
SPI is used when bandwidth is the primary bottleneck. While I2C is excellent for reading a temperature sensor every second, it is far too slow to stream raw pixel data to a 320x240 LCD display or write continuous audio samples to a DAC. SPI's push-pull architecture and lack of software addressing overhead allow it to push megabytes of data per second, making it the standard for high-throughput local peripherals.
How do I add multiple SPI devices to one microcontroller bus?
You share the SCK, MOSI, and MISO lines across all devices, but each device must have its own dedicated Chip Select (CS) wire routed to a unique GPIO pin on the microcontroller. To talk to Device A, you pull Device A's CS LOW while keeping Device B's CS HIGH. Ensure every CS line has a 10kΩ pull-up resistor to prevent bus contention during microcontroller resets.
Why is my SPI MISO line returning all zeros or 0xFF?
If you read 0x00 or 0xFF continuously, the master is not seeing the slave's response. This usually means: 1) The slave is unpowered or in reset. 2) You have the wrong SPI Mode (CPOL/CPHA), causing the master to sample the MISO line on the wrong clock edge. 3) The slave requires a specific "wake up" command or hardware reset pin toggle before it will respond to SPI clocks. Hook up a logic analyzer to verify if the slave is actually pulling the MISO line low during the transaction.
Can SPI communication work over long distances like RS-485?
No. Standard SPI is single-ended and highly susceptible to noise, ground loops, and parasitic capacitance, limiting it to roughly 30cm-1m at high speeds. If you need SPI-like speeds over long distances (e.g., 10+ meters to an outdoor sensor), you must use differential line drivers (like RS-422) to convert the SPI signals, or switch to an industrial protocol like CAN bus or RS-485 with a protocol conversion bridge at the remote end.






