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

I2C(Inter-Integrated Circuit)总线协议由Philips(现NXP)设计,是一种同步、多主从、串行通信总线。理解其底层信号时序是驱动开发与故障排查的基石。本章将拆解I2C协议的核心原子操作:起始/停止条件、数据采样窗口、字节传输格式以及应答机制。

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

在讨论具体时序前,需明确总线的默认状态:

  • SCL(时钟线):空闲时为高电平,由主机驱动。
  • SDA(数据线):空闲时为高电平,通过上拉电阻保持。
  • 线-与逻辑:任何设备拉低SDA或SCL都会使总线变为低电平。这是时钟拉伸和仲裁的物理基础。
驱动提示: 在初始化I2C控制器时,务必确认GPIO配置为开漏输出,并检查外部上拉电阻(通常4.7kΩ)是否焊接正常。缺少上拉电阻会导致总线卡死在低电平,无法产生起始条件。

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

起始和停止条件是I2C通信的“帧定界符”,由主机独占产生。

条件 信号序列 驱动代码关键点
起始条件 (S) SCL保持高电平时,SDA从高电平切换到低电平 必须先拉高SCL,再拉低SDA;需满足tHD;STA保持时间
停止条件 (P) SCL保持高电平时,SDA从低电平切换到高电平 必须先拉低SDA,再释放SCL;需满足tSU;STO建立时间
重复起始条件 (Sr) 在数据传输中,不产生停止条件,直接产生起始条件 用于改变数据方向(如读寄存器前先写地址)
总线故障场景: 如果从机时钟拉伸导致SCL被拉低,主机无法产生停止条件。此时总线会永久阻塞。驱动中必须实现超时机制,在SCL低电平超过阈值(如25ms)时强制复位总线状态机。
起始条件 (S) 与 停止条件 (P) 时序 SCL SDA S SDA下降沿 P SDA上升沿 SCL高电平时SDA跳变

2.3 数据有效性(Data Validity)

I2C协议对数据采样有严格规定,这是驱动中SCL时序控制的依据:

  • 数据稳定期:SCL为高电平时,SDA上的数据必须保持稳定。
  • 数据变化期:SCL为低电平时,SDA允许变化(准备下一位数据)。
  • 例外:起始和停止条件利用SCL高电平时的SDA跳变,不属于数据传输。
驱动实现要点: 在SCL低电平时更新SDA输出寄存器,在SCL高电平中间点采样SDA输入。大多数硬件I2C控制器自动处理此逻辑;若使用GPIO模拟I2C(Bit-Bang),必须严格按此顺序编写延时循环。

2.4 字节格式与数据传输

I2C总线以字节(8位)为基本传输单元,高位(MSB)在前。每传输一个字节后,必须跟一个应答位(ACK/NACK)。

字段 位数 说明
从机地址 7位 或 10位 标准模式为7位地址,后跟1位读写方向位
读写位 (R/W) 1位 0 = 主机写(发送),1 = 主机读(接收)
数据字节 8位 × N 每个字节后跟一个应答位,无字节数上限
应答位 (ACK) 1位 接收方在第9个SCL周期拉低SDA表示应答
I2C 字节传输格式(7位地址 + 读/写 + 数据) S A6 A5 A4 A3 A2 A1 A0 R/W ACK D7 D6 D5 D4 D3 D2 D1 D0 ACK 从机地址 (7位) 方向 数据字节 (8位) 数据传输方向 (MSB First) 每个字节后接收方必须发送ACK(第9个SCL周期) NACK表示传输结束或设备忙

2.5 应答机制(ACK/NACK)

应答是I2C协议可靠性的核心。每个字节传输完成后,接收方必须反馈应答信号:

  • ACK (Acknowledge):接收方在第9个SCL周期拉低SDA。表示“已收到,请继续发送下一字节”。
  • NACK (Not Acknowledge):接收方在第9个SCL周期保持SDA高电平。表示“无法接收更多数据”或“未找到设备”。

2.5.1 应答在驱动中的典型场景

场景 发送方 接收方 驱动处理
主机发送地址 主机 从机 若收到NACK,说明从机不存在或忙,驱动应重试或报错
主机发送数据 主机 从机 若收到NACK,从机无法处理数据,主机应停止传输
主机接收数据 从机 主机 主机在最后一个字节发送NACK,通知从机停止发送
驱动最佳实践: 在Linux I2C适配器驱动中,应答处理通常由硬件控制器自动完成。但在GPIO模拟模式下,必须在第9个SCL周期释放SDA并采样其电平。采样超时(如SCL被从机拉低)应触发错误恢复流程。

2.6 完整数据帧示例:写寄存器操作

以下是一个典型的I2C写操作帧结构,用于向从机设备写入一个寄存器地址和数据:

| S | 从机地址 (7位) | W (0) | ACK | 寄存器地址 (8位) | ACK | 数据字节 (8位) | ACK | P |

对应的Linux用户态操作(通过i2c-dev接口):

#include <linux/i2c-dev.h>
#include <sys/ioctl.h>

struct i2c_msg msgs[1];
struct i2c_rdwr_ioctl_data msgset;

unsigned char buf[2] = {0x10, 0xA5}; // 寄存器地址0x10, 数据0xA5

msgs[0].addr  = 0x50;       // 从机地址
msgs[0].flags = 0;          // 写操作
msgs[0].len   = 2;
msgs[0].buf   = buf;

msgset.msgs  = msgs;
msgset.nmsgs = 1;

if (ioctl(fd, I2C_RDWR, &msgset) < 0) {
    perror("I2C write failed");
    // 处理NACK或总线错误
}
故障排查提示: 如果ioctl返回-EIO或-EREMOTEIO,通常表示从机返回了NACK。常见原因包括:地址错误、从机处于睡眠状态、寄存器地址越界、或总线被其他设备占用。使用逻辑分析仪抓取SDA/SCL波形是定位问题的最直接手段。

2.7 本章小结