2. I2C协议时序:起始条件、停止条件、数据有效性、字节传输格式、ACK/NACK机制
I2C(Inter-Integrated Circuit)总线协议由Philips(现NXP)公司开发,是一种同步、多主从、串行通信总线。它仅使用两条双向开漏线——串行数据线(SDA)和串行时钟线(SCL),即可实现设备间的数据交换。理解I2C协议的底层时序是编写正确、稳定的I2C设备驱动的基石。本章将深入剖析I2C协议的五个核心时序要素。
2.1 起始条件(START Condition)与停止条件(STOP Condition)
I2C总线的空闲状态是SCL和SDA均为高电平。所有通信事务都以起始条件开始,以停止条件结束。这两个条件是总线上的“帧同步”信号,由主控制器(Master)产生。
- 起始条件(S):当SCL为高电平时,SDA从高电平切换到低电平。
- 停止条件(P):当SCL为高电平时,SDA从低电平切换到高电平。
2.2 数据有效性(Data Validity)
I2C协议规定,在时钟线SCL为高电平期间,数据线SDA上的电平必须保持稳定。只有在SCL为低电平期间,SDA上的数据才允许发生变化。这一规则确保了数据采样的可靠性。
| SCL状态 | SDA允许行为 | 数据采样点 |
|---|---|---|
| 高电平 | 保持稳定(不变) | 主/从设备在此阶段采样SDA |
| 低电平 | 允许变化(数据切换) | 发送方在此阶段更新SDA |
2.3 字节传输格式与帧结构
I2C总线上的数据传输以字节(8位)为单位。每个字节传输后,必须跟随一个应答位(ACK/NACK)。数据在总线上的传输遵循大端序(MSB first),即最高有效位最先发送。
一个完整的I2C通信帧(以主控器向从设备写数据为例)包含以下部分:
- 起始条件(S)
- 从设备地址(7位或10位):高7位为地址,第8位为读写方向位(0=写,1=读)。
- 应答位(ACK):从设备拉低SDA表示应答。
- 寄存器/子地址(可选):指定从设备内部的寄存器偏移。
- 应答位(ACK)
- 数据字节(1~N个):每个字节后跟一个应答位。
- 停止条件(P)
2.4 ACK/NACK 机制
应答机制是I2C协议可靠性的核心。每传输完一个字节(8位数据),接收方必须在第9个SCL时钟周期内产生一个应答位。
- ACK (Acknowledge, 应答):接收方在第9个时钟周期将SDA拉低,表示“已收到,请继续发送”。
- NACK (Not Acknowledge, 非应答):接收方在第9个时钟周期保持SDA为高电平(释放总线),表示“未准备好/传输结束/地址未匹配”。
| 场景 | 发送方 | 接收方行为 | 含义 |
|---|---|---|---|
| 地址匹配 | 主控器 | 从设备拉低SDA (ACK) | 设备在线,可以通信 |
| 数据写入 | 主控器 | 从设备拉低SDA (ACK) | 数据已接收 |
| 数据读取 | 从设备 | 主控器拉低SDA (ACK) | 请求继续发送下一字节 |
| 读取结束 | 从设备 | 主控器释放SDA (NACK) | 停止读取,随后发送停止条件 |
| 地址未匹配 | 主控器 | 从设备释放SDA (NACK) | 总线上无此地址设备 |
i2cget、i2cset命令或直接操作寄存器时,如果收到NACK(通常表现为-EIO或-EREMOTEIO错误),请检查:1) 从设备地址是否正确;2) 设备是否上电;3) 总线是否被其他设备拉死。
2.5 时序参数与驱动中的时间控制
I2C协议定义了标准模式(100kHz)、快速模式(400kHz)和高速模式(3.4MHz)等。不同的模式对应不同的时序参数,驱动开发中需要关注以下几个关键时间:
| 参数 | 符号 | 标准模式 (100kHz) | 快速模式 (400kHz) | 说明 |
|---|---|---|---|---|
| SCL时钟频率 | f_SCL | 100 kHz | 400 kHz | 由主控器产生 |
| 保持时间(起始条件) | t_HD;STA | 4.0 μs | 0.6 μs | SCL低后,SDA保持低的时间 |
| 建立时间(停止条件) | t_SU;STO | 4.0 μs | 0.6 μs | SCL高前,SDA建立高的时间 |
| 数据保持时间 | t_HD;DAT | 0 ~ 3.45 μs | 0 ~ 0.9 μs | SCL低后,SDA数据保持时间 |
| 数据建立时间 | t_SU;DAT | 250 ns | 100 ns | SCL高前,SDA数据建立时间 |
| 总线空闲时间 | t_BUF | 4.7 μs | 1.3 μs | 停止到起始的最小间隔 |
clock-frequency = <400000>; 即可将总线速率设为400kHz。内核的I2C核心框架会根据该值自动计算SCL高低电平的计数值。
2.6 驱动开发中的协议抽象
在Linux内核中,上述复杂的协议时序被I2C核心层和控制器驱动完全封装。设备驱动开发者通常不需要直接操作SCL/SDA电平,而是通过标准的内核API来完成通信:
// 发送一个字节
int i2c_master_send(struct i2c_client *client, const char *buf, int count);
// 接收一个字节
int i2c_master_recv(struct i2c_client *client, char *buf, int count);
// 组合事务(写后读,带重复起始条件)
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
这些API在底层会自动处理起始条件、地址发送、ACK/NACK检测、数据收发和停止条件。但理解底层时序对于调试硬件问题、分析逻辑分析仪波形、以及编写模拟I2C驱动至关重要。
i2c-detect 扫描总线,使用 i2cget/i2cset 进行单次通信测试。