I2C 协议基础:时序、起始条件、停止条件、数据位、ACK/NACK

做嵌入式这些年,I2C 是我打交道最多的总线——两根线,一主多从,看似简单,但坑一点不少。很多新手觉得“只要连上就能用”,结果波形一塌糊涂。今天我从逻辑分析仪的实际观察出发,把这些基础拆开揉碎,你以后抓波形时心里就有谱了。

1.1 两根线的默契:SCL 与 SDA

I2C 只有两条线:SCL(时钟)和 SDA(数据)。主设备控制时钟,从设备在时钟节拍下响应。所有通信都以起始条件开始,以停止条件结束。数据在 SCL 高电平时被采样,低电平时允许变化。嗯,核心就这些。

我遇到过两次因为上拉电阻选错导致通信间歇性失败的项目。你想想看,如果上拉太强(比如 1kΩ),上升沿虽快但功耗大、容性负载受限;上拉太弱(比如 10kΩ),上升沿变缓,SDA 在 SCL 高电平还没稳定就被采样了。逻辑分析仪上一看,数据位全是毛刺。

1.2 起始条件与停止条件——总线的“令牌”

起始条件:SCL 为高时,SDA 从高切换到低。停止条件:SCL 为高时,SDA 从低切换到高。这两个条件定义了总线的使用权移交。你可以把它们理解为通信的“开关”。

我个人习惯在逻辑分析仪上设置 SDA 下降沿触发的同时,检查 SCL 是否确实为高。有一次我在调试一块温湿度传感器,发现它经常不响应。抓波形后看到起始条件发生时 SCL 还没完全变高——建立时间不足,从机根本认不出这是起始。后来在代码里加了 1μs 的延时才解决。

条件SCL 状态SDA 变化时序要求
起始条件高下降沿(高→低)SDA 下降前 SCL 必须稳定在高电平至少 4.7μs(100kHz 标准模式)
停止条件高上升沿(低→高)SDA 上升前 SCL 必须稳定在高电平至少 4.0μs
在逻辑分析仪中配置触发条件:选择 SDA 下降沿,并添加 SCL 为高电平的判定条件。这样能精准捕获起始条件,避免误触发。

1.3 数据位与字节传输——SCL 高电平时才说了算

每个字节 8 位,加上 1 位应答位,共 9 个 SCL 周期。数据在 SCL 高电平时被采样,所以 SDA 只能在 SCL 低电平时改变。如果逻辑分析仪显示 SDA 在 SCL 上升沿附近变化,那大概率是时序违规——从机可能采到错误值。

我去年修过一个 LCD 驱动的 bug,显示字符偶尔乱码。用逻辑分析仪一帧一帧对比,发现第 7 位数据(高位)变化时恰好卡在 SCL 上升沿上。最终发现是主控的 GPIO 翻转速度太快,SDA 变化时间与 SCL 上升沿重叠。解决办法是在 SCL 下降沿后插入一个微的延迟再改变 SDA。

I2C 标准规定了最小高低电平时间。比如标准模式(100kHz)要求 SCL 高电平 ≥ 4.0μs,低电平 ≥ 4.7μs。如果主控跑在更高频率(比如 400kHz 快速模式),这些时间会缩短。但无论哪种模式,不要在 SCL 高电平时改变 SDA,除非你想发送起始/停止条件。

1.4 ACK/NACK——握手的“点头”与“摇头”

主机发送完 8 位数据后,在第 9 个时钟释放 SDA。如果从机正常工作,它会将 SDA 拉低一个时钟周期,表示 ACK(应答);维持高电平则表示 NACK(非应答)。NACK 通常用来结束读取操作或报告错误——比如从机正忙、地址不对或数据异常。

我记得有一次调试一个 EEPROM,写数据后读回来总是不对。逻辑分析仪捕获到第 9 个时钟时 SDA 一直为高——从机根本没 ACK。查手册发现 EEPROM 在写周期内不会应答任何命令。我在写操作后加了 5ms 延时,再读就正常了。这个事情告诉我:不要把 NACK 直接当故障,它可能是正常流程的一部分。

SDA 在第 9 时钟状态含义常见原因
低(被从机拉低)ACK从机已接收或准备好发送
高(主机释放后仍为高)NACK地址不匹配、从机忙、数据错误、传输结束
为什么有时会看到 SDA 在第 9 时钟被从机拉低后又马上变高?这可能是因为从机内部逻辑出错,ACK 脉冲宽度不足。用逻辑分析仪的脉宽测量功能可以轻松捕捉。

I2C 一次完整传输流程

下面这张图概括了从起始到停止的完整流程。它是我在调试时贴在显示器旁边的参考,你也可以保存下来对照波形。

I2C 一次完整传输流程 完整传输帧 起始条件 发送 7 位地址 + R/W 检查 ACK 发送数据字节(8 位) 检查 ACK 停止条件 R/W = 0 写入, 1 读取 从机拉低 SDA 为 ACK

实际中,你可以在逻辑分析仪上设置触发条件为“起始条件 + 特定地址”来捕获某个从机的通信。把上图印在脑子里,看波形时就能快速定位问题。

实战代码:模拟 I2C 起始条件

下面是一个底层 GPIO 模拟 I2C 起始条件的函数。注意时序延迟必须匹配所用频率。我用标准模式(100kHz)作为例子,你可以根据自己芯片的时钟频率调整。

void i2c_start(void)
{
    // 确保 SDA 和 SCL 初始为高
    SDA_HIGH();
    SCL_HIGH();
    delay_us(5);        // 保持时间,标准模式要求 ≥4.7μs

    // 在 SCL 高电平时拉低 SDA 产生起始条件
    SDA_LOW();
    delay_us(5);        // 起始保持时间

    // 拉低 SCL 准备传输时钟
    SCL_LOW();
    delay_us(1);        // 确保 SCL 完全变为低
}
使用逻辑分析仪时,将光标放在起始条件的 SDA 下降沿上,测量 SCL 从高到 SDA 下降的时间差。如果这个时间小于标准规定的最小保持时间(4.7μs),就要在代码中增加延迟。

以上便是 I2C 协议最基础的部分。起始/停止的边沿必须干净,数据必须在 SCL 高时稳定,ACK 必须被正确识别。把这些“规矩”记牢,后续分析波形失真时你才能分辨出是协议错误还是信号完整性问题。