SPI (Serial Peripheral Interface) is a high-speed, full-duplex serial protocol used when you need to move data quickly — driving TFT displays, reading SD cards, or communicating with fast sensors. It trades simplicity for speed: SPI needs more wires than I2C but can clock data at up to 80 MHz on the ESP32.
🔗How SPI Works
SPI is a master-slave protocol. The ESP32 is the master, and each peripheral is a slave. Unlike I2C (which uses addresses), SPI selects devices with a dedicated CS (Chip Select) line per device. Pull a device's CS line LOW to talk to it; all other devices ignore the bus.
SPI uses four signals:
| Signal | Other Names | Direction | Purpose |
|---|---|---|---|
| MOSI | SDI, DIN, DI | Master -> Slave | Data from ESP32 to device |
| MISO | SDO, DOUT, DO | Slave -> Master | Data from device to ESP32 |
| SCK | SCLK, CLK | Master -> Slave | Clock signal |
| CS | SS, CE, CSN | Master -> Slave | Chip Select — one per device |
SPI is full duplex: the master sends a byte on MOSI and simultaneously receives a byte on MISO during the same clock cycles. Even if you only want to read, you still send something (usually 0x00), and vice versa.
graph TB
ESP32["ESP32<br/>(Master)"]
subgraph Shared Lines
MOSI["MOSI"]
MISO["MISO"]
SCK["SCK"]
end
DEV1["Device A<br/>(e.g., TFT Display)"]
DEV2["Device B<br/>(e.g., SD Card)"]
DEV3["Device C<br/>(e.g., MAX7219)"]
ESP32 --- MOSI
ESP32 --- MISO
ESP32 --- SCK
MOSI --- DEV1
MOSI --- DEV2
MOSI --- DEV3
MISO --- DEV1
MISO --- DEV2
MISO --- DEV3
SCK --- DEV1
SCK --- DEV2
SCK --- DEV3
ESP32 -- "CS_A" --- DEV1
ESP32 -- "CS_B" --- DEV2
ESP32 -- "CS_C" --- DEV3MOSI, MISO, and SCK are shared. Each device gets its own CS line. When $\text{CS} = \text{LOW}$, that device is selected.
🔗ESP32 SPI Buses
The ESP32 has four SPI controllers, but only two are available for general use:
| Bus | MOSI | MISO | SCK | Default CS | Notes |
|---|---|---|---|---|---|
| VSPI | GPIO 23 | GPIO 19 | GPIO 18 | GPIO 5 | Default bus used by SPI.begin() |
| HSPI | GPIO 13 | GPIO 12 | GPIO 14 | GPIO 15 | Second bus, must be configured manually |
| SPI0 | — | — | — | — | Reserved for internal flash (do not use) |
| SPI1 | — | — | — | — | Reserved for internal flash (do not use) |
Important: SPI0 and SPI1 are used by the ESP32's flash memory. Never configure your peripherals on these buses.
🔗Using HSPI
If you need a second SPI bus (or VSPI pins are occupied), configure HSPI manually:
#include <SPI.h>
SPIClass hspi(HSPI);
void setup() {
hspi.begin(14, 12, 13, 15); // SCK, MISO, MOSI, CS
}Like I2C, the GPIO matrix lets you remap SPI pins to almost any available GPIO. Just avoid GPIOs 6--11 (flash) and 34--39 (input-only).
🔗SPI Modes
SPI has four modes defined by two parameters:
- CPOL (Clock Polarity) — the idle state of the clock line (LOW or HIGH)
- CPHA (Clock Phase) — whether data is sampled on the rising or falling clock edge
| Mode | CPOL | CPHA | Clock Idle State | Data Sampled On |
|---|---|---|---|---|
| SPI_MODE0 | 0 | 0 | LOW | Rising edge |
| SPI_MODE1 | 0 | 1 | LOW | Falling edge |
| SPI_MODE2 | 1 | 0 | HIGH | Falling edge |
| SPI_MODE3 | 1 | 1 | HIGH | Rising edge |
SPI_MODE0 is the most common. Most sensors and displays default to mode 0. Always check the device datasheet if communication fails — using the wrong mode will produce garbage data or no response at all.
🔗Clock Speed
The ESP32 supports SPI clock speeds up to 80 MHz, but practical limits depend on the device and wiring:
| Speed | Typical Use |
|---|---|
| 1 MHz | Safe default for testing |
| 10 MHz | Most sensors and modules |
| 20--40 MHz | TFT displays, SD cards |
| 80 MHz | Maximum, only for short traces on a PCB |
Longer wires and breadboard connections add capacitance, which limits the maximum reliable clock speed. On a breadboard, staying at or below 10 MHz is a good rule of thumb.
The clock frequency you request is rounded down to the nearest divisor of 80 MHz. For example, requesting 15 MHz gives you $80 / 6 \approx 13.3\,\text{MHz}$.
🔗Code Example
The standard Arduino SPI transaction pattern — this is what libraries like TFT_eSPI and SD.h do internally:
#include <SPI.h>
#define CS_PIN 5
void setup() {
Serial.begin(115200);
SPI.begin(); // Init VSPI: SCK=18, MISO=19, MOSI=23
pinMode(CS_PIN, OUTPUT);
digitalWrite(CS_PIN, HIGH); // Deselect device
}
byte spiTransfer(byte reg) {
byte result;
digitalWrite(CS_PIN, LOW); // 1. Select device
SPI.beginTransaction(SPISettings(1000000, MSBFIRST, SPI_MODE0)); // 2. Configure bus
SPI.transfer(reg); // 3. Send register address
result = SPI.transfer(0x00); // 4. Read response
SPI.endTransaction(); // 5. Release bus settings
digitalWrite(CS_PIN, HIGH); // 6. Deselect device
return result;
}
void loop() {
byte value = spiTransfer(0x0F); // Example: read WHO_AM_I register
Serial.printf("Register 0x0F = 0x%02X\n", value);
delay(1000);
}In practice, you rarely write raw SPI code. Device libraries handle the protocol details — you just need to know which pins to pass to the library's constructor.
🔗SPI vs I2C — When to Choose SPI
| Factor | SPI | I2C |
|---|---|---|
| Speed | Up to 80 MHz | Up to 400 kHz |
| Wires | 3 + 1 per device | 2 (shared) |
| Pin usage | Higher (extra CS per device) | Lower |
| Complexity | Medium | Low |
| Best for | Displays, SD cards, fast ADCs | Sensors, small OLEDs |
If your device supports both protocols, choose I2C for simplicity or SPI for speed.
🔗Common Issues
| Problem | Cause | Fix |
|---|---|---|
| No response from device | CS pin not configured as OUTPUT or not pulled LOW | Add pinMode(CS_PIN, OUTPUT) and digitalWrite(CS_PIN, LOW) before transfers |
| Garbage data | Wrong SPI mode | Check the datasheet for CPOL/CPHA and set the correct SPI_MODE |
| HSPI not working | Using VSPI pins with HSPI object | Make sure you pass the correct HSPI pin numbers to begin() |
| Intermittent failures | Clock too fast for breadboard wiring | Reduce clock speed to 1--4 MHz for testing |
| Conflicts with flash | Using GPIOs 6--11 | These pins are reserved for the ESP32's internal flash — choose other pins |
| Two devices interfering | Shared CS or floating CS line | Every device needs its own CS pin; pull unused CS lines HIGH |
🔗Used In
These articles on this site use SPI:
- MAX7219 LED Matrix Display
- WS2812B NeoPixel LEDs (timing-sensitive protocol, not traditional SPI but often compared)