SPI
SPI bus modes (CPOL/CPHA), clock polarity, phase, and multi-slave chip-select. Interactive lab for MOSI/MISO timing.
What SPI is: a synchronous serial bus
Serial Peripheral Interface (SPI) is a short-distance, synchronous, full-duplex serial interface. One controller supplies a clock and selects a target; at every clock edge, each side can shift one bit out while sampling one bit in. Unlike I2C, SPI has no universal electrical or packet-level standard: the datasheet contract for each part defines the command bytes, timing limits, and chip-select behavior.
How it is built
- Controller (historically called master): owns SCK and starts a transfer by asserting one target's chip select.
- Target (historically called slave): watches its selected CS input, samples clocked input data, and drives its output only while selected.
- Four-wire SPI uses SCK, CS (or nCS), controller-out/target-in (MOSI/COPI), and controller-in/target-out (MISO/CIPO).
- The electrical signals are normally single-ended CMOS GPIOs; SPI is intended for board-level traces, not a cable network.
Design procedure
- Configure pins, clock polarity/phase, bit order, word size, baud-rate divider, and CS idle level from the target datasheet.
- Make CS inactive, set SCK to its defined idle state, then assert the selected target's CS.
- Write a byte or word to the controller TX register; hardware shifts it while simultaneously collecting the received word.
- Read every received word (including dummy bytes) so overrun flags cannot accumulate.
- After the final clock and required hold time, wait for the peripheral's busy flag to clear and deassert CS.
Key terms
- SCK
- Serial clock generated by the controller; its frequency and edges define sampling time.
- CS / nCS
- Per-target select. It is commonly active-low, hence nCS or SS.
- MOSI / COPI
- Controller output, target input; modern naming is Controller Out, Peripheral In.
- MISO / CIPO
- Target output, controller input; it must be high impedance when that target is not selected.
- Full duplex
- Transmit and receive happen together. A read usually transmits dummy filler bytes to create clocks.
Worked example
A motion sensor read commonly holds nCS low, sends a read-address byte, then transmits 0x00 for each result byte. The controller discards the received byte during the address phase and keeps the bytes received during the dummy clocks.Common pitfalls
Bus roles and topologies
A conventional SPI bus is controller-led: only one controller drives SCK and starts each transaction. Several targets may share SCK and controller-to-target data, but a target needs an unambiguous selection mechanism. Most embedded systems use a star topology with one independent CS per target.
How it is built
- Star / independent CS: all targets share SCK and COPI; their CIPO outputs join at the controller input, but only the selected target may drive it.
- Daisy chain: the controller output enters target 1, each target's output feeds the next target's input, and the final output returns to the controller. One shared CS latches a whole shift-register chain.
- Point-to-point: one controller and one target, often the safest arrangement for fast memory or a sensitive converter.
- Multi-controller SPI exists in some hardware but requires explicit arbitration and is rarely a good default; separate buses or a higher-level bus are usually simpler.
Design procedure
- For each target, determine whether it has a dedicated CS, a daisy-chain position, or an external decoder/multiplexer.
- Keep every inactive CS at its documented inactive level from reset onward; add pull-ups where reset pin state is uncertain.
- Select exactly one shared-bus target, complete its transfer, then release it before selecting another.
- For a daisy chain, shift the total number of bits for all devices and pulse CS once to move the new values from shift registers to output latches.
Key terms
- Tri-state
- High-impedance output state used by an unselected target so another target can use CIPO safely.
- Bus contention
- Two outputs drive different logic levels on one wire; this corrupts data and can overstress output drivers.
- Daisy-chain latency
- Bits for the furthest device pass through all earlier shift registers, so software must account for total chain length.
- CS decoder
- External logic that turns a few GPIO address bits into many mutually exclusive selects; verify break-before-make timing.
Worked example
Two sensors can share SCK, COPI, and CIPO with nCS_ACCEL and nCS_GYRO. A serial LED driver chain instead uses one nCS and requires 16 bits per driver before CS rises to latch the complete frame.Common pitfalls
Clock modes: CPOL and CPHA
SPI mode names encode two independent timing choices. CPOL selects the idle level of SCK. CPHA selects whether data is sampled on the first clock edge after CS becomes active or on the second edge. The four combinations are commonly called modes 0 through 3; both endpoints must use the same edge convention.
How it is built
- CPOL = 0: SCK idles low. The first edge is rising and the second edge is falling.
- CPOL = 1: SCK idles high. The first edge is falling and the second edge is rising.
- CPHA = 0: sample on the leading (first) edge and change output on the trailing edge. The first data bit must be valid before that first edge.
- CPHA = 1: change output on the leading edge and sample on the trailing edge. The first bit is launched by the first edge.
- Mode 0 = CPOL 0/CPHA 0; Mode 1 = 0/1; Mode 2 = 1/0; Mode 3 = 1/1.
Design procedure
- Read the target timing diagram instead of relying only on the mode number; vendors sometimes describe edges from a different viewpoint.
- Set CPOL before CS is asserted so the clock sits at the target's specified idle voltage.
- Set CPHA, then confirm that controller launch and sample edges give the target at least its required input setup and hold time.
- Use a logic analyzer to verify the first bit, especially when CS is asserted immediately before the first edge.
Key terms
- Leading edge
- First transition away from the idle clock level after CS becomes active.
- Trailing edge
- Return transition toward the idle clock level within the same bit cell.
- Setup time (tSU)
- Minimum interval that data must be stable before its sampling edge.
- Hold time (tH)
- Minimum interval that data must remain stable after its sampling edge.
Worked example
A target specified as mode 3 idles SCK high, changes data on falling edges, and samples on rising edges. Configuring mode 0 instead can make every byte appear shifted or consistently wrong even when the wires are correct.Common pitfalls
Frames, word size, bit order, and chip-select boundaries
SPI transfers a continuous stream of clocked bits. A controller may group that stream into 8-, 16-, or 32-bit hardware words, but the target defines the meaningful command frame. CS is often the transaction delimiter: rising CS may reset an internal command parser, end a write, or latch an operation.
How it is built
- Bit order is usually most-significant bit first, but least-significant-bit-first devices exist and must be configured explicitly.
- Hardware word size controls FIFO/register access; it does not automatically match a target's 24-bit ADC result or 9-bit display word.
- Some controllers can hold CS active across DMA descriptors; others pulse it between words unless configured for continuous select.
- Targets may require command, address, dummy clocks, data, and a final CS rise as separate phases of one transaction.
Design procedure
- Translate the datasheet transaction diagram into an explicit byte sequence and identify where CS may or may not rise.
- Choose the controller word size that preserves byte order and alignment; use byte transfers for unusual protocol widths unless hardware support is verified.
- Assert CS, send command/address bytes, transmit required dummy bytes while receiving read data, and keep CS asserted through all mandatory phases.
- Observe minimum CS setup, inter-byte, and hold times; add a delay only when the target specifies one.
Key terms
- Dummy byte
- A filler value transmitted solely to supply clocks while reading CIPO; it is often 0x00 or 0xFF as specified.
- Frame
- Application-level command sequence understood by a target, not necessarily equal to one MCU hardware word.
- Continuous CS
- Keeping target select asserted across multiple FIFO words, essential for many command/address/data protocols.
- Endian order
- The order multi-byte values appear on the wire. SPI bit order and CPU memory endianness are separate concerns.
Worked example
A 24-bit converter may require: CS low, command 0x12, three dummy bytes, CS high. With 8-bit transfers it returns bytes B2, B1, B0; firmware assembles `(B2 << 16) | (B1 << 8) | B0` according to its datasheet.Common pitfalls
Electrical timing and signal integrity
SPI timing is a physical budget, not merely a configured clock frequency. At a sampling edge, the receiver needs a stable logic level after output clock-to-data delay, trace propagation, level shifting, ringing, and input setup time. Fast GPIO edge rates can make a modest-frequency SPI bus fail on a long or branched trace.
How it is built
- The timing budget includes controller output delay, interconnect delay, target input setup/hold, target output delay, return-path quality, and receiver margin.
- A shared CIPO trace has stubs to each target. Star branches reflect edges; daisy-chain routing or a lower edge rate can be more robust.
- Level shifters add direction, capacitance, propagation delay, and sometimes cannot support push-pull full-duplex SPI in both directions.
- Clock is the most timing-sensitive net. Route SCK with a nearby continuous ground return and keep it short before adding speed.
Design procedure
- Start below the target's rated maximum clock and validate the actual waveform at the receiving pin, not only at the controller.
- Check voltage-domain compatibility, absolute maximum ratings, input thresholds, and power-up sequencing for every attached part.
- Use controlled routing: short traces, continuous return path, minimal stubs, and optional source termination near the SCK/COPI driver when measurements show ringing.
- Reduce clock rate or GPIO slew rate before changing protocol software when failures correlate with temperature, cable length, or board revision.
Key terms
- Clock-to-out (tCO)
- Delay from a transmitter's clock edge to valid output data; it consumes the receiver's setup budget.
- Propagation delay
- Time a transition needs to travel through a trace, connector, or level shifter.
- Reflection
- Voltage step returning from an impedance discontinuity; it can cross logic thresholds more than once.
- Source termination
- A small series resistor at the driver that slows/absorbs the initial edge and reduces ringing on a point-to-point trace.
Worked example
A 20 MHz SCK is a 50 ns period, but a 1 ns GPIO edge behaves like a high-frequency transmission-line event. Adding a 22–47 ohm series resistor near the MCU SCK pin and lowering drive strength can remove ringing without reducing the nominal clock rate.Common pitfalls
Three-wire and bidirectional SPI variants
Standard SPI uses separate COPI and CIPO wires and is electrically full duplex. Some displays, sensors, and memories instead use a single bidirectional data pin (often called SDIO, IO0, or 3-wire SPI). During these transactions, the controller and target take turns driving the same wire, so direction turnaround and high-impedance timing are part of the protocol.
How it is built
- 3-wire half-duplex: one data wire carries command/address from controller then response from target; it saves a pin but cannot send and receive simultaneously.
- Bidirectional data mode may be controlled by an MCU peripheral direction bit or by changing the GPIO between output and input at a documented turnaround clock.
- Some targets label a separate D/C pin for command-versus-data selection; this is not the same as bidirectional SPI data direction.
- Multi-I/O memories generalize bidirectional operation: IO0–IO3 or IO0–IO7 are driven in parallel during selected phases.
Design procedure
- Use the target's waveform to identify command phase, turnaround cycle(s), and read phase.
- Drive the shared data pin for the controller-output phase, then release it to input/high impedance before the target begins driving.
- If the controller supports half-duplex mode, use its direction and turnaround controls rather than racing GPIO writes in software.
- Before the next command, wait for the target to release the line and re-enable controller output only at the allowed boundary.
Key terms
- Turnaround
- The interval or dummy cycle in which data-line ownership changes between controller and target.
- Half duplex
- Both directions use one wire but not at the same time.
- High impedance
- Disconnected output state needed before the other endpoint drives a shared data line.
- SDIO
- A common pin name for serial data input/output; always verify the device-specific meaning.
Worked example
A 3-wire display transaction can send an 8-bit register index, switch the MCU SDIO pin to input for one turnaround cycle, then read 16 bits of pixel/status data. Leaving SDIO as an output causes contention on the first returned bit.Common pitfalls
Firmware, Transactions, and Board Bring-Up
MCU SPI firmware configures a synchronous serial controller, assigns pins through the pin multiplexer, selects a bus format, and performs device-defined transactions. The controller is only a shift engine: a transaction is the entire CS-low interval the slave datasheet defines, including command, address, dummy clocks, payload, and CS-high recovery time. Reliable software owns that boundary, serializes access to the shared bus, and treats every timeout as a recovery path rather than an invitation to leave CS asserted.
How it is built
- Peripheral configuration is more than clock rate. A typical SPI block has enable/reset, controller role, CPOL/CPHA, frame width, bit order, baud divider, FIFO thresholds, status flags, interrupts, and DMA request enables. Configure fields while disabled when the reference manual requires it. The requested SCLK is generally PCLK divided by an integer; calculate the actual rate and select one at or below the slave maximum.
- The pin multiplexer is part of the driver. SCLK, MOSI, and a GPIO-controlled CS are master push-pull outputs; MISO is an input. Avoid enabling hardware NSS when a GPIO owns CS unless the MCU explicitly supports that arrangement. Verify pull states during reset and that no output drives an unpowered slave through its protection diodes.
- CS ownership creates transaction atomicity. Hardware NSS commonly frames one controller word, but sensors, displays, converters, and memories usually require CS to stay low across many bytes. A bus mutex must cover selecting the slave through deselecting it. Two tasks that interleave bytes corrupt both device protocols even when each byte transfer individually succeeds.
- Full duplex means receive requires transmit clocks. Each SCLK edge shifts one bit in each direction. A read sends command/address then filler bytes, conventionally 0xFF or 0x00 only if the device permits it, while collecting MISO. A write still receives bytes; drain them or use a discard RX DMA buffer so the RX FIFO does not overflow.
- Polling is ideal for short boot-time commands and register accesses: feed TX when space exists, consume RX whenever data exists, and use a deadline. Interrupt I/O avoids spinning but needs a state machine for TX-empty, RX-ready, errors, and final shifter idle. DMA suits long display, converter, audio, and memory transfers; arm RX before TX and finish only after both DMA and the SPI busy flag complete.
- DMA is hardware sharing, not merely fast memcpy. While DMA owns TX memory, CPU code must not edit it; while it owns RX memory, CPU code must not consume it. Cached MCUs must clean the TX range before DMA reads and invalidate the RX range after DMA writes, rounded safely to cache lines. Or place buffers in a DMA-accessible non-cacheable region. Check reachability, alignment, transfer-count limits, and buffer lifetime.
- Keep a transport/device split. `spi_transfer(tx, rx, count, deadline)` moves equal byte counts. Each device driver defines command bits, address width, dummy cycles, status meaning, CRC, and CS lifetime. Segment-based transport can hold CS across command, address, and data without fragile temporary concatenation buffers.
- Peripheral grammar differs by class. Sensors often have a read bit and auto-increment only while CS remains low. ADCs may need conversion-ready timing; DAC output may latch on CS rising, so do not split its word. Displays commonly use D/C plus CS and benefit from write-only DMA. Shift registers need a separate latch pulse and may return meaningless MISO data. The part datasheet wins over every generic SPI rule.
- SPI is a board-level bus, not a cable standard. Keep SCLK/MOSI/CS short over a continuous ground reference, minimize stubs, and use a source series resistor when fast edges ring. Verify VIH/VIL in both directions, common ground, local decoupling, reset/power order, and correct level shifting. A device that only works at a low clock frequently has edge, return-path, MISO-delay, or voltage trouble.
- Use instruments in layers. A logic analyzer confirms CS interval, mode, bit order, bytes, and transaction length; configure the decoder rather than trusting auto-detect. An oscilloscope reveals rise time, overshoot, ringing, ground bounce, MISO clock-to-output delay, and supply droop that a digital decoder hides. Capture and save one known-good transaction with the driver test.
Design procedure
- Before coding, record supply range, VIH/VIL, maximum SCLK for the selected mode, CPOL/CPHA, bit order, word size, CS setup/hold/high timing, command framing, reset sequence, and power-up delay from the slave datasheet.
- Configure the MCU pin mux and output electrical settings, enable and reset the SPI peripheral, choose master role, format, prescaler, and software CS. Clear stale status and drain RX FIFO before the first transfer.
- Acquire the bus lock, apply the selected device format while the controller is idle, assert exactly one CS, and obey tCSS before the first active edge.
- Send command and address. For every expected response byte, transmit an intentional dummy byte so clocks continue. Service every received byte, including discard data during writes.
- On polling or interrupt completion, wait for the shift engine busy flag to clear, not merely TX FIFO empty. Then obey tCSH, deassert CS, and wait tCSH/tCSHI before a new transaction if required.
- For DMA, prepare cache state, start RX DMA before TX DMA, wait for both streams and peripheral idle, then invalidate RX cache before software reads the result. Keep buffers and the device descriptor valid until completion.
- On deadline expiry, stop both DMA directions, reset/drain the SPI block as required, drive CS high, release the bus, and return a specific failure. Optionally reset or reinitialize the slave only by its documented recovery procedure.
- Test cold boot, brownout, reset during transfer, slow and maximum clocks, a second SPI slave with another mode, and bus contention. Validate raw waveforms, not just a friendly high-level API return value.
Key terms
- SPI controller
- The MCU hardware shift/FIFO/DMA endpoint. It generates clocks but does not understand a sensor register map or flash command.
- Transaction
- One complete device-defined CS-low interval. It may contain several transport segments and must not be interleaved with another task.
- TXE / RXNE
- Typical status indications for transmit FIFO space and receive data available. They are not proof that the final wire edge has completed.
- BSY
- Typical busy/shifter indication. Wait for its documented inactive state before releasing CS after the final byte.
- Dummy byte
- Intentional MOSI filler used to create SCLK while receiving MISO. The value is device-specific even when 0xFF is common.
- Scatter/gather segments
- Command, address, dummy, and payload buffers transferred under one CS assertion without assembling a monolithic buffer.
- Cache clean / invalidate
- Clean makes CPU-written TX bytes visible to DMA; invalidate discards stale CPU RX cache lines after DMA writes memory.
- tCSS / tCSH / tCSHI
- CS setup before first clock, hold after final clock, and high time between transactions. These are slave timing requirements.
- D/C
- A display data/command GPIO. It is separate from CS and must settle at the point specified by the display controller.
- Series termination
- A small resistor near the driving pin that slows an excessively fast edge and reduces ringing on a short point-to-point trace.
Worked example
// Board-independent C-like pattern: controller transfer is transport only.
typedef struct { SpiBus *bus; GpioPin cs; SpiFormat format; } SpiDevice;
int spi_device_xfer(SpiDevice *d, const SpiSegment *segments, size_t count) {
spi_bus_lock(d->bus); // lock spans ALL CS-low bytes
spi_apply_format_if_idle(d->bus, d->format); // mode, Hz, bits, bit order
spi_clear_stale_rx(d->bus);
gpio_write(d->cs, 0); delay_ns(d->format.cs_setup_ns);
int rc = spi_transfer_segments(d->bus, segments, count, DEADLINE_US);
if (rc == 0) rc = spi_wait_not_busy(d->bus, DEADLINE_US);
delay_ns(d->format.cs_hold_ns);
gpio_write(d->cs, 1); // executed on every error path
delay_ns(d->format.cs_high_ns);
spi_bus_unlock(d->bus);
return rc;
}
int sensor_read(SpiDevice *d, uint8_t reg, uint8_t *out, size_t n) {
uint8_t command = 0x80u | reg; // device-specific read opcode
SpiSegment s[] = {
{ .tx = &command, .rx = NULL, .count = 1 },
{ .tx = NULL, .rx = out, .count = n, .fill = 0xFF }, // NULL TX emits clocks
};
return spi_device_xfer(d, s, 2);
}
// DMA sketch: RX is armed first; cache maintenance is architecture-specific.
cache_clean(tx, n); cache_invalidate(rx, n);
dma_start_rx(SPI_RX_REQUEST, rx, n);
dma_start_tx(SPI_TX_REQUEST, tx, n);
if (!wait_event(spi_dma_done, TIMEOUT) || spi_error(&spi1)) { dma_abort_both(); spi_reset(&spi1); }
spi_wait_not_busy(&spi1, TIMEOUT);
cache_invalidate(rx, n); // only now may CPU consume received bytesCommon pitfalls
Serial memory: NOR command and state model
SPI NOR flash is non-volatile random-access storage controlled by a serial command protocol. It is not RAM: an erased cell reads as 1, programming can only change selected bits from 1 to 0, and an erase operation returns an entire sector/block to 1. A serial NOR device contains a command decoder, status/configuration registers, an address decoder, a page-program buffer, an erase/program state machine, and an array. It keeps its contents without power but has finite program/erase endurance and operation-dependent latency.
How it is built
- The host owns /CS and SCLK. A falling /CS starts a transaction; the host sends an opcode, then any required address, mode bits, dummy clocks, and data. A rising /CS ends or commits many commands. A device is selected only while its own /CS is low.
- The command decoder recognizes operations such as Read, Read Status Register, Write Enable, Page Program, Sector Erase, Block Erase, Chip Erase, Reset, and vendor-specific configuration commands. Opcode values and register layouts are part-specific; the datasheet is authoritative.
- The write-enable latch (WEL, often WEL/WEN bit) is a volatile permission latch. Write Enable sets it; a successful program/erase normally clears it. WEL prevents an accidental command stream from modifying flash, but it is not a mutex or a security boundary.
- The write-in-progress/busy bit (WIP or BUSY) reports that the embedded state machine is changing the array. While busy, a device may accept only status reads and a small documented subset of commands. Never infer completion from a fixed delay alone; poll the documented status bit with a timeout.
- Page Program loads a bounded page buffer (commonly 256 bytes, but not universally). The byte count may wrap within that page if firmware crosses the page boundary. Erase granularity is much larger than a page: typical sectors are 4 KiB and blocks are 32/64 KiB, but boot sectors and hybrid layouts exist.
- Protection bits, status-register write enables, volatile/non-volatile configuration registers, and optional password/secure regions may prohibit writes. These are useful for boot images, but their reset persistence and locking semantics differ by part.
Design procedure
- Discover the exact device and its supported features before selecting opcodes. Read JEDEC ID for a coarse identity, then parse SFDP when the part supports it; do not assume a capacity from a familiar ID alone.
- For a read, select the documented read command and bus mode, transmit opcode and address, emit exactly the required mode/dummy cycles, then keep clocking while the target shifts data out. Reads may be interrupted by de-asserting /CS unless continuous-read mode says otherwise.
- For a write, first check that the target range is erased or schedule erase. Split the payload at page boundaries. For each page fragment: issue Write Enable, verify WEL if required by the design, issue Page Program with address and data, then poll WIP/BUSY until clear or a bounded timeout expires.
- For an erase, choose the smallest supported erase unit that covers the update, issue Write Enable, issue the erase command and aligned address, and poll completion. Erase can take milliseconds to seconds; do not block a real-time path with an unbounded busy loop.
- Verify important data after programming. At minimum compare a readback CRC/hash; bootloaders should authenticate the image before selecting it. Treat timeout, unexpected status protection, or an illegal bit transition as an operation failure, not as a retry-without-limit condition.
- On reset/recovery, release /CS, provide the documented reset sequence or hardware reset, wait for the part's ready time, then explicitly reapply the required address mode, I/O mode, latency/dummy setting, and controller configuration before ordinary reads.
Key terms
- NOR flash
- Flash array optimized for direct random reads; erased bytes are normally FFh and it supports in-place execute after controller mapping.
- WEL / WIP
- Write-enable latch / write-in-progress status. WEL authorizes one modification; WIP says the internal state machine is still busy.
- Page program
- Programming operation limited by the device page buffer. It changes only 1 bits to 0 bits and must not cross the stated page boundary.
- Erase unit
- Smallest region that can be restored to all 1s. Firmware layout and wear policy should align data structures to supported units.
- tPP / tSE
- Maximum page-program / sector-erase time from the datasheet. Use these to set a timeout; typical time is not a correctness guarantee.
- /CS
- Active-low chip select. It frames the command and determines which device may drive shared data lines.
Worked example
/* Generic single-I/O NOR write. Opcode/register names and timeouts are
* device-specific; use the part's SFDP/datasheet-derived driver table. */
int nor_program(uint32_t address, const uint8_t *src, size_t length)
{
while (length != 0u) {
size_t room = NOR_PAGE_SIZE - (address % NOR_PAGE_SIZE);
size_t n = length < room ? length : room;
if (!range_is_erased(address, n))
return -EUCLEAN; /* caller schedules aligned erase first */
if (nor_write_enable() || !nor_wel_is_set())
return -EIO;
if (nor_page_program(address, src, n))
return -EIO;
if (nor_wait_ready(NOR_TPP_MAX_US))
return -ETIMEDOUT;
if (crc_flash(address, n) != crc_ram(src, n))
return -EIO;
address += (uint32_t)n;
src += n;
length -= n;
}
return 0;
}Common pitfalls
Single, dual, quad, QPI, octal, and xSPI bus modes
Serial-memory mode notation describes how many physical I/O wires carry each phase of a command. The three numbers conventionally mean instruction-address-data widths. Thus 1-1-1 is ordinary SPI, 1-1-4 is a single-wire instruction/address followed by quad output data, and 1-4-4 is a single-wire instruction followed by quad address and data. These labels describe wire use, not a promise that every command, device, or controller supports the mode.
How it is built
- 1-1-1 uses SCLK, /CS, IO0/MOSI and IO1/MISO. Commands are usually SPI modes 0 or 3, MSB first. It is the universal recovery/startup mode for common serial NOR devices.
- Dual output (1-1-2) keeps opcode/address on IO0 and returns data over IO0/IO1. Dual I/O (1-2-2) sends address and reads data on two lines. It improves transfer data rate but still requires a part-specific opcode and dummy-cycle count.
- Quad output (1-1-4) reads data over IO0..IO3; Quad I/O (1-4-4) sends address and reads data over all four I/Os. IO2 and IO3 often begin life as /WP and /HOLD or /RESET pins, so the quad-enable configuration bit and board pull-ups matter.
- QPI (4-4-4) puts instruction, address, and data on four I/O lines. It normally requires an explicit Enter QPI command and has a specific Exit QPI/reset path. A controller that sends a 1-bit recovery opcode while the chip remains in QPI will not be understood.
- Octal SPI/OPI uses IO0..IO7. SDR OPI is commonly described as 8-8-8; DDR/Double Transfer Rate parts may use 8D-8D-8D, sampling on both clock edges. Some high-speed implementations add DQS/RWDS as a data strobe. Pin names, command bytes, command-extension bytes, and DTR timing are device family details.
- xSPI is an industry interface/profile umbrella for high-performance serial memories, especially octal devices. It standardizes concepts such as an 8-I/O interface, DTR transfers, reset behavior, and discoverable parameters, but firmware must still obey the selected part's JESD216 SFDP tables and datasheet timing rather than treating 'xSPI' as one universal opcode set.
Design procedure
- Bring up in conservative 1-1-1 mode at a clock below the datasheet's initial maximum. Confirm the identity/status command, reset behavior, and ordinary reads before enabling a wider mode.
- Read configuration/SFDP capability information. Select a read command whose instruction width, address width, data width, dummy cycles, mode-byte requirements, and maximum frequency match both the flash and the MCU controller.
- If entering quad/QPI/octal mode requires a non-volatile configuration bit, first preserve unrelated register bits, issue the documented write-enable-for-status command, write the register, poll completion, then reset/re-read status to prove the desired state. Some devices use volatile settings specifically to avoid permanent mode changes.
- Configure the controller atomically: pin mux/drive strength and input sampling, bus width, clock mode/rate, opcode width/value, address byte count, alternate/mode bytes, dummy cycles, DTR/strobe settings, and /CS high time. Then validate a known data range at low and final frequencies.
- Provide an escape path. The boot ROM/first-stage loader should know how to reset the device to 1-1-1 if an interrupted update leaves it in QPI/OPI, continuous-read, 4-byte address, or DTR mode. A dedicated reset pin is simpler when the package offers one.
Key terms
- 1-1-4
- One I/O wire for opcode, one for address, four for returned data: often called Quad Output Fast Read.
- 1-4-4
- One I/O wire for opcode, then four wires for address and data: often called Quad I/O Read.
- QPI
- Quad Peripheral Interface, 4-4-4 transfers. It is a device mode, not merely a controller setting.
- OPI
- Octal Peripheral Interface using eight data I/Os; may be SDR or DDR/DTR.
- Dummy cycles
- Clock cycles with no payload used to meet array access latency. Their count can change with frequency, protocol, latency-code configuration, and SDR/DTR mode.
- DQS/RWDS
- A source-synchronous data strobe used by some high-speed octal memories to capture data with margin; it is not present on every octal flash.
Worked example
/* A read descriptor is safer than scattered magic constants. The driver
* selects this only after SFDP/device capability checks succeed. */
struct read_profile quad_io = {
.opcode = READ_QUAD_IO, .instruction_lines = 1,
.address_lines = 4, .data_lines = 4,
.address_bytes = 4, .mode_bytes = 1,
.dummy_cycles = 8, .dtr = false,
};
/* Program controller + flash as one state transition, then verify known data.
* Do not copy these numbers to another flash: opcode/mode/dummy values vary. */
int select_read_profile(const struct read_profile *p)
{
flash_exit_continuous_read_and_reset_if_needed();
if (flash_enable_required_io_mode(p))
return -EIO;
controller_apply_read_profile(p);
return verify_read_signature(FLASH_SIGNATURE_ADDRESS);
}Common pitfalls
Addressing, JEDEC ID, and SFDP discovery
A serial-memory driver must discover both identity and protocol geometry. JEDEC ID is a short manufacturer/type/capacity identification response useful for matching known parts. SFDP (Serial Flash Discoverable Parameters, JESD216) is a table format stored by the flash that describes density, erase types, read protocols, dummy cycles, addressing, and optional feature tables. SFDP is the scalable way to avoid baking one vendor's assumptions into firmware.
How it is built
- A 24-bit/3-byte address names 16 MiB of byte-addressed storage (000000h through FFFFFFh). Larger NOR parts need a 4-byte/32-bit address or a bank/extended-address mechanism. A 3-byte command against a 32 MiB part can silently alias the upper half to the lower 16 MiB.
- There are multiple 4-byte strategies: dedicated 4-byte opcodes, a global enter/exit 4-byte address mode, and legacy bank/extended-address registers. They are not interchangeable; reset persistence and boot-ROM compatibility determine which strategy is safe.
- The SFDP header is normally read through the standard Read SFDP command (commonly 5Ah in 1-1-1) at a defined table address with dummy bytes. It begins with the 'SFDP' signature and parameter headers that point to tables such as the Basic Flash Parameter Table (BFPT). Parse little-endian fields with bounds/version checks.
- The BFPT describes capacity and erase/read capabilities, but optional parameter headers provide 4-byte addressing, sector maps, xSPI/oct al profiles, status registers, and other extensions. Absence of a table means fall back only to a verified known-part profile, not guessed behavior.
- JEDEC ID and SFDP are usually readable before a full vendor driver is selected, but power state, QPI/OPI mode, or continuous-read state can change the required recovery transport. The earliest boot stage should keep a small, conservative reset-and-discovery path.
Design procedure
- After power-up, satisfy tPU/tVSL and force a documented known protocol state. Use hardware reset when available; otherwise use the part's valid software reset sequence and wait for ready.
- Read JEDEC ID and reject an all-FF or all-00 response as a wiring/select failure, not a real identity. Match only fields that the vendor guarantees stable for the chosen family.
- Read and validate the SFDP signature, revision, parameter-header count, and every pointer/length before using a table. Convert density encoding to bytes carefully; do not truncate large values to 32-bit signed arithmetic.
- Build a capability/profile object: capacity, address strategy, page size if supplied/known, supported erase sizes/opcodes, fastest reliable read profiles, required QE/OPI settings, maximum clock per profile, and timing bounds.
- Select the safest operation for the boot stage. A ROM-compatible loader may use 3-byte 1-1-1 reads while the application later uses 4-byte quad/oct al memory mapping. Keep update/recovery code able to return to the boot profile.
Key terms
- JEDEC ID
- Short identification response used to choose a family profile. It does not by itself encode every command/timing detail.
- SFDP
- Standard discoverable parameter tables in serial flash. Parse them rather than assuming a familiar opcode means identical timing/features.
- BFPT
- Basic Flash Parameter Table: core density, erase, and read capability description inside SFDP.
- 4-byte opcode
- A command variant that carries a four-byte address without changing a global addressing state.
- 4-byte address mode
- Device-wide state in which ordinary address-bearing commands expect four bytes; it can affect a later boot stage if not reset.
- Address aliasing
- Different intended addresses resolving to the same physical data because the command carried too few address bits.
Worked example
int flash_discover(struct flash_caps *caps)
{
uint8_t id[3], header[8];
flash_reset_to_safe_spi_mode();
if (spi_read_command(RDID, 0, id, sizeof id) ||
all_equal(id, sizeof id, 0xff) || all_equal(id, sizeof id, 0x00))
return -ENODEV;
if (flash_read_sfdp(0, header, sizeof header) ||
memcmp(header, "SFDP", 4) != 0)
return load_verified_profile_for_id(id, caps);
return parse_sfdp_with_bounds_checks(caps); /* checks revisions and pointers */
}
/* Never choose address_bytes from capacity alone without checking which
* 4-byte mechanism the discovered device and boot chain actually support. */Common pitfalls
Execute in place (XIP) and controller memory mapping
Execute in place (XIP) means the CPU fetches instructions directly from non-volatile serial NOR through a controller that maps a flash address window into the processor address space. The controller converts CPU reads into repeated SPI/QSPI/OSPI transactions using a preconfigured read profile. XIP saves RAM and boot-copy time, but it turns flash protocol state, cache behavior, and erase/program activity into system-wide execution concerns.
How it is built
- A serial-memory controller exposes an indirect-command path for configuration/program/erase and a mapped/AHB/AXI path for ordinary CPU or DMA reads. Mapped reads are usually read-only and use one fixed opcode, address width, dummy count, I/O width, and DTR/strobe profile.
- Instruction and data caches may hold lines from mapped flash. Prefetchers and speculative reads can create transactions that firmware did not explicitly issue. MPU/MMU attributes must match the controller's safe mapping and the architecture's executable/read-only policy.
- While a NOR die is programming or erasing, reads from it may stall, return undefined data, or be restricted depending on the part. Code that writes the same die must execute from SRAM/another flash bank, including its interrupt handlers, vector table requirements, constants, stack-dependent library helpers, and flash driver.
- Some devices support suspend/resume of erase/program so a high-priority read can proceed, but suspend latency, valid commands, and data integrity guarantees are device-specific. It is an optimization, not a substitute for a safe RAM-resident update design.
- Memory-mapped writes do not mean flash programming. A CPU store into the mapped window is normally invalid, ignored, bus-faulted, or sent nowhere. Flash modifications must use documented indirect commands with WEL/WIP handling.
Design procedure
- Start in indirect safe SPI mode, identify/configure the flash, and select a verified read profile. Program controller timing, address size, I/O width, dummy/mode cycles, DTR and sampling/strobe settings, chip-select high time, and mapped base/size.
- Read a known signature through the mapped window and compare it with an indirect read. Test at the lowest and final clock frequencies, across cache-line and erase-sector boundaries, and after reset/wake.
- For in-field update, stop clients of the mapped region. Disable or relocate code that could fetch from the target die, move the flash routine and any needed ISR/vector entries to RAM, clean/invalidate cache according to the CPU/controller manual, then leave mapping or use indirect mode.
- Erase/program with bounded WIP polling and verification. Restore the read profile and mapping, invalidate stale instruction/data cache lines and branch predictor state as required by the CPU architecture, then allow execution/readers back into the region.
- Use image headers, atomic boot metadata, rollback state, and authentication. XIP makes a partially updated executable region especially dangerous: power loss must leave a separately verified bootable slot intact.
Key terms
- Memory-mapped mode
- Controller mode translating CPU-bus reads in a fixed address range into serial-flash read transactions.
- XIP
- Execution directly from a mapped non-volatile image, avoiding a full copy of code into RAM.
- Indirect mode
- Explicit command/transaction path used for identification, register access, program, erase, and recovery.
- Cache coherency
- Requirement to invalidate or otherwise reconcile CPU instruction/data cache with changed underlying flash contents before executing/reading the change.
- Read-while-write
- Ability to read one resource while another is busy. Same-die behavior varies; dual-die/dual-bank hardware is not equivalent to a promise from one die.
- Latency code
- Configuration that selects dummy cycles/access latency for a frequency range. Flash and controller must agree.
Worked example
/* Pseudocode: flash_update_ram() and every callable dependency are linked
* into SRAM. The exact cache/barrier operations are CPU-specific. */
RAMFUNC int update_xip_slot(uint32_t dst, const uint8_t *image, size_t n)
{
stop_tasks_that_execute_or_read_xip_slot();
disable_or_relocate_interrupt_vectors_to_ram();
cache_clean_invalidate_for_flash_window();
ospi_leave_memory_mapped_mode();
int rc = erase_then_program_and_verify(dst, image, n);
if (rc == 0 && ospi_enter_memory_mapped_mode(&verified_read_profile) == 0) {
cache_invalidate_instruction_and_data_for_flash_window();
instruction_synchronization_barrier();
publish_verified_slot_metadata();
}
restore_interrupts_and_clients();
return rc;
}Common pitfalls
SPI NAND, ECC, and production reliability
SPI NAND packages NAND flash behind an SPI-like command interface. It can offer much higher density and lower cost per bit than NOR, but it is page-and-block managed rather than byte-addressable random-write storage. Data is transferred through an internal page cache, array operations happen per page/block, bad blocks are normal, and error correction code (ECC) plus wear management are mandatory parts of the storage design.
How it is built
- A NAND array is organized as blocks containing pages; pages commonly include a main data area and spare/OOB bytes. A block is erased as a unit, and page program rules are more restrictive than NOR. Geometry is device-specific and must be obtained from the part documentation/ONFI-like parameter support where applicable.
- A read normally has two phases: Page Read moves one physical page from the array into an on-chip cache and sets status/ECC results; Read From Cache clocks selected bytes from that cache over single/dual/quad I/O. Random data output selects an offset only within the loaded cache, not an arbitrary array read.
- Program Load transfers bytes into the page cache; Program Execute commits the cache to a specified page in the array. Erase operates on a block. WEL/WIP still exist, but status also reports program/erase failures and ECC correction outcomes.
- NAND ships with factory-marked bad blocks and develops wear-related failures. The system must skip bad blocks, reserve replacement capacity, maintain logical-to-physical mapping, obey program-order/partial-program limits, and recover from interrupted metadata updates.
- Internal ECC may correct a limited number of bit errors per page and expose a corrected-bit count/status class. A corrected read is a warning about retention/wear margin; an uncorrectable read is a data-loss event unless higher-level redundancy exists.
Design procedure
- Choose NAND only with a storage layer designed for it: a vendor FTL, a tested flash filesystem such as UBIFS on an appropriate MTD stack, or a purpose-built log-structured mapping. Raw NAND command code alone is not a reliable filesystem.
- At provisioning, scan and record factory bad blocks according to the part's marker convention. Do not erase or allocate them. Reserve spare blocks and protect mapping/checkpoint metadata with CRC, sequence/versioning, and power-loss-safe commit ordering.
- For every read, issue Page Read, poll ready, inspect operation/ECC status, then Read From Cache. Correctable ECC status may trigger scrubbing/relocation; uncorrectable status must be surfaced to the integrity/recovery layer.
- For every write, select a good erased page that obeys the device's page-program order and partial-program limit. Write Enable, Program Load, Program Execute, poll status, and verify the failure bit. Erase only an aligned good block and update mapping atomically.
- Measure endurance, retention, temperature, and power-fail behavior on actual hardware. Use wear leveling, bad-block growth handling, error telemetry, and authenticated redundant metadata; do not rely on a clean-lab sample as a lifetime model.
Key terms
- Page cache
- On-chip buffer used as an intermediate for SPI NAND array reads and programs. It is not a CPU cache and has different commands/state.
- OOB/spare area
- Per-page bytes used for ECC, bad-block markers, metadata, or controller-specific information; ownership must be defined by the selected stack.
- Bad block
- A block that must not receive normal data. Factory bad blocks are expected; runtime bad blocks must be retired and remapped.
- ECC
- Error-correcting code that detects/corrects bounded bit errors. Its strength, placement, and status interpretation are part/device/controller specific.
- FTL
- Flash translation layer mapping logical sectors/objects to changing physical locations while managing wear and failures.
- Program order
- Constraint on which page may be programmed next in a NAND block and how many partial programs are permitted.
Worked example
/* SPI NAND read is two operations, not a direct byte-addressed array read. */
int spinand_read_page(uint32_t page, uint16_t column, uint8_t *dst, size_t n)
{
if (nand_page_read_to_cache(page))
return -EIO;
if (nand_wait_ready(NAND_TREAD_MAX_US))
return -ETIMEDOUT;
enum ecc_result ecc = nand_decode_ecc_status(nand_get_status());
if (ecc == ECC_UNCORRECTABLE)
return -EBADMSG;
if (nand_read_from_cache(column, dst, n))
return -EIO;
if (ecc == ECC_NEAR_LIMIT)
schedule_data_scrub(page); /* relocate while still readable */
return 0;
}Common pitfalls
Pins, timing, reset, and board-level bring-up
A serial-memory protocol is simultaneously a command language and an electrical interface. Correct opcodes cannot compensate for an unpowered device, an unselected /CS, an incompatible I/O voltage, a floating multifunction pin, excess clock edge rate, or controller sampling outside the flash's timing window. High-speed dual/quad/octal designs require the schematic, layout, pin configuration, and firmware profile to be designed together.
How it is built
- Every device needs power, ground, decoupling, /CS, SCLK, and the I/O lines used by its selected protocol. Standard SPI needs IO0/IO1; quad adds IO2/IO3; octal adds IO4..IO7 and may add reset and DQS/RWDS. A package's pin function changes by mode, so use its pin table rather than a generic label.
- Voltage domains must be compatible across MCU, flash, pull-ups, level shifters, and reset supervisor. Bidirectional I/O lines make many unidirectional level translators unsuitable. Power-up ramp and /CS/SCLK behavior during reset must satisfy tVSL/tPU and input tolerance limits.
- Clock mode, maximum frequency, duty-cycle limits, /CS setup/hold/high times, output-valid delay, input setup/hold, and dummy cycles form one timing budget. In DTR, both edges matter and board skew/strobe timing reduce margin substantially.
- /WP, /HOLD, and /RESET behavior differs across devices and mode configuration. Pull these pins to documented inactive levels until firmware deliberately assigns them an I/O role. Sharing /CS among devices is invalid; each device needs a unique /CS even if SCLK/I/O are shared.
- Software reset sequences are commonly 66h followed by 99h in ordinary SPI, but no sequence is universal across all modes/vendors. Hardware reset is often more robust. A reset can also clear volatile QE/OPI/latency/address settings, so recovery must reconfigure both chip and controller.
Design procedure
- Before firmware debugging, inspect rails, reset, and /CS with a scope or logic analyzer. Confirm that /CS stays high during power ramp as required, SCLK is at its idle polarity, and no other device drives the shared I/O net while unselected.
- Start at low SCLK in 1-1-1 mode, with conservative drive/slew settings and documented inactive pull states. Capture a JEDEC-ID and status transaction; inspect opcode, /CS framing, clock edge, and response delay instead of trusting only decoded analyzer labels.
- Enable one capability at a time: first reliable fast single I/O, then dual/quad, then final clock, then DTR/octal. At each stage test cold boot, warm reset, watchdog reset, power interruption, different temperature/voltage corners, and deep-power-down exit if used.
- For a bus shared with another device, release I/O direction according to the protocol, provide a unique /CS, and ensure no selected device sees unsupported multi-I/O commands. Do not select two push-pull MISO/data-output devices together.
- Document a recovery sequence in the board support package: reset pin or software reset, ready wait, ID/status read, address/mode setup, controller profile setup, and a known-data test. Make it callable after timeout, watchdog, or unexpected boot state.
Key terms
- tCSS/tCSH
- Chip-select setup/hold time around clock activity. Violating either can make a command intermittently decode incorrectly.
- tCLQV
- Clock-low to output-valid timing (name varies by datasheet); controller sample point must allow the corresponding output delay and board skew.
- Signal integrity
- Quality of voltage waveforms at the receiver. Long traces, stubs, fast edges, poor return paths, and poor termination create ringing and timing errors.
- Deep power-down
- Low-power state available on many flashes. It reduces consumption but requires a documented release command and wake delay before accesses.
- Continuous read
- Mode in which a device suppresses repeated instruction bytes after an initial transaction. It can improve mapped reads but must have a reliable exit path.
- Bus contention
- Two outputs driving a shared line to opposite levels. It corrupts data and can overstress pads; /CS and pin-direction sequencing prevent it.
Worked example
Bring-up capture checklist
1. Measure VCC/VIO and reset release; verify the flash's power-up timing.
2. Capture /CS, SCLK, IO0, IO1 at 100 kHz in 1-1-1 mode.
3. Verify: /CS low -> opcode -> required address/dummy -> response -> /CS high.
4. Read JEDEC ID and status repeatedly across reset and clock changes.
5. Enable QE/OPI only after recording the recovery/reset command path.
6. Sweep clock rate; if failure begins at one edge rate, inspect ringing and
sample timing before changing protocol code. Add series damping only after
checking the board's impedance/layout and component datasheets.Common pitfalls
More in Connectivity
- CAN BusCAN bus arbitration, dominant/recessive voltage levels, bit stuffing, frame structure, and error states. Interactive multi-node arbitration simulator, a full-frame walkthrough that animates every field (SOF, ID, DLC, data, CRC-15, ACK, EOF) on real CANH/CANL voltage waveforms, a CRC noise demo, and a fault-injection debug lab.
- TCPTCP from first principles to advanced: full header field explorer, three-way handshake with real seq/ack numbers and SYN-loss retransmission, animated sliding-window data transfer with scripted packet loss, congestion control (slow start, congestion avoidance, cwnd halving), and window-vs-BDP throughput. Interactive simulator plus theory.
- UARTUART frame structure, baud rate calculation, start/stop bits, parity, and flow control. Interactive simulator for TX/RX timing analysis.
- I2CI2C protocol: start/stop conditions, addressing, ACK/NACK, clock stretching, and multi-master arbitration. Interactive simulator.
- I2S AudioI2S audio: word select, bit clock and MCLK arithmetic, Philips versus left-justified alignment, and the one-bit shift that ruins audio while the signals look perfect.
- 1-Wire1-Wire: bit values encoded as pulse duration, the interrupt jitter budget, reset and presence, ROM search as a binary tree walk, CRC-8, and parasitic power.
- Framing & COBSMessage framing on a byte stream: length prefixes, byte stuffing, SLIP and COBS, and why resynchronisation after a lost byte decides which one you should use.
- LIN BusLIN bus: break and sync-byte calibration for crystal-less slaves, protected identifier parity, classic versus enhanced checksums, and offline schedule tables.
- LoRaWANLoRaWAN: spreading factor against airtime, duty cycle limits, Class A downlink windows, OTAA versus ABP, frame counters and adaptive data rate.
- MQTTMQTT publish/subscribe: topic hierarchy design, QoS 0/1/2 delivery guarantees, retained messages, last will, keep-alive and persistent sessions.
- HTTP / HTTPSHTTP request and response framing, status code categories, Content-Length versus chunked encoding, keep-alive, and safe retry policy per method.
- TLS SecurityTLS on embedded targets: what certificate validation actually checks, why the clock is a security dependency, trust stores, forward secrecy and rotation.