Skip to main content

SPI

SPI is the bus you'll use for anything that needs higher bandwidth than I2C — displays, SD cards, fast IMUs, flash chips. Four wires: clock (SCK), master-out (MOSI), master-in (MISO), and one chip-select (CS) per device.

info


Enable SPI

CONFIG_SPI=y

ESP32-S3 has three SPI controllers: SPI0 and SPI1 are reserved for flash, SPI2 and SPI3 are free for your peripherals. Reference them as &spi2 and &spi3 in devicetree. nRF52840 exposes SPI0..SPI3, each with full-duplex DMA.



Devicetree — a SPI slave

&spi2 {
status = "okay";
cs-gpios = <&gpio0 10 GPIO_ACTIVE_LOW>;

bme280: bme280@0 {
compatible = "bosch,bme280";
reg = <0>; /* CS index, not address */
spi-max-frequency = <8000000>; /* 8 MHz */
};
};

The slave's reg is the index into cs-gpios, not an I2C-style address. SPI has no addressing — CS selects the device.



C code — raw transceive

If you're writing your own driver (no in-tree binding), use the raw SPI API:

#include <zephyr/drivers/spi.h>

static const struct spi_dt_spec dev =
SPI_DT_SPEC_GET(DT_NODELABEL(bme280),
SPI_WORD_SET(8) | SPI_TRANSFER_MSB, 0);

uint8_t tx_buf[] = {0xD0}; /* read ID register */
uint8_t rx_buf[1] = {0};

struct spi_buf tx = {.buf = tx_buf, .len = sizeof(tx_buf)};
struct spi_buf rx = {.buf = rx_buf, .len = sizeof(rx_buf)};
struct spi_buf_set tx_set = {.buffers = &tx, .count = 1};
struct spi_buf_set rx_set = {.buffers = &rx, .count = 1};

int ret = spi_transceive_dt(&dev, &tx_set, &rx_set);

The two helpers spi_write_dt() and spi_read_dt() are wrappers around spi_transceive_dt() with a null buffer on one side.



Operation flags

The second argument to SPI_DT_SPEC_GET is spi_operation_t — a bitmask:

FlagMeaning
SPI_WORD_SET(8)8-bit words (most peripherals)
SPI_MODE_CPOLClock idle-high
SPI_MODE_CPHASample on second edge
SPI_TRANSFER_MSBMSB first (most peripherals)
SPI_OP_MODE_MASTERMaster mode (default)
SPI_HOLD_ON_CSKeep CS asserted across calls

The peripheral's datasheet tells you which CPOL/CPHA combination it needs (often called "SPI mode 0/1/2/3").



Common mistakes

  • Wrong SPI mode — most sensors are mode 0 (no SPI_MODE_* flags). If the device returns garbage, try mode 3 (SPI_MODE_CPOL | SPI_MODE_CPHA).
  • Forgot cs-gpios — the binding has no idea which GPIO is CS unless you say so. Bus will run but no device will respond.
  • Speed too high on long wires — start at 1 MHz when prototyping on a breadboard. SPI signal integrity falls off fast above a few MHz.
  • Sharing a bus, forgetting per-device speed — each slave has its own spi-max-frequency. Zephyr reconfigures the controller per transaction.
tip

If a sensor has both I2C and SPI modes, prefer I2C for prototyping (fewer wires, easier to bus-multiple devices) and switch to SPI only when bandwidth becomes the bottleneck.