2. I2C 协议基础:起始条件、停止条件、数据字节传输、应答机制

I2C(Inter-Integrated Circuit)总线协议由 Philips(现 NXP)设计,是一种同步、多主从、串行通信总线。它仅使用两根线——串行数据线(SDA)和串行时钟线(SCL)——即可完成设备间的数据交换。理解 I2C 协议的核心时序是编写 Linux I2C 总线控制器驱动的第一步。

2.1 总线空闲状态与电气特性

在总线空闲时,SDA 和 SCL 两条线都通过上拉电阻被拉高到 Vdd(通常为 3.3V 或 5V)。I2C 设备使用开漏(Open-Drain)或开集电极(Open-Collector)输出,这意味着设备只能将总线拉低(驱动为 0),而不能主动拉高。这种设计是实现总线仲裁和时钟拉伸的基础。

关键概念: 开漏结构允许不同设备共享同一根线,任何设备都可以将总线拉低。当所有设备都释放总线时,总线由上拉电阻拉高。

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

I2C 总线上的每一次数据传输都以起始条件开始,以停止条件结束。这两个条件是唯一允许 SDA 在 SCL 为高电平时发生变化的时刻。

  • 起始条件(S): SCL 为高电平时,SDA 从高电平切换到低电平。
  • 停止条件(P): SCL 为高电平时,SDA 从低电平切换到高电平。
驱动开发要点: 在 Linux 内核的 I2C 适配器驱动中,master_xfer 回调函数必须严格按照起始条件、数据传输、停止条件的顺序操作 SDA 和 SCL GPIO 或寄存器位。时序错误会导致总线挂死或从设备无响应。
I2C 起始条件与停止条件时序图 SCL SDA S 起始条件 P 停止条件 数据有效(SDA 在 SCL 低电平时变化)

2.3 数据字节传输格式

在起始条件和停止条件之间,主设备逐字节传输数据。每个字节必须为 8 位(MSB 优先),后面紧跟一个应答位(ACK/NACK)。

  • 时钟同步: 主设备产生 SCL 时钟。每个数据位在 SCL 的一个脉冲内传输。
  • 数据有效性: 在 SCL 的高电平期间,SDA 上的数据必须保持稳定。SDA 只能在 SCL 为低电平时改变状态。
  • 字节格式: 先传输最高位(MSB),最后传输最低位(LSB)。
位序号 名称 说明
7 (MSB) D7 数据最高位,最先传输
6 D6 —
5 D5 —
4 D4 —
3 D3 —
2 D2 —
1 D1 —
0 (LSB) D0 数据最低位,最后传输
— ACK 第 9 个 SCL 脉冲,从设备拉低 SDA 表示应答
I2C 单字节数据传输时序 SCL SDA D7 D6 D5 D4 D3 D2 D1 D0 ACK 第 9 个时钟脉冲

2.4 应答机制(ACK / NACK)

应答机制是 I2C 协议中确保数据可靠传输的核心。每传输完一个字节(8 位数据),接收方必须在第 9 个 SCL 时钟周期内给出应答信号。

  • ACK(应答): 接收方在第 9 个时钟周期将 SDA 拉低,表示“已收到,请继续发送”。
  • NACK(非应答): 接收方在第 9 个时钟周期保持 SDA 为高电平,表示“未准备好接收”或“传输结束”。
驱动调试常见问题: 如果从设备未应答(NACK),主设备必须发送停止条件终止传输。在 Linux 内核中,i2c_msg 的 flags 字段中的 I2C_M_IGNORE_NAK 可以控制是否忽略 NACK,但滥用此标志可能导致总线混乱。
场景 发送方 接收方 SDA 状态(第 9 时钟)
主设备发送地址/数据 主设备 从设备 从设备拉低 → ACK
主设备读取数据 从设备 主设备 主设备拉低 → ACK(继续读)
主设备读取最后字节 从设备 主设备 主设备释放 → NACK(停止读)
从设备忙或地址错误 主设备 从设备 从设备释放 → NACK
ACK / NACK 应答机制流程图 主设备 从设备 发送 8 位数据 + 释放 SDA 接收方 拉低 SDA? 是 否 ACK:继续传输 NACK:停止传输 主设备在读取最后一个字节时必须发送 NACK,通知从设备停止发送 然后主设备发送停止条件(P)释放总线

2.5 完整数据传输流程

一个完整的 I2C 数据传输事务包含以下步骤:

  1. 起始条件(S): 主设备在 SCL 高电平时将 SDA 拉低。
  2. 发送从设备地址: 主设备发送 7 位或 10 位地址,后跟 1 位读写标志(0 = 写,1 = 读)。
  3. 从设备应答(ACK): 匹配地址的从设备在第 9 个时钟拉低 SDA。
  4. 数据传输: 主设备(写模式)或从设备(读模式)逐字节发送数据,每字节后跟应答位。
  5. 停止条件(P): 主设备在 SCL 高电平时将 SDA 拉高,释放总线。
内核代码对应: Linux 内核中的 struct i2c_msg 结构体封装了上述流程。驱动开发者通过填充 addr、flags、len、buf 字段,由 i2c_transfer() 函数自动生成完整的 SCL/SDA 时序。
// Linux 内核 I2C 消息结构体定义(include/uapi/linux/i2c.h)
struct i2c_msg {
    __u16 addr;    // 从设备地址(7 位或 10 位)
    __u16 flags;   // 传输标志:I2C_M_RD(读)、I2C_M_TEN(10 位地址)等
    __u16 len;     // 消息数据长度(字节数)
    __u8 *buf;     // 数据缓冲区指针
};

2.6 时钟拉伸(Clock Stretching)

I2C 协议允许从设备在接收或发送数据时,通过拉低 SCL 线来延长时钟周期,从而获得更多处理时间。这种机制称为时钟拉伸。

  • 工作原理: 从设备在 SCL 被主设备拉高后,主动将 SCL 拉低,迫使主设备等待,直到从设备释放 SCL。
  • 驱动影响: Linux I2C 适配器驱动必须支持检测 SCL 被拉低的情况,并实现超时机制,防止总线死锁。
  • 常见场景: 慢速 MCU 从设备、EEPROM 写入期间、传感器数据就绪等待。
硬件设计提示: 如果系统中存在不支持时钟拉伸的从设备,主设备驱动应禁用此功能。在 Linux 设备树中,可以通过 clock-frequency 属性降低总线速度来缓解时序问题。
时钟拉伸(Clock Stretching)示意图 主设备产生的 SCL 从设备拉伸后的 SCL(低电平延长) 拉伸区域 从设备拉低 SCL

2.7 本章小结