2. I2C通信协议详解:起始条件、停止条件、数据有效性、字节格式与应答机制
I2C(Inter-Integrated Circuit)总线协议由Philips(现NXP)设计,是一种同步、多主从、串行通信总线。它仅需两根线——串行数据线(SDA)和串行时钟线(SCL)——即可实现设备间的数据交换。理解其底层时序是驱动开发和设备树配置的基础。
核心要点: I2C协议的所有操作都围绕“起始条件”、“停止条件”、“数据有效性”、“字节格式”和“应答机制”这五个基本元素展开。掌握它们,就等于掌握了I2C通信的“语法”。
2.1 起始条件(START Condition)与停止条件(STOP Condition)
总线空闲时,SDA和SCL均为高电平。数据的传输由主控制器(Master)发起,并以起始条件开始,以停止条件结束。
- 起始条件(S): 当SCL为高电平时,SDA从高电平切换到低电平。此信号表示总线从空闲状态进入忙碌状态,并开始一次新的传输。
- 停止条件(P): 当SCL为高电平时,SDA从低电平切换到高电平。此信号表示当前传输结束,总线恢复空闲状态。
驱动开发提示: 在Linux内核I2C子系统(
i2c-core)中,起始和停止条件的生成由底层适配器驱动(如i2c-imx、i2c-omap)的硬件控制器自动完成。驱动开发者通常只需调用i2c_transfer()接口,无需手动操作GPIO模拟时序,除非使用GPIO模拟I2C(即i2c-gpio)。
2.2 数据有效性(Data Validity)
I2C协议规定,数据线SDA上的电平必须在时钟线SCL为高电平时保持稳定,以确保数据被正确采样。
- 数据稳定: 在SCL高电平期间,SDA上的数据必须保持不变。
- 数据变化: SDA的状态变化(从高到低或从低到高)只允许在SCL低电平期间进行。
| SCL状态 | SDA允许行为 | 说明 |
|---|---|---|
| 高电平 | 保持稳定 | 接收器在此窗口内采样SDA电平,决定当前位是0还是1。 |
| 低电平 | 允许变化 | 发送器在此窗口内改变SDA电平,为下一个数据位做准备。 |
时序参数关键点: 数据建立时间(tSU;DAT)和数据保持时间(tHD;DAT)是设备树中需要配置的重要时序参数。它们定义了SDA相对于SCL边沿的稳定时间窗口,直接影响通信的可靠性。
2.3 字节格式(Byte Format)与数据传输
I2C总线上的数据以字节(8位)为单位进行传输。每个字节后必须跟随一个应答位(ACK/NACK),因此一个完整的数据帧实际上是9位。
- 位序: 数据从最高有效位(MSB)开始传输,到最低有效位(LSB)结束。
- 帧结构: 起始条件 → 8位数据(MSB→LSB) → 应答位(ACK/NACK) → 下一个字节或停止条件。
2.4 应答机制(ACK/NACK)
应答机制是I2C协议保证数据可靠传输的核心。每传输完一个字节(8位),接收器必须在第9个时钟周期内拉低SDA线,以表示“应答”(ACK)。如果接收器未拉低SDA(即SDA保持高电平),则视为“非应答”(NACK)。
- ACK(应答): 接收器成功接收到一个字节后,在第9个时钟周期将SDA拉低。主控制器检测到SDA为低电平后,继续发送下一个字节或发起停止条件。
- NACK(非应答): 接收器无法接收更多数据、数据错误或设备地址不匹配时,在第9个时钟周期保持SDA为高电平。主控制器检测到NACK后,通常会发起停止条件终止传输。
| 场景 | 发送方 | 接收方行为 | SDA电平(第9时钟) | 结果 |
|---|---|---|---|---|
| 地址匹配 | 主控制器 | 从设备确认地址 | 低电平 (ACK) | 继续数据传输 |
| 数据写入成功 | 主控制器 | 从设备确认收到数据 | 低电平 (ACK) | 主控制器发送下一字节或停止 |
| 数据读取完成 | 从设备 | 主控制器确认收到数据 | 低电平 (ACK) | 从设备继续发送下一字节 |
| 设备忙/错误 | 主控制器 | 从设备无法处理 | 高电平 (NACK) | 主控制器发送停止条件 |
| 最后字节读取 | 从设备 | 主控制器不再需要数据 | 高电平 (NACK) | 从设备释放总线,主控制器发停止 |
驱动调试经验: 在Linux内核中,I2C传输失败最常见的原因之一就是NACK。当你在
1. 设备地址错误(7位或10位地址不匹配)。
2. 设备未上电或处于复位状态。
3. 总线被其他设备拉死(SDA或SCL被锁死)。
4. 时序参数(如时钟频率过快)不满足从设备要求。
dmesg中看到类似i2c_transfer: NACK from device的错误时,通常意味着:1. 设备地址错误(7位或10位地址不匹配)。
2. 设备未上电或处于复位状态。
3. 总线被其他设备拉死(SDA或SCL被锁死)。
4. 时序参数(如时钟频率过快)不满足从设备要求。
2.5 协议总结与驱动开发映射
将上述协议元素映射到Linux I2C驱动开发中,有助于理解内核抽象层的工作原理。
| 协议元素 | 硬件行为 | Linux内核抽象 | 驱动开发者操作 |
|---|---|---|---|
| 起始条件 (S) | SDA在SCL高电平时下降 | struct i2c_msg 的开始 |
由适配器驱动自动处理 |
| 停止条件 (P) | SDA在SCL高电平时上升 | i2c_transfer() 返回 |
由适配器驱动自动处理 |
| 数据有效性 | SDA在SCL高电平时稳定 | 时序参数 tSU;DAT, tHD;DAT |
在设备树中配置 i2c-sda-hold-time-ns 等 |
| 字节格式 | MSB优先,8位+ACK | i2c_msg.len 和 i2c_msg.buf |
填充缓冲区,指定长度 |
| 应答机制 | 第9时钟SDA电平 | i2c_msg.flags 中的 I2C_M_IGNORE_NAK |
检查返回值,处理NACK错误 |