2. I²C 通信协议详解:起始条件、停止条件、数据有效性、字节格式与应答机制

I²C(Inter-Integrated Circuit)总线是一种由 Philips(现 NXP)开发的两线式串行通信协议,广泛用于连接微控制器与低速外设。本章深入剖析 I²C 协议的底层信号时序,包括起始/停止条件、数据有效性、字节传输格式以及应答机制,为后续驱动开发中的寄存器读写操作奠定基础。

2.1 总线物理层与信号定义

I²C 总线仅使用两条双向信号线:

  • SCL(Serial Clock):时钟线,由主设备驱动,用于同步数据传输。
  • SDA(Serial Data):数据线,用于传输地址、数据和应答信号。

两条线均通过上拉电阻连接到 VDD(通常为 3.3V 或 5V)。空闲状态时,SCL 和 SDA 均为高电平。设备通过开漏输出驱动总线,实现“线与”逻辑。

关键概念: 开漏结构允许多个设备共享总线,任何设备都可以将总线拉低,但只有主设备控制时钟。总线上的设备通过唯一地址区分。

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

每次 I²C 通信以起始条件开始,以停止条件结束。这两个条件是总线上的特殊信号,用于标识传输的边界。

条件 信号时序 说明
起始条件(S) SCL 为高电平时,SDA 从高电平切换到低电平 表示总线从空闲状态进入活跃状态,主设备开始一次传输
停止条件(P) SCL 为高电平时,SDA 从低电平切换到高电平 表示传输结束,总线返回空闲状态

起始和停止条件只能由主设备产生。在驱动开发中,这两个条件通常由硬件 I²C 控制器自动生成,但在软件模拟 I²C(Bit-Banging)中需要手动控制 GPIO 电平切换。

驱动开发要点: 在 Linux 内核中,i2c_msg 结构体的 flags 字段不直接包含起始/停止标志,而是通过消息序列隐式控制:第一条消息前自动产生起始条件,最后一条消息后自动产生停止条件。连续多条消息可通过 flags 中的 I2C_M_NOSTART 或 I2C_M_STOP 微调。
I²C 起始条件与停止条件时序图 I²C 起始条件与停止条件时序图 SCL SDA 起始条件 停止条件

2.3 数据有效性(Data Validity)

I²C 协议规定,数据在 SCL 时钟高电平期间必须保持稳定,只有在 SCL 低电平时 SDA 才允许变化。这一规则确保了数据采样的可靠性。

  • 数据采样点:接收设备在 SCL 的上升沿或高电平期间采样 SDA 电平。
  • 数据变化窗口:SDA 只能在 SCL 为低电平时改变状态。
  • 例外:起始条件和停止条件属于例外,它们在 SCL 高电平时改变 SDA 状态,用于标识传输边界。
常见错误: 在软件模拟 I²C 时,如果未严格遵循“SCL 低电平时改变 SDA”的规则,可能导致从设备采样到错误数据。务必在 SCL 拉低后延迟至少 1μs(标准模式)再改变 SDA。

2.4 字节格式与传输顺序

I²C 总线上的数据以字节(8 位)为单位传输,每个字节后跟随一个应答位(ACK/NACK)。传输顺序为 MSB(最高有效位)优先。

字段 位数 说明
地址字节 7 位或 10 位 从设备地址,7 位地址后跟 1 位读写方向位(R/W)
数据字节 8 位 实际传输的数据,MSB 先发送
应答位 1 位 接收设备在第 9 个 SCL 周期拉低 SDA 表示应答(ACK),保持高电平表示非应答(NACK)

一个完整的 I²C 传输帧结构如下:

| 起始条件 | 7位地址 | R/W | ACK | 数据字节 | ACK | ... | 数据字节 | ACK/NACK | 停止条件 |
I²C 字节传输帧结构 I²C 字节传输帧结构 S 7位地址 R/W ACK 数据字节 ACK ... 数据字节 ACK/NACK P

2.5 应答机制(ACK/NACK)

应答机制是 I²C 协议可靠性的核心。每个字节传输完成后,接收设备必须在第 9 个 SCL 周期内产生应答信号。

  • ACK(应答):接收设备在第 9 个 SCL 周期拉低 SDA,表示“已收到,请继续发送”。
  • NACK(非应答):接收设备在第 9 个 SCL 周期保持 SDA 高电平,表示“未准备好”或“传输结束”。

应答位的典型应用场景:

场景 发送方 接收方 应答行为
主设备发送地址 主设备 从设备 从设备 ACK:地址匹配;NACK:无此设备或忙
主设备写数据 主设备 从设备 从设备 ACK:数据已接收;NACK:无法处理
主设备读数据 从设备 主设备 主设备 ACK:继续读取;主设备 NACK:停止读取
驱动开发提示: 在 Linux I²C 驱动中,应答位由硬件控制器自动处理。但通过 i2c_msg 的 flags 字段设置 I2C_M_IGNORE_NAK 可以忽略 NACK,用于探测设备是否存在。此外,读取最后一个字节前主设备应发送 NACK,以通知从设备停止发送。

2.6 完整传输流程示例

以下是一个典型的 I²C 写操作流程,主设备向地址为 0x50 的从设备写入一个字节数据 0xAB:

  1. 主设备产生起始条件(S)。
  2. 主设备发送 7 位地址 0x50 + 写位(0),即 0xA0。
  3. 从设备在第 9 个 SCL 周期拉低 SDA 作为 ACK。
  4. 主设备发送数据字节 0xAB(二进制 10101011)。
  5. 从设备在第 9 个 SCL 周期拉低 SDA 作为 ACK。
  6. 主设备产生停止条件(P)。

对应的 C 代码片段(使用 Linux 内核 I²C 子系统):

#include <linux/i2c.h>

struct i2c_msg msg;
unsigned char buf[1] = {0xAB};

msg.addr  = 0x50;       // 从设备地址
msg.flags = 0;          // 写操作
msg.len   = 1;          // 数据长度
msg.buf   = buf;        // 数据缓冲区

// 通过 I²C 适配器发送消息
if (i2c_transfer(adapter, &msg, 1) < 0) {
    pr_err("I2C write failed\n");
    return -EIO;
}
调试技巧: 使用逻辑分析仪抓取 SCL 和 SDA 波形,可以直观验证起始条件、地址、数据、应答和停止条件是否正确。在驱动开发初期,建议先使用 i2c-tools 工具(如 i2cget、i2cset)验证硬件连接和协议时序。

2.7 本章小结

I²C 协议的核心要素包括:

  • 起始/停止条件:标识传输的边界,由主设备控制。
  • 数据有效性:SCL 高电平时 SDA 必须稳定,低电平时允许变化。
  • 字节格式:8 位数据 + 1 位应答,MSB 优先。
  • 应答机制:ACK 表示继续,NACK 表示停止或错误。