API GUIDE / SPI Slave / Middleware

G32S10 SPI Slave Middleware 接口

Middleware 层接口:MID 组件依赖 im_dma。

1. 构建配置与文件组织

1.1 文件职责

文件说明
code/middleware/spi_slv/include/im_spi_slv.hMID 公开宏、类型、配置和全部 API
code/middleware/spi_slv/src/im_spi_slv.c固定传输、连续接收、IRQ、DMA 和状态实现
code/middleware/spi_slv/CMakeLists.txt在 CONFIG_MIDDLEWARE_SPI_SLV 生效时编译 MID

MID 组件依赖 im_dma。SPI_SLV 设备对象和所有配置、传输、进度结构由调用方 提供存储,MID 不进行动态内存分配。

2. 公共宏、枚举和数据结构

2.1 传输模式

typedef enum {
    IM_SPI_SLV_TRANSFER_POLLING = 0,
    IM_SPI_SLV_TRANSFER_IRQ,
    IM_SPI_SLV_TRANSFER_DMA,
} im_spi_slv_transfer_mode_t;
模式FIFO/DMA 推进方式典型完成方式
IM_SPI_SLV_TRANSFER_POLLING调用 im_spi_slv_wait() 的线程搬运 FIFOwait 返回。
IM_SPI_SLV_TRANSFER_IRQSPI_SLV IRQ 搬运 FIFOIRQ 完成并唤醒 wait,同时产生事件。
IM_SPI_SLV_TRANSFER_DMAIM DMA 搬运 TX/RX 数据DMA 完成中断并唤醒 wait,同时产生事件。

默认模式为 IM_SPI_SLV_TRANSFER_POLLING。模式在 im_spi_slv_init() 时确定; 切换模式需要先 deinit,再以新配置重新 init。

2.2 运行状态

typedef enum {
    IM_SPI_SLV_STATE_RESET = 0,
    IM_SPI_SLV_STATE_READY,
    IM_SPI_SLV_STATE_BUSY,
    IM_SPI_SLV_STATE_LISTENING,
    IM_SPI_SLV_STATE_ERROR,
    IM_SPI_SLV_STATE_TIMEOUT,
} im_spi_slv_state_t;
状态说明
IM_SPI_SLV_STATE_RESET尚未初始化或已经 deinit。
IM_SPI_SLV_STATE_READY已初始化且可以布防传输或监听。
IM_SPI_SLV_STATE_BUSY一笔固定长度传输已经布防或正在执行。
IM_SPI_SLV_STATE_LISTENING连续接收已经启动。
IM_SPI_SLV_STATE_ERROR最近一次传输或控制操作失败。
IM_SPI_SLV_STATE_TIMEOUT最近一次固定长度传输等待超时。

ERROR 和 TIMEOUT 状态允许再次调用 im_spi_slv_start() 或im_spi_slv_listen_start() 重新布防;成功后会根据新操作返回 BUSY、LISTENING 或 READY。

2.3 事件

typedef enum {
    IM_SPI_SLV_EVENT_TRANSFER_DONE = 0,
    IM_SPI_SLV_EVENT_TX_QUEUED,
    IM_SPI_SLV_EVENT_TRANSFER_ERROR,
    IM_SPI_SLV_EVENT_TRANSFER_TIMEOUT,
    IM_SPI_SLV_EVENT_TRANSFER_ABORTED,
    IM_SPI_SLV_EVENT_RX_READY,
} im_spi_slv_event_t;
事件触发条件
IM_SPI_SLV_EVENT_TRANSFER_DONERX-only 或全双工传输成功完成。
IM_SPI_SLV_EVENT_TX_QUEUEDTX-only 的所有帧已经进入 FIFO/DMA 硬件路径。
IM_SPI_SLV_EVENT_TRANSFER_ERROR活动传输因 HAL、FIFO、IRQ 或 DMA 错误结束。
IM_SPI_SLV_EVENT_TRANSFER_TIMEOUT等待超时并中止活动传输。
IM_SPI_SLV_EVENT_TRANSFER_ABORTED应用显式调用 im_spi_slv_abort() 中止活动操作。
IM_SPI_SLV_EVENT_RX_READY连续接收 FIFO 达到配置的 watermark。

TX_QUEUED 只表示 MID 已把数据交给硬件路径,不表示外部主机已经把这些帧移出 到物理总线上。

Polling 模式的事件回调在调用 start、wait 或 transfer 的线程中执行。 IRQ 模式和 DMA 模式的事件回调可能运行在 SPI_SLV 或 DMA 中断上下文,不能 阻塞、休眠、等待锁或执行耗时操作。

2.4 回调类型

typedef void (*im_spi_slv_callback_t)(im_spi_slv_t *dev,
                                      im_spi_slv_event_t event,
                                      void *user_data);

user_data 来自 im_spi_slv_config_t::user_data,MID 原样传入。

连续接收要求 callback 非 NULL。固定长度传输可以使用 wait 获取结果, callback 是可选的。

2.5 im_spi_slv_transfer_t

typedef struct {
    const void *tx_buffer;
    void *rx_buffer;
    size_t frame_count;
} im_spi_slv_transfer_t;

方向由缓冲区是否为 NULL 决定:

tx_bufferrx_buffer传输方向
非 NULLNULLTX-only
NULL非 NULLRX-only
非 NULL非 NULL全双工
NULLNULL非法参数

约束:

  • frame_count 表示帧数,不是字节数,范围必须为 1 至 UINT32_MAX。

  • TX 和 RX 使用同一 frame_count。

  • 缓冲区由调用方拥有,并在完成、错误、超时或 abort 前保持有效。

  • DMA 模式只要求非 NULL 缓冲区地址按 IM_SPI_SLV_DMA_BUFFER_ALIGN 对齐。

  • DMA 模式下,帧数乘以每帧字节数后传输长度还必须能放入 uint32_t。

每帧内存宽度由对应方向的 HAL 位宽决定:

位宽每帧内存占用
4 至 8 bit1 byte
9 至 16 bit2 bytes
17 至 32 bit4 bytes

TX 和 RX 可以配置不同位宽,因此同一个传输中 TX 和 RX 的每帧字节数也可能不同。

2.6 im_spi_slv_progress_t

typedef struct {
    size_t frame_count;
    size_t tx_frames_queued;
    size_t rx_frames_completed;
    size_t tx_frames_remaining;
    size_t rx_frames_remaining;
} im_spi_slv_progress_t;

进度字段描述 MID 软件可见的数据搬运状态,不代表主机已经在物理总线上完成了 相同数量的时钟周期。

对于连续接收,rx_frames_completed 统计已经由im_spi_slv_read_available() 复制出来的帧数;它不包含仍停留在 RX FIFO 中但 尚未读出的数据。

3. 配置和初始化

3.1 默认配置初始化

void im_spi_slv_config_struct_init(im_spi_slv_config_t *config);

填充 4.6 节列出的默认值。NULL 参数被忽略。

典型初始化代码:

im_spi_slv_config_t config;

im_spi_slv_config_struct_init(&config);
config.instance = IM_SPI_SLV_INSTANCE_1;
config.transfer_mode = IM_SPI_SLV_TRANSFER_IRQ;
config.irq_id = IRQ_SSLV1;
config.callback = app_spi_slv_callback;

3.2 初始化和反初始化

error_code_t im_spi_slv_init(im_spi_slv_t *dev,
                             const im_spi_slv_config_t *config);
error_code_t im_spi_slv_deinit(im_spi_slv_t *dev);

im_spi_slv_init():

  1. 校验指针和 transfer_mode。

  2. 校验逻辑 instance,映射为 SPI_SLV0/1 并初始化 SPI_SLV HAL。

  3. IRQ 模式注册 HAL 回调,申请并使能 config.irq_id。

  4. 成功后将状态设置为 IM_SPI_SLV_STATE_READY。

DMA 通道不会在 init 时申请,而是在第一次 DMA 传输时按需打开。

返回码条件
IM_OK初始化成功。
IM_ERR_INVALID_PARAM空指针、传输模式非法、实例非法或 HAL 配置非法。
IM_ERR_NOT_READY中断等平台资源尚未就绪。
IM_ERR_BUSYIRQ 等平台资源已被占用。

im_spi_slv_deinit() 会停止活动 DMA/IRQ 数据路径,释放 IRQ、DMA 和 HAL 资源。它不会发送 IM_SPI_SLV_EVENT_TRANSFER_ABORTED;需要显式事件时应先调用im_spi_slv_abort()。

返回值是第一个清理错误。正常清理后状态为 IM_SPI_SLV_STATE_RESET;清理失败 时状态为 IM_SPI_SLV_STATE_ERROR。

4. 固定长度传输接口

4.1 启动传输

error_code_t im_spi_slv_start(
    im_spi_slv_t *dev,
    const im_spi_slv_transfer_t *transfer);

im_spi_slv_start() 完成参数校验、硬件复位、RX/TX 计数配置、数据通路使能和 TX 预填充。返回 IM_OK 表示从机已经布防,不表示主机已经提供 SCLK,也不表示 串行传输已经完成。

在 Polling 模式下,如果 TX-only 数据能够全部进入 FIFO,start 返回前可能已经 达到完成条件;其他方向仍需由 im_spi_slv_wait() 搬运 FIFO 并确认完成。

主要返回码:

返回码条件
IM_OK传输已经成功布防。
IM_ERR_INVALID_PARAM空指针、帧数为 0、帧数超出 UINT32_MAX、方向非法、DMA 对齐或长度非法。
IM_ERR_BUSY当前已经在 BUSY 或 LISTENING。
IM_ERR_NOT_READY设备尚未初始化。
IM_ERR_IOHAL、FIFO、IRQ 或 DMA 路径失败。

典型调用顺序:

ret = im_spi_slv_start(&slave, &transfer);
if (ret == IM_OK) {
    /* 此时外部主机可以拉 CS 并提供 SCLK。 */
    ret = im_spi_slv_wait(&slave, 1000U);
}

4.2 等待完成

error_code_t im_spi_slv_wait(im_spi_slv_t *dev, uint32_t timeout_ms);

超时选择顺序:

  1. timeout_ms 非零时使用参数值。

  2. 参数为 0 且 config.timeout_ms 非零时使用配置值。

  3. 两者都为 0 时使用 IM_SPI_SLV_DEFAULT_TIMEOUT_MS。

行为:

  • Polling:在调用线程中搬运 RX/TX FIFO,直到完成、失败或超时。

  • IRQ/DMA:等待异步完成信号。

  • LISTENING 状态调用该接口返回 IM_ERR_BUSY。

  • 非 BUSY 状态调用时返回最近一次记录的 last_status。

  • 超时时停止活动硬件路径、记录 TIMEOUT 状态、产生IM_SPI_SLV_EVENT_TRANSFER_TIMEOUT,并返回 IM_ERR_TIMEOUT。

4.3 同步便利接口

error_code_t im_spi_slv_transfer(
    im_spi_slv_t *dev,
    const im_spi_slv_transfer_t *transfer,
    uint32_t timeout_ms);

该接口等价于先调用 im_spi_slv_start(),再调用 im_spi_slv_wait()。

当本线程还需要启动本机 SPI 主机或执行其他动作来产生 SCLK 时,应分别调用im_spi_slv_start() 和 im_spi_slv_wait()。直接调用im_spi_slv_transfer() 可能因为同一线程无法同时提供主机时钟而超时。

4.4 完成语义

方向MID 完成条件对应成功事件
TX-only所有请求帧已经进入 FIFO/DMA 硬件路径。IM_SPI_SLV_EVENT_TX_QUEUED
RX-only所有请求帧已经进入 rx_buffer。IM_SPI_SLV_EVENT_TRANSFER_DONE
全双工TX 已全部入队且 RX 已全部写入 rx_buffer。IM_SPI_SLV_EVENT_TRANSFER_DONE

TX-only 完成时,外部主机可能尚未把所有帧移出到线上。应用若需要确认物理传输 完全结束,应结合主机侧完成信号、CS 状态或额外协议层确认。

5. 连续接收接口

连续接收只支持 IRQ 模式,用于接收长度未知或长期持续的主机数据。

5.1 读取可用数据

error_code_t im_spi_slv_read_available(
    im_spi_slv_t *dev,
    void *buffer_out,
    size_t frame_capacity,
    size_t *frame_count_out);

该接口从 RX FIFO 读取至多 frame_capacity 帧,并把实际读取帧数写入frame_count_out。实际读取数可以为 0。

buffer_out 的容量必须至少为:

frame_capacity * RX_bytes_per_frame
返回码条件
IM_OK读取操作成功,包括本次读到 0 帧。
IM_ERR_INVALID_PARAM指针为空或 frame_capacity == 0。
IM_ERR_NOT_READY当前不处于 LISTENING。
IM_ERR_IOFIFO 读取失败。

RX_READY 在中断上下文产生。应用应在 callback 中及时调用该接口,或只做有界 的后续处理,避免 IRQ storm 和 RX FIFO 溢出。

5.2 停止监听

error_code_t im_spi_slv_listen_stop(im_spi_slv_t *dev);
  • 当前正在监听:关闭 RX IRQ、禁用 RX 数据通路、复位 SPI_SLV,并回到IM_SPI_SLV_STATE_READY。

  • 当前未监听但不是 RESET:无操作并返回 IM_OK。

  • 当前为 RESET:返回 IM_ERR_NOT_READY。

停止监听不会产生 IM_SPI_SLV_EVENT_TRANSFER_ABORTED。

6. 中止、状态和进度接口

6.1 中止

error_code_t im_spi_slv_abort(im_spi_slv_t *dev);

im_spi_slv_abort() 可以中止 BUSY 或 LISTENING 操作:

  1. 停止活动 DMA 通道。

  2. 关闭 SPI_SLV IRQ source 并禁用当前方向的数据通路。

  3. 复位 SPI_SLV 硬件。

  4. 唤醒正在等待的线程。

  5. 回到 READY,并把 last_status 设置为 IM_ERR_NOT_READY。

  6. 若配置了 callback,发送 IM_SPI_SLV_EVENT_TRANSFER_ABORTED。

设备处于 RESET 时返回 IM_ERR_NOT_READY。没有活动操作时返回 IM_OK, 不产生事件。

6.2 状态和时钟查询

im_spi_slv_state_t im_spi_slv_get_state(const im_spi_slv_t *dev);
error_code_t im_spi_slv_get_last_status(const im_spi_slv_t *dev);
error_code_t im_spi_slv_get_clock_info(
    const im_spi_slv_t *dev,
    im_spi_slv_clock_info_t *clock_info_out);
接口说明
im_spi_slv_get_state()返回当前状态;dev == NULL 返回 RESET。
im_spi_slv_get_last_status()返回最近记录的状态码;dev == NULL 返回 IM_ERR_INVALID_PARAM。
im_spi_slv_get_clock_info()返回父时钟和有效模块时钟快照。

last_status 在 start 或 listen_start 布防后可能为IM_ERR_NOT_READY,完成或失败后才记录最终结果。应用应以事件或 wait返回值判断固定长度传输结果。

im_spi_slv_get_clock_info() 在 dev 或输出参数为 NULL 时返回IM_ERR_INVALID_PARAM,设备尚未初始化时返回 IM_ERR_NOT_READY。

6.3 进度快照

error_code_t im_spi_slv_get_progress(
    const im_spi_slv_t *dev,
    im_spi_slv_progress_t *progress_out);

该接口在临界区内读取计数,返回与中断安全一致的软件快照。参数为空时返回IM_ERR_INVALID_PARAM。

计数含义:

字段含义
frame_count当前或最近一笔固定长度传输的请求帧数。
tx_frames_queued已写入 FIFO/DMA 硬件路径的 TX 帧数。
rx_frames_completed已写入调用方 RX 缓冲区的帧数。
tx_frames_remaining尚未进入硬件路径的 TX 帧数。
rx_frames_remaining尚未写入调用方 RX 缓冲区的帧数。

连续接收时 frame_count 为 0,rx_frames_completed 是read_available 已消费的累计帧数。

7. SPI Slave MID 状态转换

STATE  RESET
→
STATE  READY
→
STATE  BUSY
→
STATE  LISTENING
→
STATE  TIMEOUT
→
STATE  ERROR

RESET → READY · im_spi_slv_init()

READY → BUSY · im_spi_slv_start()

BUSY → READY / ERROR / TIMEOUT · 传输成功 / 错误 / 超时

READY / ERROR / TIMEOUT → LISTENING · im_spi_slv_listen_start()(IRQ 模式)

LISTENING → READY · listen_stop() / abort()

LISTENING → ERROR · IRQ / FIFO 错误

BUSY / LISTENING → READY · im_spi_slv_abort()

ERROR 和 TIMEOUT 可通过下一次 start 或 listen_start 重新进入业务流程。

补充说明:

  • ERROR 和 TIMEOUT 可通过新的 start 或 listen_start 重新布防。

  • im_spi_slv_deinit() 正常完成后为 RESET;清理失败时为 ERROR。

  • 状态变更接口由调用方串行化;不要从多个线程同时 start、wait、listen、abort 或 deinit 同一个设备对象。

8. 传输模式和资源约束

8.1 DMA 和 cache

manage_dma_cache == true 时,MID 在 DMA 传输前 clean TX 缓冲区,并对 RX 缓冲区执行 clean/invalidate;RX DMA 完成后再次 invalidate RX 缓冲区。

若应用自行管理 cache,可关闭该选项,但必须保证 DMA 和 CPU 看到的数据一致。

DMA 模式下:

  • 至少 TX/RX 一侧非 NULL。

  • 每个非 NULL 缓冲区都必须按 IM_SPI_SLV_DMA_BUFFER_ALIGN 对齐。

  • 不需要 DMA 的方向可以不提供缓冲区。

  • TX/RX DMA 使用对应 HAL 位宽决定总线宽度和每帧字节数。

8.2 超时和错误

  • 固定长度传输超时:状态进入 TIMEOUT,产生 timeout 事件并返回IM_ERR_TIMEOUT。

  • HAL、FIFO 或 DMA 错误:状态进入 ERROR,产生 error 事件。

  • 连续接收中发生硬件/FIFO 错误:退出 LISTENING,状态进入 ERROR,并产生IM_SPI_SLV_EVENT_TRANSFER_ERROR。

  • 显式中止:状态回到 READY,并产生 aborted 事件。

  • im_spi_slv_deinit() 隐式中止活动操作时不发送 aborted 事件。

9. 典型用法

9.1 固定长度全双工传输

im_spi_slv_t slave;
im_spi_slv_config_t config;
im_spi_slv_transfer_t transfer;
error_code_t ret;

im_spi_slv_config_struct_init(&config);
config.instance = IM_SPI_SLV_INSTANCE_1;
config.transfer_mode = IM_SPI_SLV_TRANSFER_IRQ;
config.irq_id = IRQ_SSLV1;
config.callback = app_spi_slv_callback;
config.user_data = app_context;

ret = im_spi_slv_init(&slave, &config);
if (ret == IM_OK) {
    transfer.tx_buffer = slave_tx;
    transfer.rx_buffer = slave_rx;
    transfer.frame_count = 64U;

    ret = im_spi_slv_start(&slave, &transfer);
}

if (ret == IM_OK) {
    /* 外部主机现在拉 CS 并提供 SCLK。 */
    ret = im_spi_slv_wait(&slave, 1000U);
}

(void)im_spi_slv_deinit(&slave);

9.2 Polling 模式配合本机主机

ret = im_spi_slv_start(&slave, &transfer);
if (ret == IM_OK) {
    ret = local_spi_master_transfer();
}
if (ret == IM_OK) {
    ret = im_spi_slv_wait(&slave, 1000U);
}

从机先布防,主机后提供 CS/SCLK。不要在同一线程中先调用阻塞的im_spi_slv_transfer(),再尝试启动本机主机。

9.3 连续接收

static void app_spi_slv_callback(im_spi_slv_t *dev,
                                 im_spi_slv_event_t event,
                                 void *user_data)
{
    uint8_t buffer[64];
    size_t count;

    if (event == IM_SPI_SLV_EVENT_RX_READY) {
        (void)im_spi_slv_read_available(dev, buffer, sizeof(buffer), &count);
        /* count 个帧需要按应用设计尽快转移或记录。 */
    }
}

实际应用应提供足够大的缓冲区或使用有界环形队列。连续接收期间不能调用im_spi_slv_wait();应通过 RX_READY 和 read_available() 消费数据。

结束监听:

(void)im_spi_slv_listen_stop(&slave);