2. I²C 通信协议详解:起始条件、停止条件、数据有效性、字节格式与应答机制
I²C(Inter-Integrated Circuit)总线是一种由 Philips(现 NXP)开发的两线式串行通信协议,广泛用于连接微控制器与低速外设。本章深入剖析 I²C 协议的底层信号时序,包括起始/停止条件、数据有效性、字节传输格式以及应答机制,为后续驱动开发中的寄存器读写操作奠定基础。
2.1 总线物理层与信号定义
I²C 总线仅使用两条双向信号线:
- SCL(Serial Clock):时钟线,由主设备驱动,用于同步数据传输。
- SDA(Serial Data):数据线,用于传输地址、数据和应答信号。
两条线均通过上拉电阻连接到 VDD(通常为 3.3V 或 5V)。空闲状态时,SCL 和 SDA 均为高电平。设备通过开漏输出驱动总线,实现“线与”逻辑。
关键概念: 开漏结构允许多个设备共享总线,任何设备都可以将总线拉低,但只有主设备控制时钟。总线上的设备通过唯一地址区分。
2.2 起始条件(START Condition)与停止条件(STOP Condition)
每次 I²C 通信以起始条件开始,以停止条件结束。这两个条件是总线上的特殊信号,用于标识传输的边界。
| 条件 | 信号时序 | 说明 |
|---|---|---|
| 起始条件(S) | SCL 为高电平时,SDA 从高电平切换到低电平 | 表示总线从空闲状态进入活跃状态,主设备开始一次传输 |
| 停止条件(P) | SCL 为高电平时,SDA 从低电平切换到高电平 | 表示传输结束,总线返回空闲状态 |
起始和停止条件只能由主设备产生。在驱动开发中,这两个条件通常由硬件 I²C 控制器自动生成,但在软件模拟 I²C(Bit-Banging)中需要手动控制 GPIO 电平切换。
驱动开发要点: 在 Linux 内核中,i2c_msg 结构体的 flags 字段不直接包含起始/停止标志,而是通过消息序列隐式控制:第一条消息前自动产生起始条件,最后一条消息后自动产生停止条件。连续多条消息可通过 flags 中的 I2C_M_NOSTART 或 I2C_M_STOP 微调。
2.3 数据有效性(Data Validity)
I²C 协议规定,数据在 SCL 时钟高电平期间必须保持稳定,只有在 SCL 低电平时 SDA 才允许变化。这一规则确保了数据采样的可靠性。
- 数据采样点:接收设备在 SCL 的上升沿或高电平期间采样 SDA 电平。
- 数据变化窗口:SDA 只能在 SCL 为低电平时改变状态。
- 例外:起始条件和停止条件属于例外,它们在 SCL 高电平时改变 SDA 状态,用于标识传输边界。
常见错误: 在软件模拟 I²C 时,如果未严格遵循“SCL 低电平时改变 SDA”的规则,可能导致从设备采样到错误数据。务必在 SCL 拉低后延迟至少 1μs(标准模式)再改变 SDA。
2.4 字节格式与传输顺序
I²C 总线上的数据以字节(8 位)为单位传输,每个字节后跟随一个应答位(ACK/NACK)。传输顺序为 MSB(最高有效位)优先。
| 字段 | 位数 | 说明 |
|---|---|---|
| 地址字节 | 7 位或 10 位 | 从设备地址,7 位地址后跟 1 位读写方向位(R/W) |
| 数据字节 | 8 位 | 实际传输的数据,MSB 先发送 |
| 应答位 | 1 位 | 接收设备在第 9 个 SCL 周期拉低 SDA 表示应答(ACK),保持高电平表示非应答(NACK) |
一个完整的 I²C 传输帧结构如下:
| 起始条件 | 7位地址 | R/W | ACK | 数据字节 | ACK | ... | 数据字节 | ACK/NACK | 停止条件 |
2.5 应答机制(ACK/NACK)
应答机制是 I²C 协议可靠性的核心。每个字节传输完成后,接收设备必须在第 9 个 SCL 周期内产生应答信号。
- ACK(应答):接收设备在第 9 个 SCL 周期拉低 SDA,表示“已收到,请继续发送”。
- NACK(非应答):接收设备在第 9 个 SCL 周期保持 SDA 高电平,表示“未准备好”或“传输结束”。
应答位的典型应用场景:
| 场景 | 发送方 | 接收方 | 应答行为 |
|---|---|---|---|
| 主设备发送地址 | 主设备 | 从设备 | 从设备 ACK:地址匹配;NACK:无此设备或忙 |
| 主设备写数据 | 主设备 | 从设备 | 从设备 ACK:数据已接收;NACK:无法处理 |
| 主设备读数据 | 从设备 | 主设备 | 主设备 ACK:继续读取;主设备 NACK:停止读取 |
驱动开发提示: 在 Linux I²C 驱动中,应答位由硬件控制器自动处理。但通过 i2c_msg 的 flags 字段设置 I2C_M_IGNORE_NAK 可以忽略 NACK,用于探测设备是否存在。此外,读取最后一个字节前主设备应发送 NACK,以通知从设备停止发送。
2.6 完整传输流程示例
以下是一个典型的 I²C 写操作流程,主设备向地址为 0x50 的从设备写入一个字节数据 0xAB:
- 主设备产生起始条件(S)。
- 主设备发送 7 位地址 0x50 + 写位(0),即 0xA0。
- 从设备在第 9 个 SCL 周期拉低 SDA 作为 ACK。
- 主设备发送数据字节 0xAB(二进制 10101011)。
- 从设备在第 9 个 SCL 周期拉低 SDA 作为 ACK。
- 主设备产生停止条件(P)。
对应的 C 代码片段(使用 Linux 内核 I²C 子系统):
#include <linux/i2c.h>
struct i2c_msg msg;
unsigned char buf[1] = {0xAB};
msg.addr = 0x50; // 从设备地址
msg.flags = 0; // 写操作
msg.len = 1; // 数据长度
msg.buf = buf; // 数据缓冲区
// 通过 I²C 适配器发送消息
if (i2c_transfer(adapter, &msg, 1) < 0) {
pr_err("I2C write failed\n");
return -EIO;
}
调试技巧: 使用逻辑分析仪抓取 SCL 和 SDA 波形,可以直观验证起始条件、地址、数据、应答和停止条件是否正确。在驱动开发初期,建议先使用 i2c-tools 工具(如 i2cget、i2cset)验证硬件连接和协议时序。
2.7 本章小结
I²C 协议的核心要素包括:
- 起始/停止条件:标识传输的边界,由主设备控制。
- 数据有效性:SCL 高电平时 SDA 必须稳定,低电平时允许变化。
- 字节格式:8 位数据 + 1 位应答,MSB 优先。
- 应答机制:ACK 表示继续,NACK 表示停止或错误。