API GUIDE / SPI Slave / Middleware
G32S10 SPI Slave Middleware 接口
Middleware 层接口:MID 组件依赖 im_dma。
1. 构建配置与文件组织
1.1 文件职责
| 文件 | 说明 |
|---|---|
code/middleware/spi_slv/include/im_spi_slv.h | MID 公开宏、类型、配置和全部 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() 的线程搬运 FIFO | wait 返回。 |
IM_SPI_SLV_TRANSFER_IRQ | SPI_SLV IRQ 搬运 FIFO | IRQ 完成并唤醒 wait,同时产生事件。 |
IM_SPI_SLV_TRANSFER_DMA | IM 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_DONE | RX-only 或全双工传输成功完成。 |
IM_SPI_SLV_EVENT_TX_QUEUED | TX-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_buffer | rx_buffer | 传输方向 |
|---|---|---|
非 NULL | NULL | TX-only |
NULL | 非 NULL | RX-only |
非 NULL | 非 NULL | 全双工 |
NULL | NULL | 非法参数 |
约束:
frame_count表示帧数,不是字节数,范围必须为 1 至UINT32_MAX。TX 和 RX 使用同一
frame_count。缓冲区由调用方拥有,并在完成、错误、超时或 abort 前保持有效。
DMA 模式只要求非 NULL 缓冲区地址按
IM_SPI_SLV_DMA_BUFFER_ALIGN对齐。DMA 模式下,帧数乘以每帧字节数后传输长度还必须能放入
uint32_t。
每帧内存宽度由对应方向的 HAL 位宽决定:
| 位宽 | 每帧内存占用 |
|---|---|
| 4 至 8 bit | 1 byte |
| 9 至 16 bit | 2 bytes |
| 17 至 32 bit | 4 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():
校验指针和
transfer_mode。校验逻辑
instance,映射为SPI_SLV0/1并初始化 SPI_SLV HAL。IRQ 模式注册 HAL 回调,申请并使能
config.irq_id。成功后将状态设置为
IM_SPI_SLV_STATE_READY。
DMA 通道不会在 init 时申请,而是在第一次 DMA 传输时按需打开。
| 返回码 | 条件 |
|---|---|
IM_OK | 初始化成功。 |
IM_ERR_INVALID_PARAM | 空指针、传输模式非法、实例非法或 HAL 配置非法。 |
IM_ERR_NOT_READY | 中断等平台资源尚未就绪。 |
IM_ERR_BUSY | IRQ 等平台资源已被占用。 |
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_IO | HAL、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);
超时选择顺序:
timeout_ms非零时使用参数值。参数为
0且config.timeout_ms非零时使用配置值。两者都为
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_IO | FIFO 读取失败。 |
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 操作:
停止活动 DMA 通道。
关闭 SPI_SLV IRQ source 并禁用当前方向的数据通路。
复位 SPI_SLV 硬件。
唤醒正在等待的线程。
回到
READY,并把last_status设置为IM_ERR_NOT_READY。若配置了 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 状态转换
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);