3. I2C通信中的常见错误类型

做I2C通信,说白了就是在两根线上跳探戈。时钟线(SCL)和数据线(SDA)配合得好,数据才能顺利传输。但实际项目中,总会有意外发生。我做了这么多年嵌入式,踩过的坑不少,今天就把最常见的四种错误类型掰开揉碎了讲清楚。

这四种错误分别是:仲裁丢失、NACK、时钟拉伸和数据损坏。它们就像四个拦路虎,搞不定它们,你的I2C通信就别想稳定。

3.1 仲裁丢失 —— 谁在抢总线?

I2C是多主设备总线,理论上多个主设备可以同时发起通信。但实际中,总得有人让步。仲裁机制就是为了解决这个“谁说了算”的问题。

为什么会发生?

当两个主设备同时向SDA线发送数据时,如果一个设备输出高电平,另一个输出低电平,总线电平会被拉低。发送高电平的设备发现SDA线上的电平跟自己预期的不同,就知道自己“仲裁丢失”了。

核心判断标准:

  • 发送高电平,但读回的是低电平 → 仲裁丢失
  • 发送低电平,读回也是低电平 → 正常

我的经验:我刚开始做I2C驱动时,就吃过仲裁丢失的亏。那是一个多主设备的传感器网络,两个主设备同时查询同一个从设备地址,结果数据全乱了。后来我在每个主设备的启动条件前加了一个随机延时,冲突概率从30%降到了几乎为零。

怎么处理?

  1. 检测到仲裁丢失后,立即释放总线(SDA和SCL都设为高电平)。
  2. 等待一个随机时间后重试。
  3. 不要在同一时刻重新发起START条件,避免再次冲突。

避坑指南:我曾经踩过一个坑——仲裁丢失后直接重试,结果又冲突了。原因是我用的随机延时太短,两个设备的随机数几乎一样。现在我的做法是:用低几位有效位做随机延时基数,这样即使时间相近,也能错开。

3.2 NACK —— 从机不理你

NACK(Not Acknowledge)是I2C通信中最让人头疼的错误类型之一。它意味着从设备没有回应你的请求。

NACK的常见场景:

场景 原因
从机地址NACK 地址写错了,或者从机不存在
数据NACK 从机忙,无法接收新数据
读NACK 从机没有可读数据

我个人习惯这样处理:

  • 收到地址NACK后,先别急着报错。我会重发一次地址,确认是不是瞬态干扰。
  • 如果连续两次NACK,再检查硬件连接和地址配置。我遇到过很多次因为地址引脚电平设置不对导致的NACK。
  • 对于从机忙导致的NACK,等待一段时间后重试,通常能解决。

小技巧:我建议在初始化阶段,主动发送一个假的从机地址并等待NACK,用来验证总线的应答信号逻辑是否正常。这不难,但能帮你提前发现硬件问题。

3.3 时钟拉伸 —— 从机说“慢点”

时钟拉伸(Clock Stretching)是I2C的一种“流量控制”机制。从设备通过拉低SCL线,告诉主设备:“我还没准备好,你等等。” 主设备必须等到SCL变高才能继续。

为什么会出现?

  • 从机内部处理速度慢,比如EEPROM正在写入。
  • 从机需要额外时间准备数据。

避坑指南:我曾经开发过一个项目,用了某款温湿度传感器。从机每次在ACK后会拉伸时钟最多300微秒。我当时没做超时处理,结果一次通信卡死在那里,整个系统都挂了。从那以后,我所有I2C驱动都加了超时计数器,超过10个时钟周期没拉高SCL,我就释放总线并报错。

怎么正确应对?

  1. 在SCL拉高后,启动一个超时计时器。
  2. 如果计时器溢出时SCL还是低电平,说明从机异常或总线卡住。
  3. 进入错误处理:先释放总线(SDA=1,SCL=1),然后发送9个额外的时钟脉冲,尝试让从机恢复。

代码示例(伪代码):

// 时钟拉伸超时处理
uint32_t timeout = 0;
while (!(SCL & 1)) {  // 等待SCL变高
    timeout++;
    if (timeout > MAX_TIMEOUT) {
        // 释放总线
        SDA = 1;
        SCL = 1;
        // 发送9个额外时钟尝试恢复
        for(int i=0; i<9; i++) {
            SCL = 0; delay_us(5);
            SCL = 1; delay_us(5);
        }
        return ERROR_STRETCH_TIMEOUT;
    }
}

3.4 数据损坏 —— 信号脏了

数据损坏,说白了就是信号在传输过程中被干扰了。电磁干扰、电源噪声、走线过长,都可能导致数据位翻转。

表现:

  • 收到的数据值与预期不符。
  • ACK变成NACK。
  • START/STOP条件被误识别。

抗干扰的核心思路:

  • 硬件层:上拉电阻选4.7kΩ~10kΩ,走线尽量短,加TVS管保护。
  • 协议层:循环冗余校验(CRC)是最好的数据完整性保障。这是本课程的核心,后面几章会详细讲。

我遇到过一个典型案例:某工业控制板,I2C线路经过一段长线缆,每次在电机启动时数据就乱。后来用示波器一看,SDA线上有强烈的脉冲干扰。加了CRC校验后,所有错误都被检测了出来,再配合重试机制,系统就稳定了。

3.5 本章知识体系总览

下面这张图把四种错误类型的关系梳理清楚了。你可以把它当作快速参考。

I2C通信常见错误类型关系图 I2C通信常见错误类型 I2C通信 仲裁丢失 NACK 时钟拉伸 数据损坏 处理要点 释放总线 → 随机延时 → 重试START 处理要点 重试一次 → 检查地址 → 检查硬件 处理要点 设置超时 → 释放总线 → 发送额外时钟 处理要点 硬件抗干扰 + CRC → 重试机制 核心思路:检测 → 重试 → CRC校验 → 保证数据完整性

我的使用习惯:上面这张图我经常打印出来贴在工位前。每次排查I2C问题,我都先对号入座,判断是哪类错误,然后按图里提示的步骤处理。说实话,用多了就变成肌肉记忆了。

好了,四种错误类型都讲完了。仲裁丢失是多主机冲突,NACK是从机不响应,时钟拉伸是从机拖慢节奏,数据损坏是外界干扰。理解了这些,你就清楚为什么需要CRC校验了——因为前三种错误可以通过协议和重试解决,但数据损坏只有靠CRC这样的强校验才能彻底搞定。