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.hGPIO 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.cGPIO SPI 后端适配实现

im_spi.c 仅在 CONFIG_DRIVER_SPI 生效时编译;im_gpio_spi_bus.c 仅在 CONFIG_DRIVER_GPIO_SPI 生效时编译。

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

2.1 公共宏

宏取值/约束说明
IM_SPI_DEFAULT_TIMEOUT_MS1000U超时参数或配置值为 0 时使用的默认值。
IM_SPI_DMA_BUFFER_ALIGNIM_CACHE_MAX_LINE_SIZE_BYTESDMA TX/RX 缓冲区起始地址要求的对齐值。
IM_SPI_BUS_ID_INVALIDUINT32_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_IRQSPI IRQ 服务 FIFO传输已经启动,完成结果通过 callback 或 im_spi_wait() 获取。
IM_SPI_TRANSFER_DMAIM 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():

  1. 复制配置并校验片选策略。

  2. 初始化 SPI HAL,配置 module clock,并保持控制器 disabled。

  3. 对 IRQ 模式注册并使能 config.irq_id。

  4. 调用片选 prepare,然后将片选置为无效状态。

  5. 成功后将状态设置为 IM_SPI_STATE_READY。

主要返回码:

返回码条件
IM_OK初始化成功。
IM_ERR_INVALID_PARAM指针为空、片选策略非法、IRQ 编号非法或 HAL 配置非法。
IM_ERR_NOT_READYIRQ 或片选准备资源不可用。
IM_ERR_BUSYIRQ 等平台资源已经被占用。

同一 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_PARAMdev == NULL 或 clock_hz == 0。
IM_ERR_NOT_READYdev 尚未完成 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_IOHAL、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 和等待 线程之间的状态同步正确。