# SPI 从机驱动程序

[English]

SPI 从机驱动程序控制在 ESP32-C6 中作为从机的 GP-SPI 外设。

有关 GP-SPI 硬件相关信息，请参考 ESP32-C6 技术参考手册 > SPI 控制器 [PDF]。

## 术语

下表为 SPI 从机驱动的相关术语。

术语 | 定义
--- | ---
主机 (Host) | ESP32-C6 外部的 SPI 控制器外设。用作 SPI 主机，在总线上发起 SPI 传输。
从机设备 (Device) | SPI 从机设备（通用 SPI 控制器）。每个从机设备共享 MOSI、MISO 和 SCLK 信号，但只有当主机向从机设备的专属 CS 线发出信号时，从机设备才会在总线上处于激活状态。
总线 (Bus) | 信号总线，由连接到同一主机的所有从机设备共用。一般来说，一条总线包括以下线路：MISO、MOSI、SCLK、一条或多条 CS 线，以及可选的 QUADWP 和 QUADHD。每个从机设备都有单独的 CS 线，除此之外，所有从机设备都连接在相同的线路下。如果以菊花链的方式连接，几个从机设备也可以共享一条 CS 线。
MISO | 主机输入，从机输出，也写作 Q。数据从从机设备发送至主机。
MOSI | 主机输出，从机输入，也写作 D。数据从主机发送至从机设备。
SCLK | 串行时钟。由主机产生的振荡信号，使数据位的传输保持同步。
CS | 片选。允许主机选择连接到总线上的单个从机设备，以便发送或接收数据。
QUADWP | 写保护信号。只用于 4 位 (qio/qout) 传输。
QUADHD | 保持信号。只用于 4 位 (qio/qout) 传输。
断言 (Assertion) | 指激活一条线的操作。反之，将线路恢复到非活动状态（回到空闲状态）的操作则称为 去断言。
传输事务 (Transaction) | 即主机断言从机设备的 CS 线，向从机设备传输数据，接着去断言 CS 线的过程。传输事务为原子操作，不可打断。
发射沿 (Launch Edge) | 源寄存器将信号 发射 到线路上的时钟边沿。
锁存沿 (Latch Edge) | 目的寄存器 锁存 信号的时钟边沿。

## 驱动程序的功能

SPI 从机驱动程序允许将 SPI 外设作为全双工设备使用。驱动程序可以发送/接收长度不超过 64 字节的传输事务，或者利用 DMA 来发送/接收更长的传输事务。然而，存在一些与 DMA 有关的 已知问题。

SPI 从机驱动程序支持将 SPI ISR 注册至指定 CPU 内核。如果多个任务同时尝试访问一个 SPI 设备，建议重构应用程序，以使每个 SPI 外设一次只由一个任务访问。此外，请使用 `spi_bus_config_t::isr_cpu_id` 将 SPI ISR 注册至与 SPI 外设相关任务相同的内核，确保线程安全。

## SPI 传输事务

主机断言 CS 线并在 SCLK 线上发出时钟脉冲时，一次全双工 SPI 传输事务就此开始。每个时钟脉冲都意味着通过 MOSI 线从主机转移一个数据位到从机设备上，并同时通过 MISO 线返回一个数据位。传输事务结束后，主机去断言 CS 线。

传输事务的属性由作为从机设备的 SPI 外设的配置结构体 `spi_slave_interface_config_t` 和传输事务配置结构体 `spi_slave_transaction_t` 决定。

由于并非每次传输事务都需要写入和读取数据，可以选择配置 `spi_transaction_t` 为仅 TX、仅 RX 或同时 TX 和 RX 传输事务。如果将 `spi_slave_transaction_t::rx_buffer` 设置为 `NULL`，读取阶段将被跳过。与之类似，如果将 `spi_slave_transaction_t::tx_buffer` 设置为 `NULL`，则写入阶段将被跳过。

备注

主机应在从机设备准备好接收数据之后再进行传输事务。建议使用另外一个 GPIO 管脚作为握手信号来同步设备。更多细节，请参阅 传输事务间隔。

## 使用驱动程序

- 调用函数 `spi_slave_initialize()`，将 SPI 外设初始化为从机设备。请确保在 bus_config 中设置正确的 I/O 管脚，并将未使用的信号设置为 `-1`。

- 传输事务开始前，需用要求的事务参数填充一个或多个 `spi_slave_transaction_t` 结构体。可以通过调用函数 `spi_slave_queue_trans()` 来将所有传输事务排进队列，并在稍后使用函数 `spi_slave_get_trans_result()` 查询结果；也可以将所有请求输入 `spi_slave_transmit()` 中单独处理。主机上的传输事务完成前，后两个函数将被阻塞，以便发送并接收队列中的数据。

-（可选）如需卸载 SPI 从机驱动程序，请调用 `spi_slave_free()`。

## 传输事务数据和主/从机长度不匹配

通常，通过从机设备进行传输的数据会被读取或写入到由 `spi_slave_transaction_t::rx_buffer` 和 `spi_slave_transaction_t::tx_buffer` 指示的大块内存中。可以配置 SPI 驱动程序，使用 DMA 进行传输。在这种情况下，则必须使用 `pvPortMallocCaps(size, MALLOC_CAP_DMA)` 将缓存区分配到具备 DMA 功能的内存中。

驱动程序可以读取或写入缓存区的数据量取决于 `spi_slave_transaction_t::length`，但其并不会定义一次 SPI 传输的实际长度。传输事务的长度由主机的时钟线和 CS 线决定，且只有在传输事务完成后，才能从 `spi_slave_transaction_t::trans_len` 中读取实际长度。

如果传输长度超过缓存区长度，则只有在 `spi_slave_transaction_t::length` 中指定的初始比特数会被发送和接收。此时， `spi_slave_transaction_t::trans_len` 被设置为 `spi_slave_transaction_t::length` 而非实际传输事务长度。若需满足实际传输事务长度的要求，请将 `spi_slave_transaction_t::length` 设置为大于 `spi_slave_transaction_t::trans_len` 预期最大值的值。如果传输长度短于缓存区长度，则只传输与缓存区长度相等的数据。

## GPIO 交换矩阵和 IO_MUX

ESP32-C6 的大多数外设信号都直接连接到其专用的 IO_MUX 管脚。不过，也可以使用 GPIO 交换矩阵，将信号路由到任何可用的其他管脚。如果通过 GPIO 交换矩阵路由了至少一个信号，则所有信号都将通过 GPIO 交换矩阵路由。

当 SPI 主机频率配置为 80 MHz 或更低时，则通过 GPIO 交换矩阵或 IO_MUX 路由 SPI 管脚效果相同。

下表列出了 SPI 总线的 IO_MUX 管脚。

管脚名称 | GPIO 编号 (SPI2)
--- | ---
CS0 | 16
SCLK | 6
MISO | 2
MOSI | 7
QUADWP | 5
QUADHD | 4

## 速度与时钟

### 传输事务间隔

ESP32-C6 的 SPI 从机外设是由 CPU 控制的通用从机设备。与专用的从机相比，在内嵌 CPU 的 SPI 从机设备中，预定义寄存器的数量有限，所有的传输事务都必须由 CPU 处理。也就是说，传输和响应并不是实时的，且可能存在明显的延迟。

解决方案为，首先使用函数 `spi_slave_queue_trans()`，然后使用 `spi_slave_get_trans_result()`，来代替 `spi_slave_transmit()`。由此一来，可使从机设备的响应速度提高一倍。

也可以配置一个 GPIO 管脚，当从机设备开始新一次传输事务前，它将通过该管脚向主机发出信号。示例代码存放在 peripherals/spi_slave 目录下。

### 时钟频率要求

SPI 从机的工作频率最高可达 40 MHz。如果时钟频率过快或占空比不足 50%，数据就无法被正确识别或接收。

## 限制条件和已知问题

1. 若启用了 DMA，则 RX 缓冲区应该以字对齐（从 32 位边界开始，字节长度为 4 的倍数）。否则，DMA 可能无法正确写入或无法实现边界对齐。若此项条件不满足，驱动程序将会报错。

   此外，主机写入字节长度应为 4 的倍数。长度不符合的数据将被丢弃。

## 应用示例

如需查看从机/主机通信的代码示例，请前往 peripherals/spi_slave_hd 目录。

- example:

    peripherals/spi_slave/receiver 演示了如何配置 SPI 从机以接收来自 SPI 主机的数据，并通过握手机制管理数据传输的准备状态。

- example:

    peripherals/spi_slave/sender 演示了如何配置 SPI 主机将数据发送到 SPI 从机，并通过握手来确保数据传输的时机恰当。

## API 参考

### 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.

  警告

  SPI0/1 is not supported

  警告

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

  警告

  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.

  参数:

    - 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.

  返回:

    - 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.

  参数:

    host -- SPI peripheral to free

  返回:

    - 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_queue_trans(spi_host_device_t host, const spi_slave_transaction_t *trans_desc, TickType_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.

  备注

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

  参数:

    - 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.

  返回:

    - 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, TickType_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`.

  参数:

    - 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.

  返回:

    - ESP_ERR_INVALID_ARG if parameter is invalid

    - ESP_ERR_NOT_SUPPORTED if flag `SPI_SLAVE_NO_RETURN_RESULT` is set

    - ESP_OK on success

esp_err_t spi_slave_transmit(spi_host_device_t host, spi_slave_transaction_t *trans_desc, TickType_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.

  参数:

    - 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.

  返回:

    - ESP_ERR_INVALID_ARG if parameter is invalid

    - 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)

此文档对您有帮助吗？

- 反馈已收到，谢谢！
- 如果您有其他意见，欢迎填写乐鑫文档反馈表。

- 我们重视您的反馈。
- 您可以填写乐鑫文档反馈表告诉我们如何改进该文档。