API GUIDE / SPI / Middleware
G32S10 SPI Middleware 接口
Middleware 层接口:im_spi.c 仅在 CONFIG_DRIVER_SPI 生效时编译; im_gpio_spi_bus.c 仅在 CONFIG_DRIVER_GPIO_SPI 生效时编译。
1. 构建配置与文件组织
1.1 源文件职责
| 文件 | 说明 |
|---|---|
code/middleware/spi/include/im_spi.h | 硬件 SPI MID 配置、状态、事件和传输 API |
code/middleware/spi/include/im_spi_bus.h | 通用片选策略、传输描述、后端操作表和总线 API |
code/middleware/spi/include/im_spi_dev.h | 逻辑设备配置、总线注册和设备 API |
code/middleware/spi/include/im_gpio_spi_bus.h | GPIO SPI 总线后端绑定 API |
code/middleware/spi/src/im_spi.c | 硬件 SPI Polling/IRQ/DMA 实现 |
code/middleware/spi/src/im_spi_bus.c | 通用总线与片选接口实现 |
code/middleware/spi/src/im_spi_dev.c | 静态总线注册表和逻辑设备实现 |
code/middleware/spi/src/im_gpio_spi_bus.c | GPIO SPI 后端适配实现 |
im_spi.c 仅在 CONFIG_DRIVER_SPI 生效时编译;im_gpio_spi_bus.c 仅在 CONFIG_DRIVER_GPIO_SPI 生效时编译。
2. 公共宏、枚举和数据结构
2.1 公共宏
| 宏 | 取值/约束 | 说明 |
|---|---|---|
IM_SPI_DEFAULT_TIMEOUT_MS | 1000U | 超时参数或配置值为 0 时使用的默认值。 |
IM_SPI_DMA_BUFFER_ALIGN | IM_CACHE_MAX_LINE_SIZE_BYTES | DMA TX/RX 缓冲区起始地址要求的对齐值。 |
IM_SPI_BUS_ID_INVALID | UINT32_MAX | 无效或尚未选择的总线编号。 |
IM_SPI_BUS_REGISTRY_SIZE | 默认 8U | 静态总线注册表容量,可在构建时覆盖。 |
2.2 传输模式
typedef enum {
IM_SPI_TRANSFER_POLLING = 0,
IM_SPI_TRANSFER_IRQ,
IM_SPI_TRANSFER_DMA,
} im_spi_transfer_mode_t;| 模式 | 数据搬运主体 | im_spi_start() 成功返回的含义 |
|---|---|---|
IM_SPI_TRANSFER_POLLING | 调用线程轮询 FIFO | 整笔传输已经完成或失败。 |
IM_SPI_TRANSFER_IRQ | SPI IRQ 服务 FIFO | 传输已经启动,完成结果通过 callback 或 im_spi_wait() 获取。 |
IM_SPI_TRANSFER_DMA | IM DMA 通道 | 传输已经启动,完成结果通过 callback 或 im_spi_wait() 获取。 |
默认模式为 IM_SPI_TRANSFER_POLLING。
2.3 运行状态
typedef enum {
IM_SPI_STATE_RESET = 0,
IM_SPI_STATE_READY,
IM_SPI_STATE_BUSY,
IM_SPI_STATE_ERROR,
IM_SPI_STATE_TIMEOUT,
} im_spi_state_t;| 状态 | 说明 |
|---|---|
IM_SPI_STATE_RESET | 尚未初始化或已经反初始化。 |
IM_SPI_STATE_READY | 已初始化且空闲。 |
IM_SPI_STATE_BUSY | 当前有一笔传输正在执行。 |
IM_SPI_STATE_ERROR | 最近一次传输或控制操作失败。 |
IM_SPI_STATE_TIMEOUT | 最近一次传输因等待超时而终止。 |
ERROR 和 TIMEOUT 不是不可恢复的终态。再次调用 im_spi_start() 时,MID
会重新禁用、清空并配置控制器;下一次成功传输可回到 READY。
2.4 事件和回调
typedef enum {
IM_SPI_EVENT_TRANSFER_DONE = 0,
IM_SPI_EVENT_TRANSFER_ERROR,
IM_SPI_EVENT_TRANSFER_TIMEOUT,
} im_spi_event_t;
typedef void (*im_spi_callback_t)(im_spi_t *dev,
im_spi_event_t event,
void *user_data);| 事件 | 触发条件 |
|---|---|
IM_SPI_EVENT_TRANSFER_DONE | 传输成功完成。 |
IM_SPI_EVENT_TRANSFER_ERROR | 硬件、FIFO、DMA、片选或控制路径失败。 |
IM_SPI_EVENT_TRANSFER_TIMEOUT | 等待超时,MID 已中止活动传输。 |
IRQ 和 DMA 模式的事件回调可能运行在中断上下文;回调中不能阻塞、休眠、 等待互斥锁或执行耗时操作。Polling 模式的事件回调在调用线程中执行。
3. 片选接口
3.1 片选类型
typedef enum {
IM_SPI_CS_MODE_NONE = 0U,
IM_SPI_CS_MODE_HARDWARE,
IM_SPI_CS_MODE_SOFTWARE,
} im_spi_cs_mode_t;
typedef enum {
IM_SPI_CS_ACTIVE_LOW = 0U,
IM_SPI_CS_ACTIVE_HIGH = 1U,
} im_spi_cs_active_level_t;typedef struct {
im_spi_cs_mode_t mode;
uint32_t hardware_index;
im_spi_cs_active_level_t active_level;
im_spi_cs_prepare_t prepare;
im_spi_cs_control_t control;
void *context;
} im_spi_chip_select_config_t;| 字段 | 说明 |
|---|---|
mode | 禁用、硬件片选或软件片选。 |
hardware_index | 控制器片选索引,仅硬件模式使用;当前 HAL 支持 0 和 1。 |
active_level | 片选有效电平。 |
prepare | 可选板级准备回调,用于 pinmux 或 GPIO 方向配置。 |
control | 软件片选控制回调;软件模式必须非 NULL。 |
context | 原样传给 prepare 和 control。 |
3.2 通用片选配置
void im_spi_chip_select_config_struct_init(im_spi_chip_select_config_t *config); error_code_t im_spi_chip_select_hardware_config( im_spi_chip_select_config_t *config, uint32_t hardware_index, im_spi_cs_active_level_t active_level, im_spi_cs_prepare_t prepare, void *context); error_code_t im_spi_chip_select_software_config( im_spi_chip_select_config_t *config, im_spi_cs_active_level_t active_level, im_spi_cs_prepare_t prepare, im_spi_cs_control_t control, void *context); void im_spi_chip_select_disable(im_spi_chip_select_config_t *config);
| 接口 | 行为与返回 |
|---|---|
im_spi_chip_select_config_struct_init() | 将策略初始化为 IM_SPI_CS_MODE_NONE 和低有效;NULL 被忽略。 |
im_spi_chip_select_hardware_config() | 配置硬件片选;参数无效返回 IM_ERR_INVALID_PARAM。 |
im_spi_chip_select_software_config() | 配置软件片选;control == NULL 时返回 IM_ERR_INVALID_PARAM。 |
im_spi_chip_select_disable() | 清零策略并设置为 IM_SPI_CS_MODE_NONE。 |
4. 硬件 SPI MID 接口
4.1 配置初始化
void im_spi_config_struct_init(im_spi_config_t *config);
填充 4.5 节列出的默认值。调用方随后至少应设置
4.2 初始化和反初始化
error_code_t im_spi_init(im_spi_t *dev, const im_spi_config_t *config); error_code_t im_spi_deinit(im_spi_t *dev);
im_spi_init():
复制配置并校验片选策略。
初始化 SPI HAL,配置 module clock,并保持控制器 disabled。
对 IRQ 模式注册并使能
config.irq_id。调用片选
prepare,然后将片选置为无效状态。成功后将状态设置为
IM_SPI_STATE_READY。
主要返回码:
| 返回码 | 条件 |
|---|---|
IM_OK | 初始化成功。 |
IM_ERR_INVALID_PARAM | 指针为空、片选策略非法、IRQ 编号非法或 HAL 配置非法。 |
IM_ERR_NOT_READY | IRQ 或片选准备资源不可用。 |
IM_ERR_BUSY | IRQ 等平台资源已经被占用。 |
同一 im_spi_t 在重新初始化前应先调用 im_spi_deinit()。初始化成功后,
应用不得直接修改 im_spi_t 内部运行字段。
im_spi_deinit() 会先中止活动传输,然后 deassert 软件片选、释放 SoC IRQ、
释放 DMA 通道并反初始化 HAL。返回第一个清理错误;无论返回值如何,对象的公开
状态会回到 IM_SPI_STATE_RESET。
4.3 修改串行时钟
error_code_t im_spi_set_clock(im_spi_t *dev, uint32_t clock_hz);
仅允许在设备空闲时修改串行时钟。clock_hz 必须非零;当前实现不会把im_spi_set_clock(..., 0) 转换为 HAL 默认值。失败时 HAL 配置不会被修改。
| 返回码 | 条件 |
|---|---|
IM_OK | 时钟配置成功。 |
IM_ERR_INVALID_PARAM | dev == NULL 或 clock_hz == 0。 |
IM_ERR_NOT_READY | dev 尚未完成 HAL 初始化。 |
IM_ERR_BUSY | 当前状态为 IM_SPI_STATE_BUSY。 |
HAL 可能把请求时钟量化为分频器可表达的频率。实际频率保存在im_spi_clock_info_t::sclk_hz,应用应通过 im_spi_get_clock_info() 查询,
不应读取 HAL 句柄内部字段。
4.4 启动、等待和同步传输
error_code_t im_spi_start(im_spi_t *dev, const im_spi_transfer_t *transfer); error_code_t im_spi_wait(im_spi_t *dev, uint32_t timeout_ms); error_code_t im_spi_transfer(im_spi_t *dev, const im_spi_transfer_t *transfer, uint32_t timeout_ms);
im_spi_start() 按配置模式分派:
Polling:同步执行整笔传输;返回
IM_OK表示已经完成。IRQ/DMA:完成参数校验、资源准备和硬件启动后返回;返回
IM_OK表示传输已经 成功启动,不表示传输已经完成。
im_spi_wait() 用于等待已经启动的 IRQ/DMA 传输。timeout_ms == 0 时使用IM_SPI_DEFAULT_TIMEOUT_MS。若等待超时且传输仍为 BUSY,MID 会中止活动
DMA、关闭 IRQ/DMA 请求、禁用控制器、deassert 软件片选,并返回IM_ERR_TIMEOUT。无活动传输时返回最近一次记录的状态。
im_spi_transfer() 是同步便利接口:
Polling:直接执行整笔传输。
IRQ/DMA:等价于
im_spi_start()后调用im_spi_wait()。
timeout_ms == 0 对这三个接口统一使用 1000 ms 默认值。
4.5 状态和时钟查询
im_spi_state_t im_spi_get_state(const im_spi_t *dev); error_code_t im_spi_get_last_status(const im_spi_t *dev); error_code_t im_spi_get_clock_info( const im_spi_t *dev, im_spi_clock_info_t *clock_info_out);
| 接口 | 说明 |
|---|---|
im_spi_get_state() | 返回当前状态;dev == NULL 返回 IM_SPI_STATE_RESET。 |
im_spi_get_last_status() | 返回最近一次记录的状态码;dev == NULL 返回 IM_ERR_INVALID_PARAM。 |
im_spi_get_clock_info() | 返回父时钟、有效模块时钟和实际串行时钟快照。 |
im_spi_get_clock_info() 在 dev 或输出参数为 NULL 时返回IM_ERR_INVALID_PARAM,设备尚未初始化时返回 IM_ERR_NOT_READY。
4.6 硬件 MID 返回码汇总
| 返回码 | 典型原因 |
|---|---|
IM_OK | 操作成功或异步传输已经成功启动。 |
IM_ERR_INVALID_PARAM | 空指针、传输描述非法、IRQ 编号非法、DMA 对齐全失败或 HAL 配置非法。 |
IM_ERR_NOT_READY | 设备尚未初始化、HAL 未就绪、FIFO/DMA 状态异常。 |
IM_ERR_BUSY | 已有一笔传输正在执行。 |
IM_ERR_TIMEOUT | 等待传输完成超时。 |
IM_ERR_IO | HAL、DMA 或总线适配层报告 I/O 错误。 |
5. 通用 bus 接口
5.1 后端操作表
typedef error_code_t (*im_spi_bus_configure_fn)(
void *context, const im_spi_device_config_t *config);
typedef error_code_t (*im_spi_bus_init_fn)(void *context);
typedef error_code_t (*im_spi_bus_deinit_fn)(void *context);
typedef error_code_t (*im_spi_bus_transfer_fn)(
void *context,
const im_spi_transfer_t *transfer,
uint32_t timeout_ms);
typedef struct {
im_spi_bus_configure_fn configure;
im_spi_bus_init_fn init;
im_spi_bus_deinit_fn deinit;
im_spi_bus_transfer_fn transfer;
} im_spi_bus_ops_t;
typedef struct {
const im_spi_bus_ops_t *ops;
void *context;
} im_spi_bus_t;四个操作函数都必须非 NULL,context 也必须非 NULL。ops 和 context在总线使用期间必须保持有效。
5.2 总线生命周期
void im_spi_bus_struct_init(im_spi_bus_t *bus); error_code_t im_spi_bus_bind(im_spi_bus_t *bus, const im_spi_bus_ops_t *ops, void *context); error_code_t im_spi_bus_configure( im_spi_bus_t *bus, const im_spi_device_config_t *config); error_code_t im_spi_bus_init(im_spi_bus_t *bus); error_code_t im_spi_bus_transfer( im_spi_bus_t *bus, const im_spi_transfer_t *transfer, uint32_t timeout_ms); error_code_t im_spi_bus_deinit(im_spi_bus_t *bus);
| 接口 | 行为 | 主要返回 |
|---|---|---|
im_spi_bus_struct_init() | 清空 ops 和 context,不访问后端。 | 无 |
im_spi_bus_bind() | 绑定操作表和后端上下文。 | 参数非法返回 IM_ERR_INVALID_PARAM。 |
im_spi_bus_configure() | 把逻辑设备配置应用到后端。 | 未绑定返回 IM_ERR_NOT_READY。 |
im_spi_bus_init() | 初始化绑定后端。 | 未绑定返回 IM_ERR_NOT_READY。 |
im_spi_bus_transfer() | 校验传输描述并调用后端同步传输。 | 传输描述非法返回 IM_ERR_INVALID_PARAM。 |
im_spi_bus_deinit() | 反初始化绑定后端。 | 未绑定返回 IM_ERR_NOT_READY。 |
普通应用通常不直接调用 im_spi_bus_configure/init/transfer/deinit,而是通过im_spi_device_* 接口使用已注册总线。
6. 逻辑设备接口
6.1 注册、打开、传输和关闭
void im_spi_device_struct_init(im_spi_device_t *device); void im_spi_device_config_struct_init(im_spi_device_config_t *config); error_code_t im_spi_bus_register(uint32_t bus_id, im_spi_bus_t *bus); error_code_t im_spi_bus_unregister(uint32_t bus_id); error_code_t im_spi_device_open( im_spi_device_t *device, const im_spi_device_config_t *config); error_code_t im_spi_device_transfer( im_spi_device_t *device, const im_spi_transfer_t *transfer, uint32_t timeout_ms); error_code_t im_spi_device_close(im_spi_device_t *device);
| 接口 | 行为 | 主要返回 |
|---|---|---|
im_spi_device_struct_init() | 清空设备句柄并设置无效 bus_id。 | 无 |
im_spi_device_config_struct_init() | 填充 8.1 节默认配置。 | 无 |
im_spi_bus_register() | 将已绑定总线注册到应用可见编号。 | IM_ERR_ALREADY_REGISTERED、IM_ERR_NO_MEMORY。 |
im_spi_bus_unregister() | 注销一个未打开的总线。 | 未找到返回 IM_ERR_NOT_FOUND,已打开返回 IM_ERR_BUSY。 |
im_spi_device_open() | 查找 bus_id,配置并初始化后端。 | 未找到、总线忙或后端配置失败。 |
im_spi_device_transfer() | 通过已打开设备执行同步传输。 | 未打开返回 IM_ERR_NOT_READY。 |
im_spi_device_close() | 反初始化后端并清除本地设备句柄。 | 返回后端 deinit 结果。 |
约束:
同一
im_spi_bus_t对象只能注册一次。同一
bus_id只能注册一次。注册表最多容纳
IM_SPI_BUS_REGISTRY_SIZE条记录。一条已注册总线同时只能打开一个逻辑设备。
必须先关闭逻辑设备,再注销总线。
im_spi_device_close()即使后端 deinit 返回错误,也会清除本地设备句柄并释放 总线的打开占用,返回值只用于报告该次清理的结果。im_spi_device_config_t::clock_hz必须非零,bits_per_word必须在 1 至 32 之间。硬件后端还会进一步要求正数帧宽至少为 2 bit。
7. 后端绑定
7.1 GPIO SPI 后端
error_code_t im_gpio_spi_bus_bind(im_spi_bus_t *bus, im_gpio_spi_bus_t *gpio_bus, im_gpio_spi_t *gpio_spi);
三个对象均由调用方提供存储,并在总线使用期间保持有效。gpio_spi 的引脚描述
必须已经由 BSP 或应用完成配置和占用。
GPIO 后端约束:
仅支持标准 SPI 格式。
仅支持同步软件传输,不支持硬件 MID 的 Polling/IRQ/DMA 模式选择。
不支持
IM_SPI_CS_MODE_HARDWARE,返回IM_ERR_UNSUPPORTED。支持
IM_SPI_CS_MODE_NONE或IM_SPI_CS_MODE_SOFTWARE。clock_hz在 1 至IM_GPIO_SPI_MAX_CLOCK_HZ之间,当前最大为 500 kHz。bits_per_word在 1 至 32 之间。timeout_ms == 0时 GPIO 后端使用 1000 ms 默认值。
8. 运行语义和资源约束
8.1 超时和中止
Polling 超时后 MID 禁用控制器、清空 FIFO、deassert 软件片选并返回
IM_ERR_TIMEOUT。IRQ/DMA 的
im_spi_wait()超时后先停止活动 DMA,再完成相同的硬件收尾。超时最终状态为
IM_SPI_STATE_TIMEOUT,并产生IM_SPI_EVENT_TRANSFER_TIMEOUT。im_spi_deinit()可以在活动传输期间调用,会先停止 DMA/IRQ 数据路径。
9. 典型用法
9.1 通过统一设备接口使用
平台: 初始化后端配置 im_spi_bus_struct_init() im_spi_bus_bind_hardware() 或 im_gpio_spi_bus_bind() im_spi_bus_register() 应用: im_spi_device_struct_init() im_spi_device_config_struct_init() 设置 bus_id、clock、bit width、SPI mode 和 chip_select im_spi_device_open() im_spi_device_transfer() im_spi_device_close() 清理: im_spi_bus_unregister()
9.2 IRQ/DMA 异步启动
ret = im_spi_start(&spi, &transfer);
if (ret == IM_OK) {
/* 可以在这里执行其他工作;缓冲区必须保持有效。 */
ret = im_spi_wait(&spi, 1000U);
}若配置了 callback,完成、错误或超时事件也可能先于 im_spi_wait() 返回。
应用应使用 im_spi_wait() 的返回值作为最终同步结果,或确保 callback 和等待
线程之间的状态同步正确。