# SPI Slave Driver

[中文]

SPI Slave driver is a program that controls ESP32-S2's General Purpose SPI (GP-SPI) peripheral(s) when it functions as a slave.

For more hardware information about the GP-SPI peripheral(s), see ESP32-S2 Technical Reference Manual > SPI Controller [PDF].

## Terminology

The terms used in relation to the SPI slave driver are given in the table below.

Term | Definition
--- | ---
Host | The SPI controller peripheral external to ESP32-S2 that initiates SPI transmissions over the bus, and acts as an SPI Master.
Device | SPI slave device (general purpose SPI controller). Each Device shares the MOSI, MISO and SCLK signals but is only active on the bus when the Host asserts the Device's individual CS line.
Bus | A signal bus, common to all Devices connected to one Host. In general, a bus includes the following lines: MISO, MOSI, SCLK, one or more CS lines, and, optionally, QUADWP and QUADHD. So Devices are connected to the same lines, with the exception that each Device has its own CS line. Several Devices can also share one CS line if connected in the daisy-chain manner.
MISO | Master In, Slave Out, a.k.a. Q. Data transmission from a Device to Host.
MOSI | Master Out, Slave In, a.k.a. D. Data transmission from a Host to Device.
SCLK | Serial Clock. Oscillating signal generated by a Host that keeps the transmission of data bits in sync.
CS | Chip Select. Allows a Host to select individual Device(s) connected to the bus in order to send or receive data.
QUADWP | Write Protect signal. Only used for 4-bit (qio/qout) transactions.
QUADHD | Hold signal. Only used for 4-bit (qio/qout) transactions.
Assertion | The action of activating a line. The opposite action of returning the line back to inactive (back to idle) is called de-assertion.
Transaction | One instance of a Host asserting a CS line, transferring data to and from a Device, and de-asserting the CS line. Transactions are atomic, which means they can never be interrupted by another transaction.
Launch Edge | Edge of the clock at which the source register launches the signal onto the line.
Latch Edge | Edge of the clock at which the destination register latches in the signal.

## Driver Features

The SPI slave driver allows using the SPI peripherals as full-duplex Devices. The driver can send/receive transactions up to 72 bytes in length, or utilize DMA to send/receive longer transactions. However, there are some known issues related to DMA.

The SPI slave driver supports registering the SPI ISR to a certain CPU core. If multiple tasks try to access the same SPI Device simultaneously, it is recommended that your application be refactored so that each SPI peripheral is only accessed by a single task at a time. Please also use `spi_bus_config_t::isr_cpu_id` to register the SPI ISR to the same core as SPI peripheral related tasks to ensure thread safety.

## SPI Transactions

A full-duplex SPI transaction begins when the Host asserts the CS line and starts sending out clock pulses on the SCLK line. Every clock pulse, a data bit is shifted from the Host to the Device on the MOSI line and back on the MISO line at the same time. At the end of the transaction, the Host de-asserts the CS line.

The attributes of a transaction are determined by the configuration structure for an SPI peripheral acting as a slave device `spi_slave_interface_config_t`, and transaction configuration structure `spi_slave_transaction_t`.

As not every transaction requires both writing and reading data, you can choose to configure the `spi_transaction_t` structure for TX only, RX only, or TX and RX transactions. If `spi_slave_transaction_t::rx_buffer` is set to `NULL`, the read phase will be skipped. Similarly, if `spi_slave_transaction_t::tx_buffer` is set to `NULL`, the write phase will be skipped.

Note

A Host should not start a transaction before its Device is ready for receiving data. It is recommended to use another GPIO pin for a handshake signal to sync the Devices. For more details, see Transaction Interval.

### Using PSRAM for DMA transfer

SPI Slave driver supports using PSRAM for DMA transfer. Directly passing a PSRAM address as `spi_slave_transaction_t::tx_buffer` or `spi_slave_transaction_t::rx_buffer` is supported. For the rx_buffer, it has alignment requirements, using `heap_caps_malloc()` to allocate memory can automatically handle the alignment requirements. For the buffers that you can not control, you can also use the `SPI_SLAVE_TRANS_DMA_BUFFER_ALIGN_AUTO` flag to enable driver to automatically align the buffer from PSRAM.

Note that this feature shares the MSPI bus bandwidth (bus frequency * bus width), so the transmission bandwidth of the host to this device should be less than the PSRAM bandwidth, otherwise data may be lost, and the `spi_slave_transmit` function will return the `ESP_ERR_INVALID_STATE` error.

## Driver Usage

- Initialize an SPI peripheral as a Device by calling the function `spi_slave_initialize()`. Make sure to set the correct I/O pins in the struct bus_config. Set the unused signals to `-1`.

If transactions will be longer than 32 bytes, allow a DMA channel by setting the parameter `dma_chan` to the host device. Otherwise, set `dma_chan` to `0`.

- Before initiating transactions, fill one or more `spi_slave_transaction_t` structs with the transaction parameters required. Either queue all transactions by calling the function `spi_slave_queue_trans()` and, at a later time, query the result by using the function `spi_slave_get_trans_result()`, or handle all requests individually by feeding them into `spi_slave_transmit()`. The latter two functions will be blocked until the Host has initiated and finished a transaction, causing the queued data to be sent and received.

- (Optional) Enable/Disable driver functions: Slave driver supports disabling / enabling driver after it is initialized by calling to `spi_slave_disable()` / `spi_slave_enable()`, to be able to change clock or power config or sleep to save power. By default, the driver state is enabled after initialized.

- (Optional) To unload the SPI slave driver, call `spi_slave_free()`.

## Transaction Data and Master/Slave Length Mismatches

Normally, the data that needs to be transferred to or from a Device is read or written to a chunk of memory indicated by the `spi_slave_transaction_t::rx_buffer` and `spi_slave_transaction_t::tx_buffer`. The SPI driver can be configured to use DMA for transfers, in which case these buffers must be allocated in DMA-capable memory using `pvPortMallocCaps(size, MALLOC_CAP_DMA)`.

The amount of data that the driver can read or write to the buffers is limited by `spi_slave_transaction_t::length`. However, this member does not define the actual length of an SPI transaction. A transaction's length is determined by the clock and CS lines driven by the Host. The actual length of the transmission can be read only after a transaction is finished from the member `spi_slave_transaction_t::trans_len`.

If the length of the transmission is greater than the buffer length, only the initial number of bits specified in the `spi_slave_transaction_t::length` member will be sent and received. In this case, `spi_slave_transaction_t::trans_len` is set to `spi_slave_transaction_t::length` instead of the actual transaction length. To meet the actual transaction length requirements, set `spi_slave_transaction_t::length` to a value greater than the maximum `spi_slave_transaction_t::trans_len` expected. If the transmission length is shorter than the buffer length, only the data equal to the length of the buffer will be transmitted.

### GPIO Matrix and IO_MUX

Most of chip's peripheral signals have direct connection to their dedicated IO_MUX pins. However, the signals can also be routed to any other available pins using the less direct GPIO matrix. If at least one signal is routed through the GPIO matrix, then all signals will be routed through it.

When an SPI Host is set to 80 MHz or lower frequencies, routing SPI pins via GPIO matrix will behave the same compared to routing them via IO_MUX.

The IO_MUX pins for SPI buses are given below.

Pin Name | GPIO Number (SPI2)
--- | ---
CS0 | 10
SCLK | 12
MISO | 13
MOSI | 11
QUADWP | 14
QUADHD | 9

## Speed and Timing Considerations

### Transaction Interval

The ESP32-S2 SPI slave peripherals are designed as general purpose Devices controlled by a CPU. As opposed to dedicated slaves, CPU-based SPI Devices have a limited number of pre-defined registers. All transactions must be handled by the CPU, which means that the transfers and responses are not real-time, and there might be noticeable latency.

As a solution, a Device's response rate can be doubled by using the functions `spi_slave_queue_trans()` and then `spi_slave_get_trans_result()` instead of using `spi_slave_transmit()`.

You can also configure a GPIO pin through which the Device will signal to the Host when it is ready for a new transaction. A code example of this can be found in peripherals/spi_slave.

### SCLK Frequency Requirements

The SPI slaves are designed to operate at up to 40 MHz. The data cannot be recognized or received correctly if the clock is too fast or does not have a 50% duty cycle.

## Restrictions and Known Issues

1. If DMA is enabled, the rx buffer should be word-aligned (starting from a 32-bit boundary and having a length of multiples of 4 bytes). Otherwise, DMA may write incorrectly or not in a boundary aligned manner. The driver reports an error if this condition is not satisfied.

   Also, a Host should write lengths that are multiples of 4 bytes. The data with inappropriate lengths will be discarded.

## Application Examples

The code example for Device/Host communication can be found in the peripherals/spi_slave directory of ESP-IDF examples.

- example:

    peripherals/spi_slave/receiver demonstrates how to configure an SPI slave to receive data from an SPI master and implement handshaking to manage data transfer readiness.

- example:

    peripherals/spi_slave/sender demonstrate how to configure an SPI master to send data to an SPI slave and use handshaking to ensure proper timing for data transmission.

## API Reference

### Header File

- components/esp_driver_spi/include/driver/spi_slave.h

- This header file can be included with:

  >     #include "driver/spi_slave.h"

- This header file is a part of the API provided by the `esp_driver_spi` component. To declare that your component depends on `esp_driver_spi`, add the following to your CMakeLists.txt:

  >     REQUIRES esp_driver_spi

  > or

  >     PRIV_REQUIRES esp_driver_spi

### Functions

esp_err_t spi_slave_initialize(spi_host_device_t host, const spi_bus_config_t *bus_config, const spi_slave_interface_config_t *slave_config, spi_dma_chan_t dma_chan)

  Initialize a SPI bus as a slave interface and enable it by default.

  Warning

  SPI0/1 is not supported

  Warning

  If a DMA channel is selected, any transmit and receive buffer used should be allocated in DMA-capable memory.

  Warning

  The ISR of SPI is always executed on the core which calls this function. Never starve the ISR on this core or the SPI transactions will not be handled.

  Parameters:

    - host -- SPI peripheral to use as a SPI slave interface

    - bus_config -- Pointer to a spi_bus_config_t struct specifying how the host should be initialized

    - slave_config -- Pointer to a spi_slave_interface_config_t struct specifying the details for the slave interface

    - dma_chan -- - Selecting a DMA channel for an SPI bus allows transactions on the bus with size only limited by the amount of internal memory.

      - Selecting SPI_DMA_DISABLED limits the size of transactions.

      - Set to SPI_DMA_DISABLED if only the SPI flash uses this bus.

      - Set to SPI_DMA_CH_AUTO to let the driver to allocate the DMA channel.

  Returns:

    - ESP_ERR_INVALID_ARG if configuration is invalid

    - ESP_ERR_INVALID_STATE if host already is in use

    - ESP_ERR_NOT_FOUND if there is no available DMA channel

    - ESP_ERR_NO_MEM if out of memory

    - ESP_OK on success

esp_err_t spi_slave_free(spi_host_device_t host)

  Free a SPI bus claimed as a SPI slave interface.

  Parameters:

    host -- SPI peripheral to free

  Returns:

    - ESP_ERR_INVALID_ARG if parameter is invalid

    - ESP_ERR_INVALID_STATE if not all devices on the bus are freed

    - ESP_OK on success

esp_err_t spi_slave_enable(spi_host_device_t host)

  Enable the spi slave function for an initialized spi host.

  Note

  No need to call this function additionally after `spi_slave_initialize`, because it has been enabled already during the initialization.

  Parameters:

    host -- SPI peripheral to be enabled

  Returns:

    - ESP_OK On success

    - ESP_ERR_INVALID_ARG Unsupported host

    - ESP_ERR_INVALID_STATE Peripheral already enabled

esp_err_t spi_slave_disable(spi_host_device_t host)

  Disable the spi slave function for an initialized spi host.

  Parameters:

    host -- SPI peripheral to be disabled

  Returns:

    - ESP_OK On success

    - ESP_ERR_INVALID_ARG Unsupported host

    - ESP_ERR_INVALID_STATE Peripheral already disabled

esp_err_t spi_slave_queue_trans(spi_host_device_t host, const spi_slave_transaction_t *trans_desc, uint32_t ticks_to_wait)

  Queue a SPI transaction for execution.

  Queues a SPI transaction to be executed by this slave device. (The transaction queue size was specified when the slave device was initialised via spi_slave_initialize.) This function may block if the queue is full (depending on the ticks_to_wait parameter). No SPI operation is directly initiated by this function, the next queued transaction will happen when the master initiates a SPI transaction by pulling down CS and sending out clock signals.

  This function hands over ownership of the buffers in `trans_desc` to the SPI slave driver; the application is not to access this memory until `spi_slave_queue_trans` is called to hand ownership back to the application.

  Note

  On esp32, if trans length not WORD aligned, the rx buffer last word memory will still overwritten by DMA HW

  Parameters:

    - host -- SPI peripheral that is acting as a slave

    - trans_desc -- Description of transaction to execute. Not const because we may want to write status back into the transaction description.

    - ticks_to_wait -- Ticks to wait until there's room in the queue; use portMAX_DELAY to never time out.

  Returns:

    - ESP_ERR_INVALID_ARG if parameter is invalid

    - ESP_ERR_NO_MEM if set flag `SPI_SLAVE_TRANS_DMA_BUFFER_ALIGN_AUTO` but there is no free memory

    - ESP_ERR_INVALID_STATE if sync data between Cache and memory failed

    - ESP_OK on success

esp_err_t spi_slave_get_trans_result(spi_host_device_t host, spi_slave_transaction_t **trans_desc, uint32_t ticks_to_wait)

  Get the result of a SPI transaction queued earlier.

  This routine will wait until a transaction to the given device (queued earlier with spi_slave_queue_trans) has successfully completed. It will then return the description of the completed transaction so software can inspect the result and e.g. free the memory or reuse the buffers.

  It is mandatory to eventually use this function for any transaction queued by `spi_slave_queue_trans`.

  Parameters:

    - host -- SPI peripheral to that is acting as a slave

    - trans_desc -- [out] Pointer to variable able to contain a pointer to the description of the transaction that is executed

    - ticks_to_wait -- Ticks to wait until there's a returned item; use portMAX_DELAY to never time out.

  Returns:

    - ESP_ERR_INVALID_ARG if parameter is invalid

    - ESP_ERR_NOT_SUPPORTED if flag `SPI_SLAVE_NO_RETURN_RESULT` is set

    - ESP_ERR_INVALID_STATE if dma over/underflow error occurs during psram transfer

    - ESP_OK on success

esp_err_t spi_slave_transmit(spi_host_device_t host, spi_slave_transaction_t *trans_desc, uint32_t ticks_to_wait)

  Do a SPI transaction.

  Essentially does the same as spi_slave_queue_trans followed by spi_slave_get_trans_result. Do not use this when there is still a transaction queued that hasn't been finalized using spi_slave_get_trans_result.

  Parameters:

    - host -- SPI peripheral to that is acting as a slave

    - trans_desc -- Pointer to variable able to contain a pointer to the description of the transaction that is executed. Not const because we may want to write status back into the transaction description.

    - ticks_to_wait -- Ticks to wait until there's a returned item; use portMAX_DELAY to never time out.

  Returns:

    - ESP_ERR_INVALID_ARG if parameter is invalid

    - ESP_ERR_INVALID_STATE if dma over/underflow error occurs during psram transfer

    - ESP_OK on success

### Structures

struct spi_slave_interface_config_t

  This is a configuration for a SPI host acting as a slave device.

  Public Members

  int spics_io_num

    CS GPIO pin for this device.

  uint32_t flags

    Bitwise OR of SPI_SLAVE_* flags.

  int queue_size

    Transaction queue size. This sets how many transactions can be 'in the air' (queued using spi_slave_queue_trans but not yet finished using spi_slave_get_trans_result) at the same time.

  uint8_t mode

    SPI mode, representing a pair of (CPOL, CPHA) configuration:

    - 0: (0, 0)

    - 1: (0, 1)

    - 2: (1, 0)

    - 3: (1, 1)

  slave_transaction_cb_t post_setup_cb

    Callback called after the SPI registers are loaded with new data.

    This callback is called within interrupt context should be in IRAM for best performance, see "Transferring Speed" section in the SPI Master documentation for full details. If not, the callback may crash during flash operation when the driver is initialized with ESP_INTR_FLAG_IRAM.

  slave_transaction_cb_t post_trans_cb

    Callback called after a transaction is done.

    This callback is called within interrupt context should be in IRAM for best performance, see "Transferring Speed" section in the SPI Master documentation for full details. If not, the callback may crash during flash operation when the driver is initialized with ESP_INTR_FLAG_IRAM.

struct spi_slave_transaction_t

  This structure describes one SPI transaction

  Public Members

  uint32_t flags

    Bitwise OR of SPI_SLAVE_TRANS_* flags.

  size_t length

    Total data length, in bits.

  size_t trans_len

    Transaction data length, in bits.

  const void *tx_buffer

    Pointer to transmit buffer, or NULL for no MOSI phase.

  void *rx_buffer

    Pointer to receive buffer, or NULL for no MISO phase. When the DMA is enabled, must start at WORD boundary (`rx_buffer%4==0`), and has length of a multiple of 4 bytes.

  void *user

    User-defined variable. Can be used to store eg transaction ID.

### Macros

SPI_SLAVE_TXBIT_LSBFIRST

  Transmit command/address/data LSB first instead of the default MSB first.

SPI_SLAVE_RXBIT_LSBFIRST

  Receive data LSB first instead of the default MSB first.

SPI_SLAVE_BIT_LSBFIRST

  Transmit and receive LSB first.

SPI_SLAVE_NO_RETURN_RESULT

  Don't return the descriptor to the host on completion (use `post_trans_cb` to notify instead)

SPI_SLAVE_TRANS_DMA_BUFFER_ALIGN_AUTO

  Automatically re-malloc dma buffer if user buffer doesn't meet hardware alignment or dma_capable, this process may loss some memory and performance.

### Type Definitions

typedef void (*slave_transaction_cb_t)(spi_slave_transaction_t *trans)

Was this page helpful?

- Thank you! We received your feedback.
- If you have any comments, fill in Espressif Documentation Feedback Form.

- We value your feedback.
- Let us know how we can improve this page by filling in Espressif Documentation Feedback Form.