We’re Officially Live β€” Lifetime Founding Membership Available for a Limited Time
Free preview

SPI

The fast 4-wire synchronous bus: a master-driven clock, full-duplex shift registers (to read you write), chip-select per device, and the CPOL/CPHA modes both ends must match.

24 min read

In this lesson:


The fast synchronous bus

SPI (Serial Peripheral Interface) is the speed champion of simple on-board buses, tens of MHz, used for displays, SD cards, flash, ADCs, radios. It's synchronous (a shared clock) and full-duplex, with one master (controller) driving everything and one or more slaves (peripherals).

Four signals:

Signal Driven by Meaning
SCLK master the clock, every bit is timed by its edges
MOSI (a.k.a. COPI) master Master Out, Slave In, master β†’ slave data
MISO (a.k.a. CIPO) selected slave Master In, Slave Out, slave β†’ master data
CS / SS master Chip Select (active-low), one per slave

(The industry is moving from MOSI/MISO to COPI/CIPO, Controller/Peripheral, same wires, clearer names.)


Full-duplex: to read, you write

The key mental model: master and slave each hold an 8-bit shift register, wired into a ring. On each SCLK edge, one bit shifts out of the master (MOSI) into the slave, and simultaneously one bit shifts out of the slave (MISO) into the master. After 8 clocks they've swapped bytes.

        MOSI
 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Άβ”
 β”‚  master shift reg    slave shift reg
 β”‚  [b7..b0] ──SCLK──▢ [b7..b0]
 β””β—€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        MISO
 8 clocks β†’ master's byte is in the slave, slave's byte is in the master

Consequence: every transfer is bidirectional. To send and ignore the reply, you write a byte and discard what comes back. To read, you still must generate clocks, so you write a dummy byte (often 0x00 or 0xFF) to clock the slave's data in. There's no "read-only" operation; reading and writing are the same shift.

// One SPI byte exchange: send tx, receive whatever shifts back.
uint8_t spi_xfer(uint8_t tx) {
    while (!(SPIx->SR & SPI_SR_TXE)) { }   // TX buffer empty?
    SPIx->DR = tx;                          // writing DR starts the 8-clock shift
    while (!(SPIx->SR & SPI_SR_RXNE)) { }   // wait for the received byte
    return (uint8_t)SPIx->DR;               // the byte the slave shifted back
}
 
// To read a register: send the address, then send a dummy to clock the data in.
cs_low();
spi_xfer(REG_ADDR);            // we ignore the byte returned here
uint8_t value = spi_xfer(0x00); // dummy out, real data in
cs_high();

Chip select and multiple slaves

SPI has no addressing in the protocol. The master picks a slave by pulling that slave's CS low (active-low) before the transfer and releasing it high after. Each slave needs its own CS line:

              β”Œβ”€β”€ CS0 ──▢ slave A
 master ──────┼── CS1 ──▢ slave B      shared SCLK / MOSI / MISO
              └── CS2 ──▢ slave C      one CS line per slave

A non-selected slave must tri-state its MISO (go high-impedance) so it doesn't fight the selected one. This is why pin count grows with device count, N slaves need N chip-selects (a "daisy-chain" mode exists for some devices but is less common).


The four modes: CPOL and CPHA

Both ends must agree on when the clock idles and which edge samples data. Two bits define this:

That's four combinations, modes 0-3:

Mode CPOL CPHA Clock idle Sample on
0 0 0 low 1st (rising) edge
1 0 1 low 2nd (falling) edge
2 1 0 high 1st (falling) edge
3 1 1 high 2nd (rising) edge
[FIGURE: SPI timing β€” CS, SCLK (CPOL=0), MOSI, MISO; sample edges for CPHA=0 vs CPHA=1]

The slave's required mode is fixed by its datasheet; the master must be configured to match. Mode 0 and mode 3 are the most common. Get it wrong and you read shifted/garbled data, the #1 SPI bug.


Gotchas


TL;DR

This is just the start

Sign up free to track progress on this lesson, get coding problems with the Run button, and unlock the full interview Q&A.

More in Communication Protocols

Serial vs Parallel, Sync vs AsyncUART
I2C
Choosing UART, SPI, or I2C
CAN Bus
Common Bus Bugs
SPI | EmbeddedPrep.io