API GUIDE / I2C / Middleware

G32S10 I2C Middleware 接口

Middleware 层接口:说明:

1. 资源模型与公开类型

1.1 模块能力摘要

项目HAL BackendGPIO Backend
控制器G32S10 I2C0、I2C1、I2C2BSP 指定的任意 SCL/SDA GPIO
目标地址7-bit,0x00~0x7F7-bit,0x00~0x7F
消息数量1~IM_I2C_MAX_MESSAGE_COUNT1~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 pinmuxBSP 申请并配置 GPIO 输入状态

1.2 总线与设备生命周期

STEP 1  zero-initialized im_i2c_bus_t
↓
STEP 2  im_<backend>_bus_register()
↓
STEP 3  BUS READY
↓
STEP 4  im_i2c_device_register()
↓
STEP 5  BUS + DEVICE READY
↓
STEP 6  im_i2c_device_transfer() / probe()
↓
STEP 7  COMPLETE
↓
STEP 8  im_i2c_device_unregister()
↓
STEP 9  BUS READY
↓
STEP 10  im_<backend>_bus_unregister()
↓
STEP 11  BUS UNREGISTERED

说明:

  • 总线注册时立即调用后端的 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_MAX7-bit 目标地址最大值。0x7F。
IM_GPIO_I2C_MAX_BITRATE_HZGPIO Backend 支持的最大速率。100000。
IM_GPIO_I2C_ADDRESS_MAXGPIO 驱动支持的最大 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;
字段说明
directionIM_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_MEMORYBackend 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_outim_i2c_device_t **输出接收设备句柄;失败时写入 NULL。
bus_iduint8_t输入已注册总线 ID。
addressuint8_t输入7-bit 目标地址,最大 0x7F。
nameconst 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);

功能说明:

从所属总线的设备列表中删除逻辑设备并释放设备对象。

参数表:

参数类型方向说明
deviceim_i2c_device_t *输入已注册的设备句柄。

返回值表:

返回值说明
IM_OK设备已注销并释放。
IM_ERR_INVALID_PARAMdevice 为空。
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。

参数表:

参数类型方向说明
deviceim_i2c_device_t *输入已注册设备。
msgsconst im_i2c_msg_t *输入1~8 条非空消息。
msg_countsize_t输入消息数量,范围 1~8。
timeout_msuint32_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 目标执行一次单字节读并丢弃结果,用于确认目标是否响应地址。

参数表:

参数类型方向说明
deviceim_i2c_device_t *输入已注册设备。
timeout_msuint32_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,并将其加入全局总线列表。

参数表:

参数类型方向说明
busim_i2c_bus_t *输入/输出已清零的调用方总线对象。
bus_iduint8_t输入全局唯一逻辑总线 ID。
nameconst char *输入总线名称,注销前必须有效。
opsconst im_i2c_bus_ops_t *输入完整后端操作表。
contextvoid *输入传给四个后端回调的上下文。

返回值表:

返回值说明
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,成功后从全局总线列表移除。

参数表:

参数类型方向说明
busim_i2c_bus_t *输入/输出已注册总线。

返回值表:

返回值说明
IM_OK总线已注销。
IM_ERR_INVALID_PARAMbus 为空。
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()。

参数表:

参数类型方向说明
busim_i2c_bus_t *输入/输出已清零的调用方总线对象。
bus_iduint8_t输入全局唯一逻辑总线 ID。
nameconst char *输入总线名称,注销前必须有效。
gpio_busim_gpio_i2c_t *输入BSP 已准备好引脚并完成字段配置的 GPIO I2C 句柄。

返回值表:

返回值说明
IM_OKGPIO 后端已初始化并注册。
IM_ERR_INVALID_PARAMgpio_bus 或通用总线参数非法。
IM_ERR_NOT_READYGPIO 引脚未由 BSP 预留,或 SDK 时基不可用。
IM_ERR_BUSYGPIO 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.him_i2c
平台注册通用总线im_i2c_bus.him_i2c
注册硬件 I2C 后端im_hal_i2c_bus.him_i2c、im_i2c HAL 依赖
注册 GPIO I2C 后端im_gpio_i2c_bus.him_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.cHAL/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 句柄必须保持有效到总线注销完成。