3. I2C故障排查:总线锁死、地址冲突、应答错误、时钟拉伸异常、上拉电阻选择不当

I2C 总线调试,说白了就是跟波形和时序打交道。我做了十几年嵌入式,遇到最多的坑就是这五个:总线锁死、地址冲突、NACK、时钟拉伸异常、上拉电阻选错。今天咱们一个一个掰开揉碎了讲。

核心原则:I2C 是开漏总线,任何设备都能拉低 SCL 或 SDA。故障排查的第一步,永远是拿示波器看波形,而不是猜代码。

3.1 总线锁死(SCL/SDA被拉低)

总线锁死是最常见的故障,没有之一。现象就是 SCL 或 SDA 一直为低电平,主设备发不出起始条件,整个总线瘫痪。

3.1.1 锁死原因分析

原因 典型场景 排查方向
从设备未释放 SDA 从设备在应答位后仍拉低 SDA 检查从设备状态机、复位逻辑
主设备异常中断 主设备在传输中复位,未发送停止条件 增加超时复位机制
SCL 被从设备拉低 从设备时钟拉伸超时 检查从设备时钟拉伸最大时间
总线竞争 多个主设备同时发送不同数据 检查多主设备仲裁逻辑

我个人习惯,遇到总线锁死第一件事就是量电压。用万用表测 SCL 和 SDA 对地电压,如果接近 0V,基本就是被拉死了。然后断开所有从设备,只留主设备和上拉电阻,看总线能不能恢复高电平。能恢复,说明某个从设备有问题;不能恢复,查主设备 GPIO 配置和上拉电阻。

避坑指南:我曾经遇到一个案子,SDA 被拉低后怎么查都查不出原因。最后发现是从设备的 VDD 掉电了,但 SDA 引脚还连着总线,内部 ESD 二极管把总线钳位到地了。所以排查总线锁死时,别忘了检查每个从设备的供电。

3.1.2 软件恢复策略

硬件排查完了,软件上也得有兜底方案。我建议在驱动层加一个总线恢复函数:

void i2c_bus_recover(void) {
    // 1. 先发 9 个时钟脉冲,让从设备释放 SDA
    for (int i = 0; i < 9; i++) {
        SCL_HIGH();
        delay_us(5);
        SCL_LOW();
        delay_us(5);
    }
    // 2. 发送停止条件
    SDA_LOW();
    delay_us(5);
    SCL_HIGH();
    delay_us(5);
    SDA_HIGH();
    delay_us(5);
    // 3. 检查总线是否释放
    if (SDA_READ() == 1 && SCL_READ() == 1) {
        // 总线恢复成功
    } else {
        // 硬件复位所有从设备
    }
}

这个函数的核心逻辑就是模拟 9 个时钟脉冲,让处于中间状态的从设备完成当前操作,释放 SDA。嗯,这里要注意:9 个脉冲不是随便定的,I2C 规范里规定从设备必须在 9 个时钟内完成一个字节的传输。

3.2 地址冲突

地址冲突说白了就是两个从设备用了同一个 I2C 地址。现象是主设备发地址后,总线上有两个设备同时应答,数据就乱了。

3.2.1 地址分配规则

I2C 设备地址分 7 位和 10 位两种,咱们平时用的最多的是 7 位地址。每个从设备都有一个固定地址,但很多芯片会留出几个引脚(比如 A0、A1、A2)让你配置地址的低几位。

地址类型 地址范围 保留地址 可用地址数
7 位地址 0x00 ~ 0x7F 0x00(通用呼叫)、0x01(起始字节)等 112 个
10 位地址 0x000 ~ 0x3FF 保留地址段 1024 个

经验之谈:我建议在设计阶段就把所有从设备的地址列一张表,标注出可配置的地址引脚。如果两个设备地址冲突,优先考虑改地址引脚的电平,而不是换芯片。

3.2.2 排查方法

排查地址冲突,最直接的办法就是逐个从设备测试。断开所有从设备,只接一个,发它的地址看有没有 ACK。然后换下一个。如果单独接都有 ACK,接在一起就出问题,那基本就是地址冲突了。

你想想看,如果总线上有两个地址一样的设备,主设备发地址时,两个设备都会拉低 SDA 做应答。但数据阶段,两个设备可能输出不同的数据,导致 SDA 被一个拉高一个拉低,结果就是总线电平不确定,主设备读到的数据全是乱的。

3.3 应答错误(NACK)

NACK 就是主设备发完地址或数据后,从设备没有拉低 SDA 做应答。这是 I2C 调试里最常看到的错误。

3.3.1 NACK 的几种情况

  • 地址 NACK:主设备发完从设备地址后,第 9 个时钟 SDA 为高。原因:地址不对、从设备不存在、从设备忙。
  • 数据 NACK:主设备发完数据后,从设备不应答。原因:从设备不支持该命令、接收缓冲区满、从设备正在处理上一条指令。
  • 读 NACK:主设备读完最后一个字节后,主动发送 NACK 表示不再读。这是正常行为,不是故障。

关键点:地址 NACK 一定是故障,数据 NACK 可能是协议规定的正常行为,读 NACK 是主设备主动发出的。别把正常的读 NACK 当成故障来查。

3.3.2 排查步骤

  1. 用示波器抓地址阶段的波形,确认主设备发出的地址是否正确(注意 7 位地址左移 1 位后加上 R/W 位)。
  2. 检查从设备的供电和复位引脚,确保从设备处于正常工作状态。
  3. 检查从设备的 I2C 地址配置引脚,用万用表量一下电平对不对。
  4. 如果地址正确,从设备也正常,但就是 NACK,试试降低 I2C 速率。有些从设备在高速下会应答失败。

我记得有一次调一个温度传感器,地址 NACK 查了半天,最后发现是数据手册上写的地址是 7 位值 0x48,但代码里直接发了 0x48,没左移。实际上 I2C 地址发送时是 8 位,高 7 位是地址,最低位是 R/W。所以 0x48 左移一位变成 0x90,再加上写位 0 就是 0x90。这个坑很多新手都会踩。

3.4 时钟拉伸异常

时钟拉伸是从设备的一种流控机制。从设备在应答位后拉低 SCL,告诉主设备“我还没准备好,你等等”。主设备必须检测 SCL 电平,等从设备释放 SCL 后才能继续发时钟。

3.4.1 时钟拉伸超时

问题出在有些主设备不支持时钟拉伸,或者从设备拉伸时间太长。比如某些 MCU 的硬件 I2C 外设在检测到 SCL 被拉低后,会直接超时报错。

从设备类型 典型时钟拉伸时间 注意事项
EEPROM(写操作) 最大 5ms 写周期内会拉伸时钟
传感器(ADC 转换) 最大 10ms 转换期间拉伸时钟
OLED 控制器 最大 500μs 命令处理期间拉伸

避坑指南:我曾经用 STM32 的硬件 I2C 外设驱动一个 EEPROM,写操作时总是超时。查了数据手册才发现 EEPROM 写周期会拉伸时钟最长 5ms,而 STM32 硬件 I2C 的时钟拉伸超时默认只有 1ms。解决办法是把超时时间改成 10ms,或者用软件 I2C 模拟。

3.4.2 软件 I2C 处理时钟拉伸

用软件 I2C 模拟时,时钟拉伸的处理其实很简单:在 SCL 拉高后,加一个超时循环检测 SCL 电平,直到从设备释放 SCL 或超时退出。

uint8_t i2c_wait_scl_release(uint32_t timeout_us) {
    while (timeout_us--) {
        if (SCL_READ() == 1) {
            return 1;  // SCL 已释放
        }
        delay_us(1);
    }
    return 0;  // 超时
}

这个函数在每次 SCL 拉高后调用,确保从设备不拉低 SCL 才继续。如果超时,就报错并执行总线恢复流程。

3.5 上拉电阻选择不当

上拉电阻是 I2C 总线最容易被忽视的环节。电阻选大了,信号上升沿太慢;选小了,功耗大且可能拉不低。

3.5.1 电阻值计算

上拉电阻的最小值和最大值由以下公式决定:

  • 最小值:Rmin = (VDD - VOL) / IOL,其中 VOL 是输出低电平最大值(通常 0.4V),IOL 是灌电流能力(通常 3mA ~ 20mA)。
  • 最大值:Rmax = tr / (0.8473 × Cb),其中 tr 是上升时间要求,Cb 是总线电容(包括引脚电容、走线电容、上拉电阻寄生电容)。
总线速率 最大上升时间 推荐上拉电阻(Cb=100pF) 推荐上拉电阻(Cb=400pF)
标准模式(100kHz) 1000ns 4.7kΩ ~ 10kΩ 2.2kΩ ~ 4.7kΩ
快速模式(400kHz) 300ns 1.5kΩ ~ 4.7kΩ 1kΩ ~ 2.2kΩ
快速模式+(1MHz) 120ns 1kΩ ~ 2.2kΩ 470Ω ~ 1kΩ

经验之谈:我一般先按 4.7kΩ 起步,然后用示波器看上升沿。如果上升沿太缓(超过允许值的 70%),就换小一点的电阻。如果总线电容大(比如走线长、挂的设备多),直接上 2.2kΩ 甚至 1kΩ。

3.5.2 常见问题

  • 电阻太大:上升沿变缓,高速通信时数据采样错误。现象是偶尔通信失败,降低速率后正常。
  • 电阻太小:从设备拉低总线时电流过大,可能导致从设备输出级损坏。现象是从设备发热、通信不稳定。
  • 电阻缺失:总线无法回到高电平,SCL 和 SDA 一直为低,通信完全无法进行。

你想想看,如果总线电容是 200pF,上拉电阻用了 10kΩ,那 RC 时间常数就是 2μs。对于 400kHz 的快速模式,允许的上升时间只有 300ns,这显然不够。所以上拉电阻的选择一定要结合总线电容和速率来算。

3.6 本章知识体系

下面这张图把 I2C 故障排查的五个核心问题串起来了,方便你对照排查:

I2C 故障排查知识体系 I2C 故障排查五大核心问题 I2C 总线故障 总线锁死 地址冲突 应答错误 NACK 时钟拉伸异常 上拉电阻不当 排查要点 • 量 SCL/SDA 对地电压 • 断开从设备逐级排查 • 检查从设备供电 • 软件 9 脉冲恢复 排查要点 • 列出所有从设备地址 • 逐个从设备测试 ACK • 检查地址配置引脚 • 注意 7 位/10 位地址 排查要点 • 区分地址/数据/读 NACK • 抓波形确认地址值 • 检查从设备状态 • 尝试降低速率 排查要点 • 查从设备拉伸时间 • 主设备超时配置 • 软件 I2C 加超时检测 • 考虑换硬件 I2C 排查要点 • 计算 Rmin 和 Rmax • 用示波器看上升沿 • 考虑总线电容 • 4.7kΩ 起步调试 排查顺序:波形 → 电压 → 地址 → 速率 → 电阻

这张图把五个问题的排查要点都列出来了。实际调试时,我建议按这个顺序来:先看波形,确认总线有没有被拉死;再量电压,排除供电问题;然后查地址和应答;最后调速率和上拉电阻。按这个流程走,90% 的 I2C 问题都能在半小时内定位到根因。