做硬件调试这几年,I2C 是我遇到问题最多的总线,没有之一。它只有 SDA、SCL 两根线,靠电平高低传数据,看起来比 UART 简单,可一旦设备扫不到、读出来全是 0xFF、或者偶尔好偶尔坏,很多朋友就不知道该拿万用表还是示波器下手。这篇文章不是复述 I2C 协议文档,而是按我自己的工程习惯,从万用表该测什么、示波器怎么设触发、怎么用第 9 个时钟上的 ACK 位做分段定位,整理成一条能照着做的排查流程。适合刚接触 I2C 的嵌入式新手,也适合那些示波器买了很久、但遇到问题只会乱戳按钮的同学。
1. 动手之前,先弄懂 I2C 的电气和时序底子
很多排查卡壳,不是示波器不会用,而是脑子里没有一套“正常的波形应该长什么样”的参照。I2C 的工作方式和推挽输出的 SPI 不一样,先把这个底子补齐,后面所有测量才有意义。
1.1 两根线上到底发生了什么:开漏结构和上拉电阻
I2C 的 SDA 和 SCL 都是开漏/开集电极结构,芯片内部的 MOS 管只能把线拉低,不能主动拉高。线上想要出现高电平,必须靠外部上拉电阻接到电源 VDD。所以空闲状态下,总线电压应该被上拉电阻拉到接近 VDD;某个设备要发送逻辑 0,就打开内部 MOS 管把线拉到地;要发送逻辑 1,就释放这条线,让它被上拉电阻拉回高电平。
这种设计的优点是多设备可以直接“线与”,谁输出低谁就赢,不需要额外的片选信号。代价是对上拉电阻很敏感。电阻选太大,总线上升沿太慢,超过 I2C 规范容许的上升时间,从机就可能采不到正确电平;电阻选太小,待机电流变大,而且部分从机灌电流能力有限,低电平可能抬不到规范允许的 VIL 以下。
上拉电阻的估算其实不难。总线上的分布电容、芯片引脚寄生电容加在一起,典型值在 50pF 到 200pF 左右。上升时间大概遵循 RC 特性,工程上按 30% 到 70% 电平常用的估算公式是:tr ≈ 0.8473 × R × C。举个例子,3.3V 供电、总线电容 100pF、上拉 4.7kΩ,算出来上升时间接近 397ns,跑 100kHz 模式可以接受,但跑 400kHz 就会紧张,因为后者最大上升时间只有 300ns。更稳妥的做法是直接按从机手册推荐值布板,调试阶段先挂 2.2kΩ 或 1kΩ 实验,再用示波器实测确认。
我调试时习惯先查一个关键状态:总线空闲时 SDA 和 SCL 应该都是高电平。如果某根线在空闲时就被拉低,说明有设备在“霸占”总线,或者上拉电阻没焊好。这两根线不是“有波形就行”,它们的静态电平、上升沿斜率、高电平幅度,每个参数都可能成为故障源。
1.2 时序和 ACK:第 9 个时钟是排查的分水岭
I2C 的时序没有并行总线的时钟约束那么复杂,但有几个关键点必须背下来。第一,数据在 SCL 高电平期间必须保持稳定,只能在 SCL 为低电平时变化。第二,START 条件是 SCL 为高时 SDA 产生一个下降沿,STOP 条件是 SCL 为高时 SDA 产生一个上升沿。平时我们看波形,先要能找到这个“高电平期间跳变”的起点,因为它代表一帧传输的开头。
每个字节发完 8 位数据后,在第 9 个 SCL 时钟周期会有一个 ACK 位。发送方在第 8 个时钟结束后主动释放 SDA,接收方如果正常接收,会把 SDA 拉低,让主机在第 9 个时钟的高电平期间采样到低电平,这就是 ACK。如果接收方不响应或者没收到,SDA 线会被上拉电阻拉高,主机采样到高电平,就是 NACK。
这里有个细节经常被新手忽略:写操作时,主机发完数据后释放 SDA,由从机回应 ACK;读操作时,主机作为接收方,在前面的字节也要主动拉低 SDA 回 ACK,只有读到最后一个字节前,主机要故意不拉低,发一个 NACK 告诉从机“不要再发了”。如果你在示波器上看到读取操作时主机地址之后从机已经 ACK,但数据段全是高电平,很可能不是从机没响应,而是主机代码在接收最后一个字节前没有正确处理 NACK 时序。
ACK 位是整个排查流程的“分水岭”。只要抓住“第 9 个时钟上 SDA 是低还是高”这个观测点,就能把问题切成三段:地址发出去有没有 ACK、寄存器地址有没有 ACK、数据字节有没有 ACK。绝大多数 I2C 故障,都能在这三个位置上找到对应症状。
2. 万用表在 I2C 排查中的真实定位
在拿出示波器之前,先用万用表把电气基础检查一遍,能节省大量盲目抓波的时间。万用表虽然看不到时序,但在“总线死锁”“上拉缺失”“短路”这三类问题上,效率比示波器高得多。
2.1 万用表能测什么,不能测什么
万用表能测的是静态或者慢变化量:空闲电平、电源是否到位、上下拉电阻是否虚焊、线路是否短路和断路。给系统上电后,可以把表笔分别点在 SDA 和 SCL 上,对 GND 测电压。正常情况下两块表笔测到的都接近 VDD。如果测到 0V,首先怀疑没上拉或者设备没上电;测到 VDD 的一半左右,多半是总线被某个设备拉低和上拉电阻在“较劲”的动态平均值,说明有芯片输出级损坏或者死锁,别继续猜,马上查是哪个设备把线咬死了。
万用表的输入阻抗通常是 10MΩ,对 I2C 总线并行接入的负载效应不算大,测空闲电平没问题。但它的采样率太低,抓不到毫秒级的 ACK 脉冲,也看不出上升沿是否太慢。更实际的作用是“断电后的电阻测量”:把系统断电,用蜂鸣档检查 SDA、SCL 有没有对地短路,测一下上拉电阻是否等于标称值,顺带检查排针、连接器各脚和杜邦线是不是接触良好。调试板上杜邦线松动导致 I2C 偶发不通的情况,我碰到过不止一次,这种问题直接用示波器去抓反而很难定位。
有一点要提醒:不要在总线正在通信时用万用表去碰高速变化的数据线,试图靠数字跳变判断“有没有信号”。万用表显示的是积分后的均值,跳来跳去反而误事。它擅长回答“有没有电”“是不是断”“是不是短”,不擅长回答“时序对不对”。只要涉及波形和 ACK,就轮到示波器上场了。
2.2 用万用表定位“总线忙”和上拉缺失
具体排查顺序,我习惯从最简单的“空闲电平”开始。第一步,系统上电但不发任何通信指令,用万用表直流电压档,红表笔点 SDA,黑表笔接 GND,记录电压;再点 SCL,记录电压。如果两个都是接近 VDD 的高电平,说明上拉和供电正常,可以放心打开逻辑分析仪或者示波器。如果任何一个电压明显偏低,就沿着这根线把所有并联设备逐个断开,每断开一个重新量一次,看哪一步后电平恢复正常。
第二步,处理总线死锁。我见过最多的情况是从机内部的 I2C 状态机卡在了中间状态,一直拉着 SDA 不放。此时常见做法是给主控发一个软件复位,或者对 SCL 连续产生 9 个额外时钟脉冲,帮助从机把状态机走完、释放总线。但如果你不知道哪个从机在捣乱,最稳的办法仍然是断电、断开疑似设备、重新上电,再用万用表确认总线已经回到高电平。先把“静态电平不对”这个问题消除,再谈后面的时序排查。
第三步,检查上下拉。I2C 系统里常见的错误是上拉电阻缺失或者用了 100kΩ 这种明显过大的值。把板上电后,掉电测量上拉电阻到 VDD 的通路,以及电阻两端阻值。如果使用的是 MCU 内部上拉,调试阶段先别太依赖它,内部上拉一般只有几十 kΩ,很多人误以为“单片机有内部上拉所以不用外接”,结果在 400kHz 模式下波形完全走样。如果你不确定,可以选择开漏模式加外部 4.7kΩ/2.2kΩ,这是最不容易出问题的组合。
万用表这部分能做的事情就这么多,结论很直接:电平基础没问题,继续;电平基础有问题,先解决再往下走。不要觉得这个步骤绕路,它往往能让你避开一次两小时的示波器空抓。
3. 示波器:从基础设置到协议解码
示波器是 I2C 排查的主力。先花十分钟把探头、触发、时基设置对,比事后对着一屏乱糟糟的波形猜答案要划算得多。这里说的都是很具体的操作,不是为了讲参数而讲参数。
3.1 探头、通道、触发的标准设置
通道分配方面,我习惯把 CH1 接 SDA,CH2 接 SCL,这样看协议解码时不容易搞混。探头打到 ×10 档,先做探头补偿。多数数字示波器前面板有 1kHz 方波校准输出,把探头接上去,调到屏幕上看到平顶的方波为止。很多稀奇古怪的波形畸变,根源就是探头没补偿好,尤其是二手示波器或者探头换来换去的情况。如果你手上是正点原子 DM40 这类入门示波器,同样先确认探头补偿波形,再做信号测量。
触发设置是抓 I2C 的关键。直接用 SCL 的边沿触发,抓到的一般只是反复变化的时钟,看不出帧的开头。更好的方法是利用“START 条件”触发:在 SCL 为高电平时,SDA 突然拉低的这一跳变作为触发点。中端以上的示波器,比如鼎阳、普源、力科的常见型号,都有“斜率触发”“脉冲触发”或者专门的串行触发菜单,能指定“SDA 下降沿且 SCL 为高”这样的条件;如果菜单里没有,退而求其次,用 SDA 通道的下降沿触发,再配合一段时基后移,也能把完整帧拉进屏幕。触发方式选对以后,示波器才会稳定地显示一帧从 START 到 STOP 的完整波形,而不是满屏乱跳。
时基设置取决于通信速率。100kHz 模式下一帧假设是“地址+寄存器+一个数据字节”,总时长大约 270µs,用 100µs/div 到 200µs/div 可以看到整帧;400kHz 模式下时间缩短到 70µs 左右,用 20µs/div。先保证采样率够用,再把水平刻度拉开,观察整帧和 ACK。部分示波器有自动测量功能,可以把 SCL 频率直接读出来,这比肉眼数格快很多。顺带一提,力科和鼎阳等品牌的示波器支持 SCPI 指令或者网页远程控制,调试过程中需要连续监控总线状态时,可以把触发后的波形导出到电脑上分析,比一张张按截图按钮省事。
3.2 看懂一帧:从 START 到 ACK 的标志位检查
把示波器调好以后,先建立一个“标尺”:到底什么样的波形算正常。正常情况下,空闲时两根线都是高位;START 出现后,SCL 开始产生方波,SDA 在每个字节的第 9 个时钟之前会被拉低一下。这个低脉冲就是 ACK。我用光标分别卡在第 8 个时钟下降沿和第 9 个时钟高电平中间,直接观察 SDA 的电平状态。SDA 为低,这次响应成功;SDA 为高,对端没有 ACK。别看其他东西,先看这一位。
示波器屏幕上的 I2C 波形,SDA 是一串不规则的高低变化,而 SCL 是规则方波。不要试图去“读”每一位二进制,那是逻辑分析仪和协议解码器的工作。手动排查时,我只需要确认三件事:START 位置是否明确、SCL 频率是否和目标一致、第 9 个时钟时 SDA 是否是低。只要这三个都正常,就可以判断通信流程走到了哪一步。
如果示波器有 I2C 协议解码功能,那会快很多。一般菜单里有“串行解码”或者“总线解码”,选择 I2C,指定 SDA 和 SCL 对应的通道,再设置逻辑阈值。常用阈值选 VDD/2,比如 3.3V 供电就设 1.65V 左右。设好之后,屏幕上会直接标出地址字节、数据字节和 ACK/NACK 标记。很多入门示波器比如 DM40 也有这一功能,别只当它是一个普通双通道示波器用。需要提醒的是,解码器标记的 NACK 是“它认为 SDA 在第 9 个时钟上没有变低”,这和张照片手动观察的结论等价,但偶尔会因为阈值设置错误而误判。最好还是切到原始波形,用光标亲自验一次。
4. 核心排查流程:从“无波形”到“NACK”到“偶发掉线”
前面把工具准备好了,这部分是真正动手的排查路线。我习惯把所有异常归成三类:完全没有波形、地址 ACK 失败、数据阶段错乱,每一类有不同的原因和对应操作。这里重点讲怎么用 ACK 做分段定位。
4.1 按字节分段排查:地址 ACK、寄存器 ACK、数据 ACK
一次典型的 I2C 写操作是“START + 从机地址 + 写位 + ACK + 寄存器地址 + ACK + 数据 + ACK + STOP”。三次从响应方回 ACK 的机会,每次都对应不同的故障点。
第一段是地址 ACK。主机发完地址字节后,第 9 个时钟上 SDA 有没有变低?如果这里就是高电平,说明从机根本没有响应。别急着怀疑时序,先检查从机供电、地址引脚状态,再看代码里的地址是不是写错了。I2C 地址最大的坑是 7 位地址和 8 位读写地址的换算。手册上写设备地址是 0x50,这通常是 7 位地址;代码里写 I2C 发送时,要左移一位变成 0xA0(写)或者 0xA1(读)。如果驱动封装已经帮你做了移位,你却重复移位一次,地址就完全不对,NACK 也是必然的。拿示波器看波形,直接在屏幕上读出地址字节的二进制值,比对是不是期望值,很容易排除这种问题。
第二段是寄存器地址 ACK。如果地址字节后已经看到 ACK,但随后寄存器地址字节后是 NACK,说明从机收到了地址,但认为后续的寄存器地址不合法。可能是寄存器的分页问题、该地址只支持写不支持读,也可能从机正忙,比如 Flash 写入期间不接受新的寄存器操作。这时候去看数据手册的寄存器表,并检查代码里是不是把“存储器写”和“寄存器写”的操作码搞混了。
第三段是数据字节阶段的 ACK 问题。写操作时,从机对每个数据字节都要回 ACK;如果某段数据后出现 NACK,常见原因是从机写保护开启、写入长度超过页大小、或者连续写太多字节把从机内部的缓冲区撑爆。读操作时,情况反过来,主机在收到最后一个字节前必须发 NACK 来示意停止。如果你在读取时发现从机已经正常 ACK,但读到的全是 0xFF,先别猜器件坏,看看是不是主机在最后字节上没有正确释放 SDA,导致后续数据线一直被主机拉低。典型的“总线上永远低电平”现象,往往就是主机状态机里漏了读操作最后一个字节的“释放总线”动作。
4.2 三个高频问题定位方法
第一个高频问题是总线死锁,症状是 SDA 或 SCL 一直低电平,万用表测出来就是 0V。造成死锁最常见的原因是从机状态机在传输中途被中断,比如发送方已经开始一个字节,主机却因为某次异常响应主动结束传输,从机还停在“等待第 9 个时钟”的状态里,死死拉着 SDA。此时给它正常的 SCL 时钟,但 SDA 一直被拉低,之后所有传输都会失败。解决办法是在 SCL 线上手动产生 9 个额外脉冲,把从机“踢”出等待状态;如果还不行,就断开从机电源,确认总线恢复高电平后,再从主机开始排查。这里面有一个容易忽略的点:产生复位脉冲时要保证 SCL 始终正常翻转,同时 SDA 保持高;很多人在软件里模拟 9 个脉冲时把两根线顺序搞反,越复位越乱。
第二个高频问题是地址 NACK,但硬件看起来没什么问题。这种时候优先怀疑地址配置引脚。不少 I2C 从机有 A0/A1/A2 硬件地址引脚,焊接时直接接地或者悬空,地址就会不同。板子上看到的丝印和芯片手册的推荐接法不一定一致,最好用示波器读一下实际线上跑的地址,再对照手册确认。另外还有一些 I2C 从机不支持“重复起始条件”,主机却在同一事务里先写地址后发读命令,设备就会在读到第二个地址时 NACK。遇到这种问题,不要只调示波器,还要看主机驱动里是否使用了 repeat-start,必要时改成“先 STOP 再 START”的分离式读写。
第三个高频问题是偶发 NACK 或偶发数据错误。波形大多数正常,但每隔一段时间就出现一个 NACK,或者读回来的字节某一位被毛刺污染。这时候重点测上升时间:打开示波器的自动测量,看看 SCL 和 SDA 的上升沿是不是已经超出所在速率模式的规定。100kHz 模式最大上升时间 1000ns,400kHz 模式是 300ns,1MHz 模式是 120ns。如果上升沿超了,优先减小上拉电阻。如果总线连线超过 10cm、用了排线或者长杜邦线,串一个 22Ω 到 100Ω 的电阻靠近主控端,能抑制振铃。电源纹波也要顺带看一眼,I2C 是电平判决总线,VDD 上叠加高频噪声,很容易让从机把低电平误判成高电平。
4.3 逻辑分析仪与自动化脚本的配合
示波器不是唯一工具。几十块钱的 USB 逻辑分析仪在 I2C 排查里也很有用,尤其是多字节长事务。它直接采样引脚高低电平,软件自动解码成地址、数据和 ACK 标记,还能连续抓很长时间,适合复现偶发故障。逻辑分析仪的问题在于看不到电压幅度和上升沿,属于“只懂逻辑、不懂电气”。所以我通常的逻辑是:先用示波器确认电气参数和触发位置,再用逻辑分析仪做长时间统计监控,两边互补。
如果你手上的示波器支持把波形导出成 CSV,也可以用 Python 写一个简单脚本,把捕获到的 SDA/SCL 电平变化按边沿恢复成数据位,自动打印十六进制帧。这个小工具不复杂,但对于长期调试某些只在特定操作序列下出错的问题很有效。拿 VISA 库调力科、鼎阳等示波器的 SCPI 接口也能实现类似功能,不过这是进阶玩法,刚开始不必折腾。
5. 常见故障速查表和实操中的几个习惯
最后把经验压缩成一张表,方便大家在实际布线、焊接、调驱动时对照。这张表是我多次故障笔记的浓缩,比单纯背协议管用。
5.1 常见现象、原因、解决方向对应表
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 空闲时 SDA 或 SCL 为 0V | 上拉缺失、设备未上电、从机死锁 | 万用表逐段测电平,断开设备重新上电 |
| SCL 有方波,SDA 一直高 | 地址错误、从机未激活、重复启动时序冲突 | 示波器读地址二进制值,对照手册和驱动 |
| 地址后有 ACK,寄存器地址后 NACK | 寄存器地址非法、寄存器分页、设备忙 | 查手册寄存器表,确认操作码 |
| 写操作数据后 NACK | 写保护、页长度超限、缓冲满 | 检查写保护引脚,拆分写包 |
| 读操作返回 0xFF | 主机读操作释放 SDA 时序错误、从机空闲 | 观察读操作数据段第 9 个时钟的 SDA 电平 |
| 通信偶发 NACK/数据错 | 上升沿太慢、长线振铃、电源噪声 | 测上升时间,减小上拉,加串阻,看电源纹波 |
| 低速正常,高速模式出错 | 上拉电阻太大、从机不支持高速 | 按速率规范重新计算上拉,降低时钟测试 |
5.2 实操中我习惯坚持的注意事项
第一,每次换通道、换探头、换示波器,都要先做一次探头补偿。很多看起来像是 I2C 时序畸变的波形,其实是探头电容没匹配好。不要高估记忆,也别嫌这一步麻烦,用示波器之前的探头校准输出,30 秒钟的事情。
第二,在信号不稳定时,把示波器的触发时基调到几十毫秒,然后从 STOP 条件往回找异常点。I2C 的偶发故障不会每次都压在同一个字节上,直接盯第一帧反而容易漏。使用长时间采集,让示波器等待“再一次触发”并保持波形冻结,再去判断是哪个字节出了问题。很多入门示波器在“扫描”或者“滚动”模式下也能实现类似效果,别只盯着普通触发界面。
第三,调试之前先断开所有无关的 I2C 从设备,只保留一个最小系统。如果单从机能通、多设备时不通,问题往往出在地址冲突、总线电容过大、或者某个从机上拉又被额外挂了一路上拉。这类“上拉叠加”的问题在示波器上看起来只是高电平偏高,实际却可能导致低电平电压超标,让从机无法识别逻辑 0。
第四,保存一份“健康波形”作为参照。我一般在拿到一块新板子、第一次跑通 I2C 后,会把示波器离线的地址字节、ACK 位置、上升沿时间截图存档。后面一旦出现定位困难的偶发故障,把这些历史截图调出来对比,哪个字节变样一目了然,比重新分析协议高效得多。
最后再分享一个个人习惯:排查 I2C 时,我会同时打开示波器的协议解码和原始波形,解码器负责提示“哪里是 NACK”,原始波形负责确认“为什么 NACK”。只看解码器容易忽略电气层面的诱因,只盯波形又太慢。两边配合,绝大多数 I2C 问题都能在半小时内收敛到某个具体原因上。下次再拿到一台“怎么都连不上”的传感器模块,不妨按这个顺序走一遍,你会有种“原来是这样”的踏实感。