When a new sensor or memory chip fails to read on the workbench, the culprit is rarely a broken wire. More often than not, the microcontroller and the peripheral are speaking the same language, but with a different accent. In the Serial Peripheral Interface (SPI) protocol, this accent is defined by the SPI Mode. Understanding how Clock Polarity (CPOL) and Clock Phase (CPHA) interact is the difference between a clean data read and a frustrating string of 0xFF or 0x00 returns.
This primer strips away the abstract theory and dives straight into the physical layer, the exact timing matrices, and the bench-level debugging techniques you need to get your SPI bus running reliably.
The SPI Bus Mechanics and Physical Layer
Unlike asynchronous protocols, SPI is a synchronous, full-duplex master-slave architecture. Before writing a single line of code, you must understand the physical constraints of the bus. SPI relies on push-pull output drivers, meaning it actively drives lines high and low. This fundamentally changes how you wire it compared to open-drain buses.
| Parameter | SPI Specification | Practical Bench Notes |
|---|---|---|
| Wires | 4 shared (SCK, MOSI, MISO, GND) + 1 dedicated CS per slave | CS (Chip Select) is active-low. Never share a CS line between two different ICs unless you are deliberately multiplexing. |
| Speed | 1 MHz to 80 MHz (ESP32 max) | Start debugging at 1 MHz. Only push to 20+ MHz if your PCB traces are short and impedance-matched. |
| Addressing | None (Hardware routed via CS lines) | Requires one GPIO pin per slave. Use a 74HC138 decoder if you run out of MCU pins. |
| Distance | < 1 meter (unbuffered) | High-frequency clock edges degrade over long wires due to parasitic capacitance. Use RS-422 differential buffers for longer runs. |
Decoding SPI Mode: CPOL, CPHA, and the 4 Clock Modes
The term "SPI Mode" refers to one of four distinct timing configurations determined by two bits: CPOL (Clock Polarity) and CPHA (Clock Phase). If your microcontroller is set to Mode 0 and your sensor expects Mode 3, the data will be sampled on the wrong clock edge, resulting in bit-shifted or entirely corrupted payloads.
- CPOL (Clock Polarity): Dictates the idle state of the clock line. CPOL=0 means SCK idles LOW. CPOL=1 means SCK idles HIGH.
- CPHA (Clock Phase): Dictates which edge the data is sampled on. CPHA=0 samples on the leading (first) edge. CPHA=1 samples on the trailing (second) edge.
| SPI Mode | CPOL | CPHA | Clock Idle State | Data Sampled On | Common Example ICs |
|---|---|---|---|---|---|
| Mode 0 | 0 | 0 | Low | Rising Edge (Leading) | MAX7219, BMP280, MCP3008 ADC |
| Mode 1 | 0 | 1 | Low | Falling Edge (Trailing) | MAX31855, TI DAC8551 |
| Mode 2 | 1 | 0 | High | Falling Edge (Leading) | ADXL345 (Alt config), specific TI ADCs |
| Mode 3 | 1 | 1 | High | Rising Edge (Trailing) | W25Q128 Flash, ADXL345, MPU-9250 |
According to the Analog Devices ADXL345 datasheet, this specific accelerometer supports both Mode 0 and Mode 3, but many flash memory chips like the Winbond W25Q128 strictly require Mode 3. Always check the "Timing Characteristics" section of your target IC's datasheet for the exact CPOL/CPHA requirement.
Classic Bus Failures: Protocol Showdown and Debugging
Choosing the right protocol depends entirely on your system constraints. If you need high speed (>10 MHz) and point-to-point connections over short distances (<1m), SPI is the undisputed winner. If you need to connect 50+ low-speed sensors on just two wires, I2C fits the bill. If you need long-distance asynchronous communication between distinct systems, UART (often buffered via RS-485) is the correct choice.
Every protocol has its classic failure mode, and misdiagnosing them is a rite of passage for embedded engineers:
- The I2C Classics: The missing pull-up resistor that leaves SDA/SCL floating and hangs the bus, or the address clash when two sensors share the same hardcoded 7-bit ID and refuse to ACK.
- The UART Classic: The baud mismatch (e.g., transmitting at 115200 while the receiver listens at 9600), which turns readable ASCII telemetry into garbage characters.
- The SPI Classic: The SPI Mode mismatch. Because SPI has no ACK bit and no addressing, a Mode 0 master talking to a Mode 3 slave won't throw an error flag. It will simply shift the data by one bit or return all zeros, leading the developer to falsely assume the chip is dead or the wiring is wrong. A secondary SPI classic is MISO contention, which happens when multiple slaves are wired to the same MISO line but lack tri-state outputs, causing a short circuit when one drives high and the other drives low.
How to Sniff and Debug the SPI Bus
When your code returns 0xFF, stop guessing and hook up a logic analyzer. A basic $15 24MHz 8-channel clone analyzer running PulseView/sigrok is sufficient for 90% of SPI debugging.
- Probe all 4 lines: Clip onto SCK, MOSI, MISO, and the specific CS line for your target device.
- Set the Decoder: In PulseView, add the SPI decoder. Set the clock polarity and phase to match your microcontroller's configuration.
- Verify CS Toggle: Ensure CS goes LOW before the first clock pulse and stays LOW for the entire transaction. If CS bounces, your slave will reset its internal state machine mid-byte.
- Check MISO Tri-state: When CS is HIGH, look at the MISO line. It should be floating (or pulled to a default state). If it is actively driving high/low while CS is high, your slave IC is faulty or misconfigured.
Minimal Working Exchange: ESP32 to ADXL345
Below is a bare-metal SPI transaction using the ESP32 Arduino core. We are using the hardware SPI bus to read the Device ID register (0x00) of an ADXL345 accelerometer. The ADXL345 defaults to SPI Mode 3 when the CS line is routed properly.
| ESP32 GPIO | ADXL345 Pin | Function |
|---|---|---|
| GPIO 18 | SCL (SCK) | SPI Clock |
| GPIO 23 | SDA (MOSI) | Master Out, Slave In |
| GPIO 19 | SDO (MISO) | Master In, Slave Out |
| GPIO 5 | CS | Chip Select (Active Low) |
| GND | GND | Common Ground |
| 3V3 | VCC | Power (Do not use 5V!) |
#include <SPI.h>
// ESP32 Hardware SPI pins are fixed: SCK=18, MISO=19, MOSI=23
const int CS_PIN = 5;
void setup() {
Serial.begin(115200);
delay(1000); // Allow serial monitor to connect
pinMode(CS_PIN, OUTPUT);
digitalWrite(CS_PIN, HIGH); // Deselect slave immediately
// Initialize the hardware SPI bus
SPI.begin();
Serial.println("SPI Bus Initialized. Reading ADXL345 DEVID...");
}
void loop() {
// The ADXL345 requires SPI_MODE3 (CPOL=1, CPHA=1)
// We set clock to 1MHz for safe bench debugging
SPISettings spiSettings(1000000, MSBFIRST, SPI_MODE3);
SPI.beginTransaction(spiSettings);
digitalWrite(CS_PIN, LOW); // Assert Chip Select
// To read a register on ADXL345, we must send the register address
// with the MSB set to 1 (Read bit) and bit 6 set to 0.
// Register 0x00 (DEVID) -> Read command = 0x80
SPI.transfer(0x80);
// The second byte sent clocks out the actual data from the slave
uint8_t deviceID = SPI.transfer(0x00);
digitalWrite(CS_PIN, HIGH); // Deassert Chip Select
SPI.endTransaction();
// The ADXL345 DEVID register should always return 0xE5 (229 in decimal)
if (deviceID == 0xE5) {
Serial.printf("Success! Read DEVID: 0x%02X\n", deviceID);
} else {
Serial.printf("Failure. Expected 0xE5, got 0x%02X. Check SPI Mode and wiring.\n", deviceID);
}
delay(2000);
}
By explicitly defining SPI_MODE3 inside the SPISettings object, the ESP32 SPI Master Driver automatically configures the internal GPIO matrix to idle the clock HIGH and sample on the rising edge. If you change this to SPI_MODE0 and re-upload, the serial monitor will immediately report a failure, proving that the physical layer timing is just as critical as the logical code.






