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 排查步骤
- 用示波器抓地址阶段的波形,确认主设备发出的地址是否正确(注意 7 位地址左移 1 位后加上 R/W 位)。
- 检查从设备的供电和复位引脚,确保从设备处于正常工作状态。
- 检查从设备的 I2C 地址配置引脚,用万用表量一下电平对不对。
- 如果地址正确,从设备也正常,但就是 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 故障排查的五个核心问题串起来了,方便你对照排查:
这张图把五个问题的排查要点都列出来了。实际调试时,我建议按这个顺序来:先看波形,确认总线有没有被拉死;再量电压,排除供电问题;然后查地址和应答;最后调速率和上拉电阻。按这个流程走,90% 的 I2C 问题都能在半小时内定位到根因。