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

5 interview questions on this topic

Each one opens a full worked answer.

This is just the start

Sign up free to track your progress through this course, mark lessons complete, and work through 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