Skip to main content

spi-slave

Claim the bus in target (slave) mode using the portable alp_spi_target_* surface from <alp/peripheral.h> (v0.9, [ABI-EXPERIMENTAL]) and answer an external SPI controller.

Source: examples/peripheral-io/spi-slave/.

What it does

  1. alp_init() — return value checked; future backends can fail bring-up.
  2. alp_spi_target_open() on BOARD_SPI_ARDUINO, ALP_SPI_MODE_0, 8 bits. There is no cs_pin_id on our side — the external controller owns SCK and /CS.
  3. Loops on alp_spi_target_transceive(tgt, tx, rx, 5, &got, timeout_ms): preload a TX reply, wait for the controller to clock a transfer, decode what arrived, preload the next reply.
  4. alp_spi_target_close().

:::info Replies always lag one frame SPI is full-duplex. A target can never answer a command within the same transfer — the reply you preload is served on the next one. :::

The bounded-wait pattern

A finite timeout_ms keeps the thread responsive when the controller goes quiet, and it's what makes a clean close possible — close refuses with ALP_ERR_BUSY while a transceive is blocked.

Drivers without an async path report ALP_ERR_NOSUPPORT for finite timeouts (on Zephyr this needs CONFIG_SPI_ASYNC). The example detects that, prints bounded wait unsupported; waiting forever, and degrades to the unbounded UINT32_MAX wait. It also gives up after three consecutive idle timeouts.

The toy protocol

Fixed 5-byte frames:

CommandByte 0Reply (next frame)
PING0x010x00 + the 4-byte payload echoed back
GET_VERSION0x020x00 + ALP_VERSION_MAJOR / MINOR / PATCH from <alp/version.h> + the tag char 'A'
anything else0x00 then 0xEE fill

board.yaml

som:
sku: E1M-AEN801

preset: e1m-evk
supported_boards:
- e1m-evk
- e1m-x-evk

pins:
- { e1m: E1M_SPI1, macro: EVK_SPI_BUS_ARDUINO, doc: "Arduino UNO header SPI" }

cores:
m55_hp:
app: ./src
peripherals:
- spi

diagnostics:
log_level: info

prj.conf carries two knobs the board.yaml derivation does not cover:

CONFIG_SPI_SLAVE=y # every in-tree Zephyr SPI driver compiles its target path only when set
CONFIG_SPI_ASYNC=y # bounded-wait transceive rides spi_transceive_signal + k_poll

Availability

Zephyr's SPI slave support is patchy — some SoC controller drivers reject SPI_OP_MODE_SLAVE. Backends or drivers without slave mode fail with ALP_ERR_NOSUPPORT, or ALP_ERR_NOT_READY when the bus alias is unset. The example handles both by printing the diagnostic and exiting.

That is the expected outcome on native_sim, which does not wire BOARD_SPI_ARDUINO (only the alp-spi0 loader alias exists there):

[spi-slave] listening on BOARD_SPI_ARDUINO (mode 0, 8 bits)
[spi-slave] target open failed: alp_last_error=-2
[spi-slave] SPI target (slave) mode is unavailable here
[spi-slave] done

Test setup (real hardware)

  1. Flash this example onto board A (the target).
  2. Adapt spi-master on board B to send 5-byte frames:
    • [0x01, 0xDE, 0xAD, 0xBE, 0xEF] → expect [0x00, 0xDE, 0xAD, 0xBE, 0xEF] back on the next frame.
    • [0x02, 0xFF, 0xFF, 0xFF, 0xFF] → expect [0x00, <major>, <minor>, <patch>, 'A'] on the next frame.
  3. Wire SCK–SCK, MOSI–MOSI, MISO–MISO, /CS–/CS, GND–GND. The master drives SCK + MOSI + /CS; the target drives MISO.
  4. Board A's console shows one transfer N line per frame board B clocks.

A missing GND between boards is a classic inter-board SPI bug — it turns SPI edges into noise. A logic analyser with an SPI decoder makes CPOL/CPHA mismatches obvious.

Status

Two twister rows, one per EVK route table, on native_sim/native/64; the harness latches on [spi-slave] done — via the graceful NOT_READY path, not a working transfer. Target-mode behaviour is a bench signal.

See also

Questions about this page? Discuss in Community Forum