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 或寄存器位。时序错误会导致总线挂死或从设备无响应。
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 表示应答 |
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 |
2.5 完整数据传输流程
一个完整的 I2C 数据传输事务包含以下步骤:
- 起始条件(S): 主设备在 SCL 高电平时将 SDA 拉低。
- 发送从设备地址: 主设备发送 7 位或 10 位地址,后跟 1 位读写标志(0 = 写,1 = 读)。
- 从设备应答(ACK): 匹配地址的从设备在第 9 个时钟拉低 SDA。
- 数据传输: 主设备(写模式)或从设备(读模式)逐字节发送数据,每字节后跟应答位。
- 停止条件(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 属性降低总线速度来缓解时序问题。