I2C 从机开发:事务流程、数据组织与耗时应答
I2C 从机开发先掌握普通事务,再处理变长响应和耗时操作。
先记住四条规则
- 从机不能主动发起通信。
- 主机决定读写方向、读取字节数和结束时机。
- 写数据阶段由从机逐字节应答;读数据阶段由主机逐字节应答,最后一字节回 NACK。
- ACK 只表示地址或字节已接收,不代表业务完成。
从机开发先关注三种事务:
写: START → 地址+W → 请求 → STOP
读: START → 地址+R → 响应 → STOP
写后读: START → 地址+W → 请求 → RESTART → 地址+R → 响应 → STOP从请求到响应
I2C 只定义传输,不定义字节含义。从机软件可按一条链路组织:
RX 回调 → 请求缓冲 → 业务执行 → 响应快照 → TX 回调回调负责搬运数据和记录事件,耗时业务放在回调外。TX 始终读取一份稳定快照。写后读可能紧接 RESTART,来不及完成的请求先返回 BUSY。
两种数据模型
I2C 从机主要使用寄存器模型和命令模型。命令通常采用二进制编码;文本命令属于自定义的应用层编码。
| 寄存器 | 二进制命令 | 文本命令(少见) | |
|---|---|---|---|
| 模型 | 寄存器模型 | 命令模型 | 命令模型 |
| 开头 | 寄存器地址 | 操作码 | 命令词 |
| 长度 | 由寄存器定义 | 定长或长度字段 | 写入可分隔,读取须约定 |
| 特点 | 适合配置和状态 | 紧凑、效率高 | 可读、便于调试 |
寄存器可由软件虚拟,也可包含命令寄存器。设备可混合使用寄存器和二进制命令。文本命令多用于 MCU 间自定义通信、调试或复用 UART 命令行。移植到 UART 或 SPI 时,帧边界和读写方向仍需适配。
特殊情况一:变长读取
I2C 读阶段没有从机发出的结束标记。多数主机 API 要求调用前给定字节数,通用方案是两次应用层读取:
读固定响应头 → 取得长度 N → 校验 N → 再读 N 字节寄存器模型可提供长度寄存器和数据区,命令模型可返回响应头和载荷。实现时还要保证:
- 两次读取使用同一份响应快照;
- N 不超过协议上限和主机缓冲区;
- 第二次读取完成前不复用旧缓冲。
文本结束符只能标记内容结束,不能替主机结束 I2C 读取。
少数控制器支持读到长度后在同一事务中继续收取,具体取决于硬件和驱动。
特殊情况二:耗时操作
Flash 擦写、采样或电机动作不能立即完成时,可从以下方式中选择:
| 方式 | 含义 | 适用场景 | 限制 |
|---|---|---|---|
| 时钟拉伸 | 从机拉低 SCL 暂停时钟 | 很短且上限明确 | 占用总线,可能超时 |
| 状态轮询 | 主机反复读取状态 | 通用默认方案 | 增加轮询事务 |
| 地址 NACK | 从机忙时拒绝地址访问 | 主从明确约定 | 无法区分忙、掉电或缺失 |
| GPIO 通知 | 完成后改变 GPIO 电平 | 时间长或不确定 | 多占一个引脚 |
状态通过寄存器、命令或响应头返回 BUSY/DONE/ERROR。耗时很长时可增加 GPIO 通知。
STM32 实现提醒
- 主机侧
HAL_I2C_Mem_*对应寄存器访问,HAL_I2C_Master_*收发原始数据;MemAddress仍会在线路上传输。 - 两个独立的阻塞式发送、接收通常产生 STOP。需要 RESTART 时使用对应系列的 Sequential/Seq 接口。
HAL_I2C_SlaveRxCpltCallback通常只表示预设长度收满,不等于检测到 STOP 或 RESTART。- 用逻辑分析仪验证写、读、写后读和异常中断。