SPI

High-speed synchronous peripheral communication

SPI Fundamentals

SPI (Serial Peripheral Interface) is a synchronous, full-duplex serial bus originally introduced by Motorola in the 1980s. One device — the master — generates the clock and drives every transfer; one or more slave devices respond in lock-step with that clock. There is no arbitration, no bus addressing and no built-in acknowledgment: the master decides everything, which is exactly what makes SPI simple, fast and predictable inside a single board or module.

High speed and very low protocol overhead — almost every clock edge carries data

Full-duplex: read and write can happen in the same clock cycles

Simple hardware: a shift register on each side is enough to implement it

No address field in basic SPI — device selection is physical, via CS

Flexible transfer width (8/16/32-bit) and clock speed per device

More wires than I²C, and one extra CS wire per additional slave

No standardized ACK/NACK — the master cannot tell if a slave "heard" it

No universal SPI-level CRC or error mechanism Master-centric and usually short-distance (board-level, not bus-length cables)

Multi-master SPI exists but is uncommon and needs careful arbitration SPI transports bits, not meaning

SPI itself only defines how bits move: a clock, a data-in line, a data-out line and a select line. It says nothing about what those bits mean. The peripheral's own datasheet defines the command set, register map, address width, dummy cycles, timing limits and how multi-byte values are packed. Two SPI flash chips from different vendors can use the exact same electrical SPI bus and still speak completely different "languages" on top of it.

Where SPI fits in a typical ECU A microcontroller rarely uses SPI in isolation. It is one of several serial buses (alongside I²C, UART and often CAN/LIN for networking) chosen per-peripheral based on required speed, wire count budget and whether the peripheral needs to share a bus with many others. SPI is the default choice whenever a single peripheral needs high throughput and the extra CS pin is not a problem — flash memory, displays and high-rate sensors are the classic cases.

SPI Signals

SCLK — Serial Clock Generated by the master; its frequency sets the data rate. Idle level and active edge depend on the SPI mode (CPOL/CPHA).

MOSI — Master Out, Slave In Carries commands, register addresses and write data from master to slave. On some datasheets called SDO/SDI depending on which side is described.

MISO — Master In, Slave Out Carries data back from the currently selected slave. Deselected slaves must put this line in a high-impedance state so they don't corrupt the response.

CS / SS — Chip / Slave Select Typically active-low: pulling it LOW selects the device and frames the transaction; releasing it HIGH ends the transaction and often resets the slave's internal bit counter.

Voltage levels and electrical practicalities Common logic levels are 3.3 V and 1.8 V on modern MCUs, and 5 V on some legacy or automotive parts — mixing levels needs a level shifter or a part rated for both sides.

SPI has no defined pull-up requirement like I²C, but CS lines are often pulled to their inactive level with a resistor so an unselected device doesn't float mid-transaction during reset or boot.

Trace length and edge rate matter more as clock speed rises — at tens of MHz, unmatched trace lengths between SCLK/MOSI/MISO can cause skew.

Some devices add extra lines beyond the core four — WP (write-protect) and HOLD on many SPI flash parts — used in Dual/Quad SPI, covered later.

Pin multiplexing reminder On most MCUs, the physical pins used for SPI are shared with GPIO and other peripherals through a pin multiplexer. A correct SPI register configuration will still fail to transfer anything if the pins were never switched from GPIO mode into their SPI alternate function — a very common first debugging mistake.

SPI Data Transfer

Underneath SPI is a pair of shift registers — one in the master, one in the slave — wired MOSI-to-in and MISO-to-in so they form a single large ring. Every SCLK pulse shifts one bit out of the master into the slave, and simultaneously one bit out of the slave into the master. After N clocks, the two registers have effectively swapped their N-bit contents.

Each clock edge moves one bit Master → Slave and one bit Slave → Master at the same time. Even a pure "read" transaction still clocks bytes out of the master (often dummy bytes) to generate the clocks needed to receive data.

Bit order & width Devices may require MSB-first or LSB-first shifting — most are MSB-first, but always confirm in the datasheet. Controllers commonly support 8, 16 or 32-bit frame sizes; the frame size affects how CS behaves between words in some MCUs.

Byte order across multi-byte values Bit order (MSB/LSB-first) is a per-bit shifting detail defined by the SPI mode/hardware. Byte order (big-endian vs little-endian) is a separate, higher-level convention defined by the peripheral for multi-byte fields such as a 16-bit ADC result or a 32-bit flash address — don't confuse the two when decoding a capture.

SPI Clock Modes

CPOL (clock polarity) sets whether SCLK idles LOW (CPOL=0) or HIGH (CPOL=1). CPHA (clock phase) sets whether data is sampled on the first clock edge after CS goes active (CPHA=0) or on the second edge (CPHA=1). Combined, they produce four modes — 0 through 3 — and master and slave must agree on the same mode for the bus to work at all.

How to read the four modes Mode 0 (CPOL=0, CPHA=0) — idle low, sample on the rising edge. The most common default across many sensors and flash chips.

Mode 1 (CPOL=0, CPHA=1) — idle low, sample on the falling edge.

Mode 2 (CPOL=1, CPHA=0) — idle high, sample on the falling edge.

Mode 3 (CPOL=1, CPHA=1) — idle high, sample on the rising edge. Common on many automotive sensors.

Master and slave must agree on polarity and phase. A mismatch doesn't usually stop the bus from toggling — it just produces shifted, garbled or off-by-one-bit data, which is a classic "first byte looks almost right" symptom during bring-up. Always set the MCU's SPI mode to match the value stated in the peripheral's datasheet before suspecting anything else.

SPI Transactions

CS LOW → command byte → address bytes → dummy clocks/bytes if required by the device → receive data bytes → CS HIGH.

CS LOW → write command → address/register → data bytes → CS HIGH. Some devices require a separate "write-enable" command before a write transaction is accepted.

Why dummy data?

The master alone controls SCLK, so to receive N bytes back it must still clock N bytes out — even if it has nothing meaningful to send. It typically transmits a don't-care byte such as 0x00 or 0xFF purely to generate the clock edges the slave needs to shift its response out on MISO.

A concrete example — reading a register from a typical sensor

This "read bit in the command byte" convention (bit 7 = read/write) is extremely common on IMUs, magnetometers and pressure sensors, but it is a convention, not a rule of SPI — always confirm the exact bit meaning against the specific part's datasheet.

Multiple SPI Devices

Separate Chip Select All devices share SCLK/MOSI/MISO; each peripheral gets its own dedicated CS line from the MCU. Only the currently selected device should drive MISO — every other device must tri-state its output.

Some device families (certain shift registers, LED drivers, sensor chains) support serial daisy-chaining, where MISO of one device feeds MOSI of the next. This is not a universal SPI feature — it depends entirely on the specific part.

Why CS timing matters here Because all devices see the same SCLK/MOSI toggling all the time, a device only "listens" while its own CS is LOW. If two CS lines are accidentally both LOW at once, two slaves can try to drive MISO simultaneously — an electrical bus conflict. Firmware managing multiple CS pins must guarantee exactly one CS is active at any time, and should also serialize access to a shared SPI bus across tasks so one task's transaction can't be interrupted mid-frame by another.

Managing several CS pins in firmware Each select/transfer/deselect sequence should be treated as one atomic transaction, ideally protected by a mutex if multiple RTOS tasks share the bus.

SPI vs I²C vs UART

These three cover almost every low-speed, board-level or short-cable serial link an embedded engineer will touch. Picking the right one is a trade-off between wire count, speed, number of devices on the bus, and whether the link needs addressing.

You need the highest raw throughput to one device (flash, display, fast ADC) Extra CS pins per device are acceptable

Full-duplex simultaneous read/write is useful Pin count is tight and several devices must share only two wires

Devices support hardware addressing so no extra select lines are needed Speed requirements are modest Pick UART when

The link is point-to-point, asynchronous (no shared clock), and often crosses a connector or short cable — debug consoles, GPS modules and simple modems are classic UART use cases. It has no addressing and no bus-sharing concept at all.

MCU SPI Peripheral

GPIO → Pin Mux → SPI function. A correct SPI register configuration can still fail to move a single bit if the physical pins remain in GPIO mode.

FIFO & Interrupts Many MCUs buffer TX/RX data in a small FIFO and generate TX-empty, RX-full, transfer-complete or error interrupts so software doesn't have to service every single byte.

Typical CONTROL register bit-fields (illustrative) Exact bit names and layouts differ by silicon vendor — always work from the specific MCU's reference manual, not from memory of a similar part.

The SPI clock is normally derived from a peripheral bus clock divided by a prescaler, e.g. f_SCLK = f_PCLK / prescaler. As a rough illustration, an 80 MHz peripheral clock divided by 8 gives roughly 10 MHz — but the exact available prescaler values, rounding behavior and maximum supported frequency are always MCU-specific, so check the reference manual before relying on a calculated value.

SPI Driver

Start transfer → poll status flag → read/write the data register → repeat until complete. Simple to implement and easy to debug, but ties up the CPU for the whole transfer.

Interrupt / DMA Interrupts let the CPU do other work between bytes and only react when the peripheral needs attention. DMA goes further, moving entire buffers to/from the peripheral with minimal CPU involvement — the natural choice for large or high-throughput transfers like flash pages or display framebuffers.

Blocking vs non-blocking A blocking call returns only once the transfer finishes — simple to use in a linear init sequence, but wastes CPU time waiting. A non-blocking call kicks off the transfer and returns immediately, completing later via interrupt/DMA callback — better for real-time systems, at the cost of more complex state handling in the caller.

Sharing one SPI bus across an RTOS When several tasks each own a different SPI device on the same physical bus, the driver (or application) must serialize access — typically with a mutex held for the full select → transfer → deselect sequence — so that one task's transaction can never be interleaved with another's. Losing this guarantee is a common source of intermittent, hard-to-reproduce data corruption.

SPI Timing

Clock frequency and period: t = 1 / f_SCLK Setup time — data must be stable this long before the sampling edge

Hold time — data must remain stable this long after the sampling edge

CS setup and hold relative to the first/last clock edge Data valid / output delay from the driving edge

Inter-byte or inter-frame delay some devices require The peripheral's datasheet — not the MCU's maximum SPI clock — usually sets the real speed ceiling. At high clock rates, PCB trace length matching and signal integrity (ringing, reflections, crosstalk) start to dominate reliability.

A logic analyzer or oscilloscope captures CS, SCLK, MOSI and MISO together, so setup/hold and CS timing can be measured directly against the datasheet's numbers. Probe loading itself can slow edges enough to make a marginal high-speed link fail only while it's being probed — worth keeping in mind when a bug "disappears" as soon as you attach test equipment.

Error Handling

SPI itself has no universal, standardized error mechanism — everything above is implementation specific to the MCU's peripheral or to the connected device. Never assume a status bit name or meaning carries over between different silicon vendors.

Practical SPI Debugging

Capture CS, SCLK, MOSI and MISO together. Decode using the expected CPOL/CPHA and bit order, then compare the decoded command/address/dummy/data fields against the peripheral datasheet, byte by byte, rather than eyeballing waveforms alone.

Real Automotive Applications

SPI is often used purely as the short, local link between an MCU and an external CAN controller chip sitting on the same board. CAN then carries the actual vehicle network traffic through its transceiver — the two protocols solve different problems at different physical scales, and it's common to see both inside a single ECU.

Advanced SPI

Where to go from here Once standard SPI is comfortable, the natural next steps are reading a real flash or sensor datasheet end-to-end, capturing an actual transaction with a logic analyzer, and writing a small bare-metal driver against a specific MCU's reference manual — theory only really sticks once it's matched against one real device's quirks.