2. I2C协议基础:起始条件、停止条件、数据有效性、字节格式、应答机制(ACK/NACK)

I2C(Inter-Integrated Circuit)总线协议由Philips(现NXP)公司设计,是一种同步、多主从、串行通信总线。它仅使用两条双向开漏信号线——串行数据线(SDA)和串行时钟线(SCL),即可实现设备间的数据交换。理解I2C协议的底层时序是编写Linux内核I2C设备驱动的前提,因为驱动最终需要将这些时序转化为寄存器操作或通过I2C适配器抽象层进行收发。

驱动开发视角: 在Linux内核中,I2C核心层已经封装了协议细节,驱动开发者通常不需要手动操作SCL/SDA电平。但理解起始/停止条件、ACK/NACK机制,对于调试通信失败、分析设备树时序参数(如clock-frequency)以及处理从设备异常行为至关重要。

2.1 起始条件(START Condition)与停止条件(STOP Condition)

I2C总线的空闲状态是SCL和SDA均为高电平。每次通信事务都以一个起始条件(S)开始,以一个停止条件(P)结束。这两个条件是总线上的“帧同步”信号,由主设备(Master)产生。

  • 起始条件(S): 当SCL为高电平时,SDA从高电平切换到低电平。
  • 停止条件(P): 当SCL为高电平时,SDA从低电平切换到高电平。

起始和停止条件具有唯一性,总线上的所有从设备都会检测这两个边沿,从而判断一次传输的开始和结束。在一次传输中,主设备可以发送多个字节的数据,并在不产生停止条件的情况下,通过重复起始条件(Sr)来改变数据传输方向或切换从设备。

关键规则: SDA在SCL为高电平时的任何变化都被视为控制信号(起始或停止)。数据位必须在SCL低电平时改变,在SCL高电平时采样。
I2C 起始条件与停止条件时序图 I2C 起始条件 (S) 与 停止条件 (P) 时序 SCL SDA S SDA下降沿,SCL高 P SDA上升沿,SCL高 数据位传输区域(SCL低时变化) 起始后总线被占用 停止后总线释放

2.2 数据有效性(Data Validity)

I2C协议规定,SDA线上的数据必须在SCL时钟的高电平周期内保持稳定。数据位的改变只允许在SCL为低电平时进行。这意味着,在SCL高电平期间,SDA的电平状态就是当前被采样的数据位。

I2C 数据有效性规则
SCL 状态 SDA 行为 数据有效性
高电平 必须保持稳定,不能变化 数据位被采样,有效
低电平 允许变化,准备下一位数据 数据位无效,正在切换
驱动调试提示: 如果从设备因时钟拉伸(Clock Stretching)而拉低SCL,主设备必须等待SCL释放为高后才能继续传输。Linux I2C适配器驱动通常会自动处理时钟拉伸,但某些老旧或非标准设备可能需要特殊配置。

2.3 字节格式(Byte Format)与传输顺序

I2C总线上的数据以字节(8位)为单位进行传输。每个字节传输完成后,必须跟随一个应答位(ACK/NACK)。因此,一个完整的字节传输实际上需要9个SCL时钟周期。

  • 位序: 数据从最高有效位(MSB,bit 7)开始传输,到最低有效位(LSB,bit 0)结束。
  • 字节数: 每次传输的字节数没有限制,但每个字节后都必须有应答位。
  • 地址字节: 起始条件后的第一个字节通常是7位从设备地址(高7位)加1位读写标志位(R/W)。
I2C 字节格式与传输顺序 I2C 字节格式:8位数据 + 1位应答 MSB bit6 bit5 bit4 bit3 bit2 bit1 LSB ACK 8位数据(由主设备或从设备驱动) 应答位(由接收方驱动) 传输顺序:先传高位(MSB),后传低位(LSB) 每个字节后必须跟一个应答位,共9个SCL时钟

2.4 应答机制(ACK/NACK)

应答机制是I2C协议保证数据完整性的核心。每个字节传输完成后,接收方(可能是主设备或从设备)必须在第9个SCL时钟周期内拉低SDA线,表示应答(ACK);如果接收方不拉低SDA(保持高电平),则表示非应答(NACK)。

ACK 与 NACK 的含义与场景
信号 SDA 电平(第9个时钟) 含义 典型场景
ACK 低电平 接收成功,可以继续发送下一字节 从设备确认地址匹配;从设备确认数据写入成功
NACK 高电平 接收失败或传输结束 主设备读取数据后发送NACK表示读取结束;从设备忙或无法响应
驱动中的NACK处理: 在Linux I2C驱动中,当主设备读取最后一个字节时,驱动会在该字节后主动发送NACK(不产生应答),然后发送停止条件。如果从设备在地址阶段返回NACK,I2C核心通常会返回 -ENXIO 错误码,表示设备不存在或未响应。
I2C 应答机制流程图 I2C 应答机制 (ACK/NACK) 决策流程 发送方传输8位数据 接收方 就绪? 接收方拉低SDA (ACK) 继续下一字节 SDA保持高 (NACK) 发送停止条件

2.5 完整数据传输示例:写操作与读操作

结合上述所有要素,一个典型的I2C写操作序列如下:

  1. 起始条件(S):主设备产生起始条件。
  2. 发送地址字节:主设备发送7位从设备地址 + 写标志位(0)。
  3. 从设备应答(ACK):地址匹配的从设备拉低SDA应答。
  4. 发送寄存器地址:主设备发送8位寄存器地址(或命令字节)。
  5. 从设备应答(ACK):从设备确认收到寄存器地址。
  6. 发送数据字节:主设备发送一个或多个数据字节。
  7. 从设备应答(ACK):每个数据字节后从设备应答。
  8. 停止条件(P):主设备产生停止条件,结束传输。

读操作类似,但在发送寄存器地址后,主设备会发送重复起始条件(Sr),然后发送地址字节 + 读标志位(1),之后主设备变为接收方,从设备变为发送方。主设备在读取最后一个字节后发送NACK,表示读取结束。

内核驱动对应: Linux I2C核心提供了 i2c_master_send() 和 i2c_master_recv() 等API,它们自动处理起始、停止、地址和应答。驱动开发者只需填充 struct i2c_msg 结构体数组,指定标志位(如 I2C_M_RD)即可实现复合传输。
I2C 完整写操作时序 I2C 写操作完整时序:S + 地址 + ACK + 数据 + ACK + P S 地址+W A Reg Addr A Data Byte A 更多... P S: 起始条件 | A: 应答 (ACK) | P: 停止条件 每个数据块后紧跟一个应答位,由接收方驱动SDA

2.6 协议总结与驱动开发要点

I2C 协议要素与驱动开发对应关系
协议要素 硬件行为 Linux 驱动对应
起始条件 (S) SCL高时SDA下降沿 i2c_msg 数组起始,由适配器自动产生
停止条件 (P) SCL高时SDA上升沿 i2c_transfer() 返回后自动产生
地址 + R/W 7位地址 + 1位读写标志 struct i2c_msg.addr 和 flags 中的 I2C_M_RD
数据字节 MSB优先,8位传输 struct i2c_msg.buf 指向的数据缓冲区
应答 (ACK) 接收方拉低SDA 由适配器硬件自动处理,驱动检查返回值
非应答 (NACK) 接收方保持SDA高 返回 -ENXIO 或 -EIO 错误
常见驱动错误: 初学者容易忽略I2C传输的原子性。在中断上下文或持有自旋锁时,不能调用 i2c_transfer(),因为它可能睡眠等待硬件完成。应使用 i2c_master_send() 的原子版本或通过工作队列延迟传输。