API GUIDE / I2C / Middleware
G32S10 I2C Middleware 接口
Middleware 层接口:说明:
1. 资源模型与公开类型
1.1 模块能力摘要
| 项目 | HAL Backend | GPIO Backend |
|---|---|---|
| 控制器 | G32S10 I2C0、I2C1、I2C2 | BSP 指定的任意 SCL/SDA GPIO |
| 目标地址 | 7-bit,0x00~0x7F | 7-bit,0x00~0x7F |
| 消息数量 | 1~IM_I2C_MAX_MESSAGE_COUNT | 1~IM_I2C_MAX_MESSAGE_COUNT |
| 传输模型 | 同步等待 HAL IRQ 完成 | 同步 GPIO 时序传输 |
| 速率 | 由 HAL 配置的 speed/bitrate 决定 | 1~IM_GPIO_I2C_MAX_BITRATE_HZ |
| 多消息时序 | 消息之间产生 repeated START | 消息之间产生 repeated START |
| 超时恢复 | 中止 HAL master transfer | 执行 GPIO 总线恢复 |
| IRQ 所有权 | Middleware 注册并持有对应 SoC IRQ | 不占用 I2C SoC IRQ |
| 配置存储 | Middleware 复制 HAL 配置 | 调用方提供并保持 GPIO 句柄有效 |
| 板级引脚 | BSP 配置硬件 I2C pinmux | BSP 申请并配置 GPIO 输入状态 |
1.2 总线与设备生命周期
说明:
总线注册时立即调用后端的
init回调。设备注册只建立逻辑目标映射,不初始化硬件。
总线仍有已注册设备时,总线注销返回
IM_ERR_BUSY。总线注销时调用后端
deinit,成功后才从全局总线列表移除。总线对象、设备名称和总线名称在注销前必须保持有效。
1.3 全局注册表
中间层使用一个全局总线列表:
总线 ID 类型为
uint8_t,在整个固件内唯一;同一个 ID 不能同时注册两种后端。同一个总线对象重复注册返回
IM_ERR_ALREADY_REGISTERED。不同总线对象使用相同
bus_id返回IM_ERR_ALREADY_EXISTS。同一总线上相同 7-bit 地址只能注册一个逻辑设备。
同一个物理器件若可通过多个地址访问,应分别注册多个设备对象。
所有总线查找和设备地址检查均受互斥锁保护。
1.4 公开宏
| 宏 | 使用说明 | 取值/约束 |
|---|---|---|
IM_I2C_MAX_MESSAGE_COUNT | 单次 transfer 的最大消息数量。 | 8。 |
IM_I2C_ADDRESS_7BIT_MAX | 7-bit 目标地址最大值。 | 0x7F。 |
IM_GPIO_I2C_MAX_BITRATE_HZ | GPIO Backend 支持的最大速率。 | 100000。 |
IM_GPIO_I2C_ADDRESS_MAX | GPIO 驱动支持的最大 7-bit 地址。 | 0x7F。 |
1.5 消息方向
typedef enum {
IM_I2C_DIRECTION_WRITE = 0U,
IM_I2C_DIRECTION_READ = 1U,
} im_i2c_direction_t;1.6 消息描述
typedef struct {
im_i2c_direction_t direction;
const uint8_t *write_data;
uint8_t *read_data;
size_t data_len;
} im_i2c_msg_t;| 字段 | 说明 |
|---|---|
direction | IM_I2C_DIRECTION_WRITE 或 IM_I2C_DIRECTION_READ。 |
write_data | 写消息的数据指针,写方向必须非 NULL。 |
read_data | 读消息的数据指针,读方向必须非 NULL。 |
data_len | 本条消息的字节数,必须大于 0。 |
消息约束:
每条消息都必须是非空消息,当前接口不接受零长度占位消息。
单次 transfer 支持 1~8 条消息。
第 2 条及后续消息默认使用 repeated START。
最后一条消息完成后产生一个 STOP。
只有一条读消息时,从目标当前地址开始读取。
1.7 返回状态约定
中间层可失败接口统一返回 SDK 公共错误码 error_code_t。
| 返回值 | 说明 |
|---|---|
IM_OK | 操作成功。 |
IM_ERR_INVALID_PARAM | 空指针、地址越界、消息为空、方向非法、长度为 0、消息数超限或超时为 0。 |
IM_ERR_NOT_READY | 总线或设备未注册、已注销,或后端操作表不可用。 |
IM_ERR_BUSY | 总线正被占用、仍有设备依赖总线,或底层设备忙。 |
IM_ERR_NO_MEMORY | Backend context 或逻辑设备分配失败。 |
IM_ERR_ALREADY_EXISTS | 总线 ID 或同一总线上的设备地址重复。 |
IM_ERR_ALREADY_REGISTERED | 同一个总线对象已经注册。 |
IM_ERR_NOT_FOUND | 目标地址或数据未收到 ACK。 |
IM_ERR_TIMEOUT | 等待传输完成或 GPIO 时钟拉伸超时。 |
IM_ERR_IO | 底层 I/O 失败或业务数据校验失败。 |
| 其他错误码 | HAL、IRQ、GPIO 或 OS 后端透传的错误码。 |
2. Device API
2.1 设备注册
2.1.1 im_i2c_device_register
error_code_t im_i2c_device_register(im_i2c_device_t **device_out, uint8_t bus_id, uint8_t address, const char *name);
功能说明:
在已注册的总线上创建一个逻辑设备,并保存目标地址和名称。设备对象由中间层 通过 heap 分配,调用方持有不透明句柄。
参数表:
| 参数 | 类型 | 方向 | 说明 |
|---|---|---|---|
device_out | im_i2c_device_t ** | 输出 | 接收设备句柄;失败时写入 NULL。 |
bus_id | uint8_t | 输入 | 已注册总线 ID。 |
address | uint8_t | 输入 | 7-bit 目标地址,最大 0x7F。 |
name | const char * | 输入 | 设备名称,注册期间必须保持有效。 |
返回值表:
| 返回值 | 说明 |
|---|---|
IM_OK | 设备注册成功。 |
IM_ERR_INVALID_PARAM | 输出指针或名称为空,或地址超过 0x7F。 |
IM_ERR_NOT_READY | 指定总线未注册。 |
IM_ERR_ALREADY_EXISTS | 该总线上已存在相同地址的设备。 |
IM_ERR_NO_MEMORY | 设备对象分配失败。 |
补充说明:
bus_id是逻辑总线编号,不要求等于硬件控制器编号。一个目标芯片包含多个独立地址时,应分别注册。
设备名称通常使用字符串常量或具有静态生命周期的名称。
2.2 设备注销
2.2.1 im_i2c_device_unregister
error_code_t im_i2c_device_unregister(im_i2c_device_t *device);
功能说明:
从所属总线的设备列表中删除逻辑设备并释放设备对象。
参数表:
| 参数 | 类型 | 方向 | 说明 |
|---|---|---|---|
device | im_i2c_device_t * | 输入 | 已注册的设备句柄。 |
返回值表:
| 返回值 | 说明 |
|---|---|
IM_OK | 设备已注销并释放。 |
IM_ERR_INVALID_PARAM | device 为空。 |
IM_ERR_NOT_READY | 设备未注册或已经注销。 |
补充说明:
注销会等待该总线上正在执行的 transfer 或 probe 释放总线锁。
注销完成后不得继续使用原设备句柄。
中间层没有设备引用计数,调用方应保证其他线程不再访问该句柄。
2.3 同步传输
2.3.1 im_i2c_device_transfer
error_code_t im_i2c_device_transfer(im_i2c_device_t *device, const im_i2c_msg_t *msgs, size_t msg_count, uint32_t timeout_ms);
功能说明:
在设备所属总线上执行一组同步消息。后端逐条处理消息,第 2 条及后续消息前 产生 repeated START,最后一条消息后产生 STOP。
参数表:
| 参数 | 类型 | 方向 | 说明 |
|---|---|---|---|
device | im_i2c_device_t * | 输入 | 已注册设备。 |
msgs | const im_i2c_msg_t * | 输入 | 1~8 条非空消息。 |
msg_count | size_t | 输入 | 消息数量,范围 1~8。 |
timeout_ms | uint32_t | 输入 | 传输等待超时,必须非 0。 |
返回值表:
| 返回值 | 说明 |
|---|---|
IM_OK | 所有消息完成。 |
IM_ERR_INVALID_PARAM | 设备、消息、方向、buffer、长度、消息数或超时非法。 |
IM_ERR_NOT_READY | 设备或其所属总线未注册,或后端不可用。 |
IM_ERR_BUSY | 底层总线或控制器忙。 |
IM_ERR_NOT_FOUND | 地址、数据或 general call 未收到 ACK。 |
IM_ERR_TIMEOUT | 等待完成超时;硬件后端会尝试 abort,GPIO 后端会尝试恢复总线。 |
IM_ERR_IO | 底层 I/O、用户 abort 或其他硬件错误。 |
| 其他错误码 | 后端初始化、IRQ 或 GPIO 错误。 |
补充说明:
写 buffer 和读 buffer 在函数返回前必须保持有效。
函数返回后可以修改或释放消息 buffer。
硬件后端的原始完成状态由 HAL 分类;GPIO 后端返回地址/数据 NACK、时钟拉伸和总线竞争等状态。
硬件后端超时后会调用 HAL abort;abort 本身返回非成功值时,上层返回 abort 的错误码。
GPIO 后端超时后会调用一次总线恢复;恢复失败时返回恢复错误,恢复成功时仍返回
IM_ERR_TIMEOUT。
2.4 目标探测
2.4.1 im_i2c_device_probe
error_code_t im_i2c_device_probe(im_i2c_device_t *device, uint32_t timeout_ms);
功能说明:
对已注册的 7-bit 目标执行一次单字节读并丢弃结果,用于确认目标是否响应地址。
参数表:
| 参数 | 类型 | 方向 | 说明 |
|---|---|---|---|
device | im_i2c_device_t * | 输入 | 已注册设备。 |
timeout_ms | uint32_t | 输入 | 探测超时,必须非 0。 |
返回值表:
| 返回值 | 说明 |
|---|---|
IM_OK | 目标响应地址。 |
IM_ERR_INVALID_PARAM | 设备为空或超时为 0。 |
IM_ERR_NOT_READY | 设备、总线或探测后端不可用。 |
IM_ERR_NOT_FOUND | 地址 NACK。 |
IM_ERR_TIMEOUT | 等待超时。 |
| 其他错误码 | 后端透传错误。 |
补充说明:
probe 不是纯地址扫描,它会执行一次真实读操作。
对有读副作用的设备应避免使用 probe,改为读取无副作用的状态寄存器。
同一条总线上的 probe 与 transfer 共用总线锁。
3. Bus API 与后端注册
3.1 总线对象
typedef struct im_i2c_bus im_i2c_bus_t;
im_i2c_bus_t 的完整定义位于 im_i2c_bus.h,其中包含互斥锁和链表节点。
调用方应把总线对象作为静态或长期对象保存:
第一次注册前必须清零,例如
im_i2c_bus_t bus = {0};。注册后不能复制、移动或由另一块内存重新初始化该对象。
注销成功前不能释放总线对象。
应用不应直接修改
bus_id、is_registered、ops、context、lock或链表字段。
3.2 通用后端操作表
typedef struct {
im_i2c_bus_init_fn init;
im_i2c_bus_deinit_fn deinit;
im_i2c_bus_transfer_fn transfer;
im_i2c_bus_probe_fn probe;
} im_i2c_bus_ops_t;四个回调都必须非 NULL:
| 回调 | 调用时机 |
|---|---|
init | 通用总线注册时,在加入全局总线列表之前。 |
deinit | 通用总线注销时,在从全局总线列表移除之前。 |
transfer | 每次 im_i2c_device_transfer() 调用。 |
probe | 每次 im_i2c_device_probe() 调用。 |
普通应用不应自行构造操作表;应使用 HAL Backend 或 GPIO Backend。
3.3 通用总线注册
3.3.1 im_i2c_bus_register
error_code_t im_i2c_bus_register(im_i2c_bus_t *bus, uint8_t bus_id, const char *name, const im_i2c_bus_ops_t *ops, void *context);
功能说明:
注册一个后端无关的 I2C 总线,调用后端 init,并将其加入全局总线列表。
参数表:
| 参数 | 类型 | 方向 | 说明 |
|---|---|---|---|
bus | im_i2c_bus_t * | 输入/输出 | 已清零的调用方总线对象。 |
bus_id | uint8_t | 输入 | 全局唯一逻辑总线 ID。 |
name | const char * | 输入 | 总线名称,注销前必须有效。 |
ops | const im_i2c_bus_ops_t * | 输入 | 完整后端操作表。 |
context | void * | 输入 | 传给四个后端回调的上下文。 |
返回值表:
| 返回值 | 说明 |
|---|---|
IM_OK | 总线注册成功。 |
IM_ERR_INVALID_PARAM | 对象、名称、操作表、回调或 context 为空。 |
IM_ERR_ALREADY_REGISTERED | 同一个总线对象已经注册。 |
IM_ERR_ALREADY_EXISTS | 已有其他总线使用相同 bus_id。 |
| 其他错误码 | 后端 init 失败。 |
补充说明:
后端
init在全局总线列表锁内调用。ops和name指针由总线对象长期保存,必须在注销前保持有效。context的所有权由具体后端决定。通用 Bus API 不释放
context。
3.4 通用总线注销
3.4.1 im_i2c_bus_unregister
error_code_t im_i2c_bus_unregister(im_i2c_bus_t *bus);
功能说明:
在总线没有已注册设备时调用后端 deinit,成功后从全局总线列表移除。
参数表:
| 参数 | 类型 | 方向 | 说明 |
|---|---|---|---|
bus | im_i2c_bus_t * | 输入/输出 | 已注册总线。 |
返回值表:
| 返回值 | 说明 |
|---|---|
IM_OK | 总线已注销。 |
IM_ERR_INVALID_PARAM | bus 为空。 |
IM_ERR_NOT_READY | 总线未注册或缺少后端操作。 |
IM_ERR_BUSY | 仍存在已注册设备。 |
| 其他错误码 | 后端 deinit 失败。 |
补充说明:
注销会获取总线锁,等待正在执行的 transfer/probe 结束。
仍有设备时不会调用后端
deinit。后端
deinit失败时,总线仍保持注册,可修复后重试。
3.5 HAL Backend 注册
3.5.1 im_hal_i2c_bus_register
error_code_t im_hal_i2c_bus_register(im_i2c_bus_t *bus,
uint8_t bus_id,
const char *name,
hal_i2c_init_t *i2c_init);
功能说明:
创建硬件 I2C 后端 context,复制 hal_i2c_init_t 配置,并注册到 I2C 中间层。
参数表:
| 参数 | 类型 | 方向 | 说明 |
|---|---|---|---|
bus |
im_i2c_bus_t * |
输入/输出 | 已清零的调用方总线对象。 |
bus_id |
uint8_t |
输入 | 全局唯一逻辑总线 ID。 |
name |
const char * |
输入 | 总线名称,注销前必须有效。 |
i2c_init |
hal_i2c_init_t * |
输入 | HAL 初始化配置;注册期间复制。 |
返回值表:
| 返回值 | 说明 |
|---|---|
IM_OK |
硬件后端创建并注册成功。 |
IM_ERR_INVALID_PARAM |
参数为空或 HAL 控制器实例非法。 |
IM_ERR_NO_MEMORY |
后端 context 分配失败。 |
| 其他错误码 | 通用总线注册或 HAL/IRQ 初始化错误。 |
补充说明:
- 注册时复制
hal_i2c_init_t的全部字段,调用方可以在注册返回后释放或复用原配置对象。 - 总线对象本身仍必须在注销前保持有效。
hal_i2c_init_t::i2cx必须是I2C0、I2C1或I2C2。- 同一个物理控制器不能同时由多个 I2C middleware 后端管理。
- 通用总线注册失败时,本次创建的后端 context 会被释放。
- 板级 SDA/SCL pinmux 由 BSP 配置,本接口不修改 pinmux。
3.5.2 im_hal_i2c_bus_unregister
error_code_t im_hal_i2c_bus_unregister(im_i2c_bus_t *bus);
功能说明:
注销 HAL Backend 总线;通用注销成功时释放后端 context。
参数表:
| 参数 | 类型 | 方向 | 说明 |
|---|---|---|---|
bus |
im_i2c_bus_t * |
输入/输出 | 已注册的 HAL Backend 总线。 |
返回值表:
| 返回值 | 说明 |
|---|---|
IM_OK |
后端已注销且 context 已释放。 |
IM_ERR_INVALID_PARAM |
bus 为空。 |
IM_ERR_NOT_READY |
总线未注册。 |
IM_ERR_BUSY |
仍有设备注册在该总线。 |
| 其他错误码 | HAL 反初始化或通用总线注销失败。 |
3.6 GPIO Backend 注册
3.6.1 im_gpio_i2c_bus_register
error_code_t im_gpio_i2c_bus_register(im_i2c_bus_t *bus, uint8_t bus_id, const char *name, im_gpio_i2c_t *gpio_bus);
功能说明:
把已配置的 GPIO I2C 句柄注册为统一总线后端。注册过程会调用im_gpio_i2c_init()。
参数表:
| 参数 | 类型 | 方向 | 说明 |
|---|---|---|---|
bus | im_i2c_bus_t * | 输入/输出 | 已清零的调用方总线对象。 |
bus_id | uint8_t | 输入 | 全局唯一逻辑总线 ID。 |
name | const char * | 输入 | 总线名称,注销前必须有效。 |
gpio_bus | im_gpio_i2c_t * | 输入 | BSP 已准备好引脚并完成字段配置的 GPIO I2C 句柄。 |
返回值表:
| 返回值 | 说明 |
|---|---|
IM_OK | GPIO 后端已初始化并注册。 |
IM_ERR_INVALID_PARAM | gpio_bus 或通用总线参数非法。 |
IM_ERR_NOT_READY | GPIO 引脚未由 BSP 预留,或 SDK 时基不可用。 |
IM_ERR_BUSY | GPIO I2C 句柄已经初始化。 |
| 其他错误码 | 通用总线注册或 GPIO 初始化错误。 |
补充说明:
本接口直接保存
gpio_bus指针,不复制句柄;该对象在注销前必须保持有效。BSP 必须提前申请并把 SCL/SDA 配置为释放状态,外部上拉通常也是必需的。
GPIO 后端不申请、配置或释放引脚。
GPIO 后端最高支持 100 kbit/s。
3.6.2 im_gpio_i2c_bus_unregister
error_code_t im_gpio_i2c_bus_unregister(im_i2c_bus_t *bus);
功能说明:
注销 GPIO Backend 总线并反初始化 GPIO I2C 句柄。
返回值和约束与通用总线注销一致。GPIO 句柄仍由调用方持有,不会被释放。
4. 配置与集成
4.1 头文件和构建组件
| 使用场景 | 公开头文件 | CMake 组件 |
|---|---|---|
| 应用注册设备和传输 | im_i2c_dev.h | im_i2c |
| 平台注册通用总线 | im_i2c_bus.h | im_i2c |
| 注册硬件 I2C 后端 | im_hal_i2c_bus.h | im_i2c、im_i2c HAL 依赖 |
| 注册 GPIO I2C 后端 | im_gpio_i2c_bus.h | im_i2c、GPIO I2C 依赖 |
code/middleware/i2c/CMakeLists.txt 始终编译 im_i2c_dev.c,并按配置分别
加入 HAL Backend 或 GPIO Backend 源文件。
逻辑设备对象和 HAL Backend context 使用 heap 分配,因此移植环境必须提供
可用的 malloc/free 实现。总线对象和 GPIO Backend 句柄由调用方提供。
4.2 示例工程
| 示例或测试 | 内容 |
|---|---|
code/middleware/i2c/example/ | HAL Backend EEPROM 探测、单消息写和 repeated-START 读写。 |
code/examples/hal/i2c_example.c | HAL/middleware EEPROM 功能与错误诊断示例。 |
code/examples/ll/i2c_eeprom_rw_example.c | 直接操作 LL 寄存器的 EEPROM 读写自测,不经过本中间层。 |
code/middleware/shell/cmds/drivers/im_shell_cmd_i2c.c | 基于中间件的 I2C shell 诊断命令。 |
示例的板级控制器、引脚、速率和 EEPROM 地址仅代表具体示例配置。产品使用时应 根据实际硬件修改。板级 pinmux 始终由 BSP 负责。
5. 关键行为约束
5.1 并发与所有权
全局总线列表锁保护注册表;每总线互斥锁保护设备列表和该总线上的传输。
总线锁没有超时,
timeout_ms不包含等待其他 transfer 释放总线的时间。注册、注销和传输都是可阻塞操作,不能在 ISR context 调用。
调用方必须自行保证设备句柄在 unregister 后不再被其他线程使用。
im_i2c_bus_t::lock和链表字段是内部状态,调用方不得修改。
5.2 超时与恢复
HAL Backend 在完成等待超时后调用 HAL abort。
abort 成功后返回
IM_ERR_TIMEOUT;abort 失败时返回 abort 的错误码。ABORT 操作本身也使用
timeout_ms,因此发生超时时,总调用时间可能超过timeout_ms。GPIO Backend 在传输超时后尝试最多 9 个 SCL 时钟和 STOP 的恢复流程。
恢复失败时返回恢复错误;恢复成功后仍返回
IM_ERR_TIMEOUT。transfer 失败后是否需要重新初始化控制器,应由后端错误码和硬件状态决定。
6. 典型用法
6.1 GPIO Backend 注册
下面的示例只展示中间层接入;GPIO 引脚申请和 pinmux 由 BSP 完成。
#include "im_gpio_i2c.h"
#include "im_gpio_i2c_bus.h"
#include "im_i2c_dev.h"
static im_i2c_bus_t s_gpio_bus;
static im_gpio_i2c_t s_gpio_i2c;
error_code_t demo_gpio_i2c_open(void)
{
error_code_t ret;
im_gpio_i2c_struct_init(&s_gpio_i2c);
s_gpio_i2c.scl_gpiox = BOARD_I2C_SCL_PORT;
s_gpio_i2c.scl_pin_mask = BOARD_I2C_SCL_PIN;
s_gpio_i2c.sda_gpiox = BOARD_I2C_SDA_PORT;
s_gpio_i2c.sda_pin_mask = BOARD_I2C_SDA_PIN;
s_gpio_i2c.bitrate_hz = 100000U;
s_gpio_i2c.clock_stretch_timeout_us = 1000U;
ret = im_gpio_i2c_bus_register(&s_gpio_bus,
8U,
"demo-gpio-i2c",
&s_gpio_i2c);
return ret;
}BOARD_I2C_* 宏应由具体 BSP 提供。GPIO 句柄必须保持有效到总线注销完成。