In this lesson:
- the four SPI signals and the master/slave roles
- full-duplex: two shift registers in a ring, "to read, you write"
- chip-select, and how you address multiple devices
- the four modes (CPOL/CPHA) and why both ends must match
- the gotchas: mode mismatch, CS timing, no ACK, pin growth
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 masterConsequence: 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 slaveA 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:
- CPOL (Clock Polarity): clock idles low (0) or high (1).
- CPHA (Clock Phase): data sampled on the first edge (0) or second edge (1) of each bit.
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
- CPOL/CPHA mismatch. If master and slave disagree on mode, the master samples on the wrong edge and reads bit-shifted garbage. Always set the master's mode to the slave's datasheet value (often mode 0 or 3). Top SPI bring-up failure.
- CS timing matters. Many slaves latch the transaction on the CS edge, assert CS before the first clock and deassert after the last bit. Some need CS toggled per byte, others hold it for a whole multi-byte transaction; follow the datasheet. Driving CS in software gives you that control.
- No ACK, no flow control, no error check. SPI is "fire and forget", the master never learns whether the slave understood. Higher layers (a status register, a CRC in the payload) provide reliability if you need it.
- MISO contention / floating. A deselected slave must release MISO (high-Z). If two are selected, or a device doesn't tri-state, they fight. With no slave selected, MISO floats, don't trust a read.
- Pin count and MSB-first. N slaves cost N CS lines. SPI is MSB-first by default (configurable on some MCUs); a bit-order mismatch scrambles bytes.
TL;DR
- SPI is a fast, synchronous, full-duplex bus with a master-driven clock and four signals: SCLK, MOSI (COPI), MISO (CIPO), CS.
- It works as two shift registers in a ring: each clock swaps a bit each way, so a full byte is exchanged in 8 clocks, to read, you must write (clock out a dummy byte).
- There's no addressing; the master selects a device by pulling its active-low CS low, and each slave needs its own CS line (pin count grows with devices).
- Both ends must use the same mode (CPOL = idle level, CPHA = sampling edge β modes 0-3); mode 0/3 are most common, and a mismatch is the top SPI bug.
- SPI has no ACK/flow control/error checking, it's fast and simple; add reliability in a higher layer if needed, and mind CS timing and MISO tri-stating.