1. 先搞清楚:I2C 调试到底在调什么
干硬件这些年,I2C 大概是我调得最多也最容易被“看似简单”坑到的一个总线。两根线、一个时钟、一个数据,看似比 SPI 和 UART 都省事,可一旦设备不回 ACK、读出来的寄存器全是 0xFF、或者总线莫名其妙被拉死,你就知道这个协议并没有想象中那么温和。这篇东西想聊的,就是把“I2C 信号怎么测”这件事从头到尾捋一遍:万用表能验什么、示波器该怎么设、ACK 这根“命脉”怎么抓、以及现场排查时到底按什么顺序走,才能不瞎折腾。
先说结论:I2C 调试的核心就三件事——电平对不对、时序对不对、应答有没有。电平靠万用表和示波器直流档看,时序靠示波器触发和展开看,应答是否出现则是判断从机是否活着的关键证据。你可以不用背协议手册里的每个细节,但一定要会抓一帧完整报文,并且能指着屏幕说出“这里为什么是 NACK”。
这篇文章适合刚接手第一块 I2C 从设备、正被“设备不响应”折腾的工程师,也适合那些已经会调但总在 ACK 异常上反复踩坑的同行。我会把从静态检查到动态抓包的过程完整写出来,配合实测波形经验,尽量做到你看完就能照着操作。
1.1 两条线、一个时钟、一个应答,别被协议吓住
I2C 物理层就两条线:SCL(时钟)和 SDA(数据),都是开漏结构,靠上拉电阻拉到高电平。这意味着总线上任何一方拉低,对应线路就是低电平;谁都不拉时,线路自动回到高。这个“线与”特性是整个协议的基础,也是很多故障的根源。
通信过程简单说就是:主机先发一个起始条件(SCL 为高时 SDA 从高跳低),然后发送 7 位从机地址加 1 位读写标志,接着等从机拉低 SDA 表示 ACK。之后每传输一个字节,接收方都要回一个 ACK;如果是读操作,主机读完最后一个字节后回 NACK 再发停止条件,表示“我不要了,结束吧”。停止条件是 SCL 为高时 SDA 从低跳高。
这套流程里,最容易被忽视却又最容易出问题的就是 ACK。从机没有 ACK,要么地址不对,要么设备没上电,要么总线电平有问题。而你用示波器能看到的最有价值的信号,恰恰就是主机发出地址字节后 SDA 上的那一下“拉低”——那一小段低电平,代表总线上有一个活着的从机认领了这个地址。
1.2 万用表、示波器、逻辑分析仪各自该干哪份活
很多新手一上来就抓示波器,结果波形乱成一团,越看越懵。正确的做法是先按“工具分工”来:
- 万用表负责静态检查:测短路、测上拉电阻、测静态电平、确认电源和地是否正常。它看不到时序,但能最快暴露物理层问题。
- 示波器负责动态波形:看 SCL 频率、看 SDA 数据位、看 ACK 位置、看毛刺和沿质量。至少需要两通道,带宽 100 MHz 以上足够应付绝大多数 I2C 调试。
- 逻辑分析仪负责协议解码:能把波形直接翻译成地址、寄存器、数据,省去手动数位的痛苦。如果手头有,强烈建议配合示波器一起用;没有的话,示波器逐帧手动分析也能干,就是慢一些。
另外,力科、鼎阳、普源这类示波器现在基本都带 I2C 解码功能,有的还支持 SCPI 指令做自动化抓取。后面我会专门讲手动抓波形和用解码功能的互补关系。
2. 万用表能做的事:从短路、上拉到静态电平
2.1 万用表测 I2C 的正确姿势
I2C 调试第一步不是上示波器,而是拿起万用表,先把总线“健康度”查一遍。上电之前,最值得做的是断电测对地电阻和两线之间是否短路。
具体操作是:断电状态下,把万用表打到二极管档或电阻档。红表笔接地、黑表笔分别接 SDA 和 SCL,记录读数。正常的 I2C 总线,因为有上拉电阻,通常能测到几百欧到几千欧不等的阻值,具体取决于上拉电阻大小和线上挂的设备数量。如果读数是 0 或者非常接近 0,说明总线对地短路,十有八九是芯片引脚虚焊连锡、PCB 走线被铜屑搭接,或者是排针/排线座内部短路。
反过来,红表笔接 SDA、黑表笔接 SCL,如果读数极低,说明两根线之间短路,这在设计上是不允许的,会造成数据和时钟互相干扰,波形会非常乱。
上电之后的静态电平检查也很有用:万用表直流电压档,黑表笔接地,红表笔分别点 SDA 和 SCL。空闲状态下,两根线都应该是高电平,接近上拉电压(常见 3.3 V 或 5 V)。如果 SDA 是低、SCL 是高,说明总线处于一种“半通信”状态,可能是主机程序卡在某个字节中间,也可能是从机把 SDA 拉死在应答状态。如果两根线都低,优先查上拉电阻是否虚焊,或者是否有某个器件把总线拉死。
注意:万用表测高电平只能证明“静态状态没问题”,不能证明“通信没问题”。真正判断通信质量,还是要靠示波器抓动态波形。
2.2 测上拉电阻,判断总线是被谁拉低的
上拉电阻的取值直接影响沿速度和电平。I2C 标准模式(100 kHz)一般推荐 4.7 kΩ 到 10 kΩ;快速模式(400 kHz)常用 2.2 kΩ 到 4.7 kΩ;高速模式(1 MHz 以上)可能用到 1 kΩ 到 2.2 kΩ。取值太小,灌电流大、功耗高,但沿快;取值太大,沿太缓,加上总线电容后可能连电平都拉不到阈值。
万用表电阻档在断电状态下可以测到上拉电阻的实际值,但这有个坑:如果你直接在电路板上测,测得的是上拉电阻和芯片引脚内部电路的并联结果,读数会偏小。所以最好是把上拉电阻一端断开再测,或者对照原理图确认电阻是否焊错。
还有一个常见的判断方法是:把 CPU 和从机的驱动都释放(比如拔掉从机,或者让程序进入空闲态),然后量 SDA 和 SCL 的电压。如果这时候某一根线仍然低,说明线上有器件在持续拉低,要么是该器件坏了,要么是它的 SDA/SCL 引脚配置错了——我见过不少“从机没上电但引脚已经内部接地”的奇葩案例。
3. 示波器才是看协议的主场:从触发到 ACK 捕获
3.1 一次有效的抓波设置
示波器抓 I2C,设置是有讲究的。通道布局方面,我习惯 CH1 接 SCL、CH2 接 SDA,这样看波形时一眼就能对应协议。探头的地线夹要尽量短,直接夹在板子的地上,不要飞一根长线去远端点地,否则会引入噪声,波形边沿看着就不干净。
时基方面,100 kHz 的 I2C,一个位只有 10 μs,一个完整字节加 ACK 大约 90 μs。示波器时基一般设置在 20 μs/div 到 50 μs/div 比较合适,能看到完整一帧。如果是 400 kHz,时基可以压缩到 5 μs/div 到 10 μs/div。电压档位按上拉电压设置,3.3 V 系统用 1 V/div。
触发是关键。抓起始条件最常用的方式是:触发通道选 SDA,触发方式选下降沿,触发电平设在 1.5 V 左右(对 3.3 V 系统来说大约一半电平)。这样示波器会在主机发出起始条件(SDA 从高跳低)时开始采集,后面跟着的就是地址字节、ACK、数据……整个通信过程都会留在屏幕上。
如果你用的是带 I2C 解码的示波器,可以直接选 I2C 触发类型,设置从机地址,比如 0x50,示波器就只会在总线上出现这个地址时触发。这个功能在总线比较活跃、抓特定设备通信时非常有用,不用靠碰运气按单次触发。
经验分享:抓 I2C 时别忘了把示波器设为单次触发模式(Single),然后去启动主机侧的通信。否则连续触发模式下,屏幕刷新太快,一帧波形还没看清就被下一帧覆盖了。很多初学同好反馈“抓不到波形”,八成是没用 Single。
3.2 怎么读懂一帧 I2C 报文
抓到的波形不会自己解释自己,你得会拆。一帧典型的写操作长这样:起始条件,然后是 9 个时钟周期——前 7 个是地址位,第 8 个是读写位,第 9 个是 ACK 位。如果从机在地址匹配时拉低了 SDA,第 9 个时钟周期里 SDA 就是低电平。之后主机继续发寄存器地址或数据,每发 8 位收一次 ACK,最后是停止条件。
读操作稍有不同:主机发完从机地址和读标志后,从机 ACK;接下来从机驱动 SDA 发送数据,主机在每个字节后回 ACK;主机读完想要的数据后,在最后一个字节回 NACK,表示“别再发了”,然后发停止条件。这里有个很多新手容易看反的地方:数据方向切换后,SDA 的驱动权从主机换到从机,示波器上波形看起来还是连续的,但你要知道每个字节的 8 个数据位到底是谁发的。
以读 EEPROM(AT24C 系列)为例,完整流程是:主机写设备地址 0xA0,从机 ACK;主机写内部存储地址,从机 ACK;主机再发读地址 0xA1,从机 ACK;然后从机连续输出数据字节,主机回 ACK;直到主机回 NACK 并发送停止条件。你把示波器从头到尾存下来,按这个流程逐段对照,基本能判断问题在哪一段。
动手数位的时候有个技巧:先在屏幕上找到起始条件(SDA 高跳低且 SCL 为高),然后从 SCL 的第一个上升沿开始数。SCL 高电平时 SDA 保持不变的那些周期,就是有效数据位;SCL 高电平时 SDA 发生跳变,那基本可以断定是时序错误或毛刺干扰,应当警惕。
3.3 手动 ACK 和自由数据模式:示波器解码功能的妙用
现在不少示波器支持 I2C 协议解码,比如力科的 SCPI 命令、鼎阳的串行解码、普源的协议分析功能,用法都差不多。里面有两个容易被忽略但非常好用的选项:手动 ACK 和自由数据模式。
手动 ACK 适用于主机在读操作后没回 ACK 或回 NACK 的场景。示波器默认的解码逻辑可能会把 NACK 当作错误标红,但只要从机实际有响应,这个“错误”其实是正常的主机行为。你可以在解码设置里打开手动 ACK 选项,手动指定哪一段是 ACK、哪一段是 NACK,解码结果会更准确,不至于被误报警吓得去改从机固件。
自由数据模式则是在你不知道设备寄存器布局、只想抓裸数据时用的。它不按地址/寄存器/数据的结构去解析,而是把所有字节原样列出来。调试前期摸不清从机行为的时候特别管用,先把原始字节流抓出来,再对着 datasheet 找含义,比一开始就套协议解析框架更灵活。
另外,如果你的示波器支持 SCPI 远程控制,还可以把这类 I2C 触发的采集过程写成脚本,批量跑不同地址扫描。这个进阶玩法在量产测试和产线排查时很香,但平时调试手动操作就够了。
4. 完整排查流程:从静态检查到动态抓包
4.1 第一步:上电前后都该量什么
我处理 I2C 故障的固定套路,你可以直接照着抄。
上电前:
- 用万用表二极管档测 SDA 对地、SCL 对地的压降读数,确认没有对地短路。
- 测 SDA 与 SCL 之间是否短路。
- 对照原理图确认上拉电阻已经焊接,且阻值在合理范围。
- 确认所有 I2C 设备的电源引脚都接到了正确的电压域,别让 3.3 V 的器件挂在 5 V 上拉上。
上电后:
- 量 SDA、SCL 静态电压,确认都在高电平。
- 量从机电源电压、地是否正常。
- 如果某条线被拉低,用分段排除法:先断开所有从机,只留主机和上拉,看总线是否恢复;再逐级接入从机,找到拉低总线的元凶。
很多时候,问题在上电前就能暴露。我在一块板子上遇到“SDA 始终为低”的故障,排查到最后是排阻的公共端没接上拉电压,导致上拉电阻根本没工作,总线一直浮空被某个输入端拉低。这类问题用示波器是看不出所以然的,只有万用表能快速定位。
4.2 第二步:读操作抓波形,按帧拆解
假设上电静态检查都过了,但 I2C 通信还是失败,这时就要上示波器抓读操作。
这里我建议分两个场景:
场景一:主机主动发起读操作(CPU 读传感器)。 示波器 CH1 接 SCL、CH2 接 SDA,触发设 SDA 下降沿,Single 模式,然后在代码里触发一次读操作,比如读 BMP280 的温度寄存器。抓到的波形应该是一串连续帧:写地址、写寄存器、重启起始条件、读地址、读数据。把这串波形按帧拆开,逐一核对地址位、ACK 位、数据内容。
场景二:主机在启动后进行自动检测,周期扫描从机地址。 这种场景适合用示波器的 I2C 触发按地址过滤,或者用逻辑分析仪全量抓取。把地址扫描过程完整录下来,看是否存在“主机发地址后从机没有 ACK”的帧。很多传感器和 EEPROM 在地址识别失败时,SDA 在第 9 个时钟周期保持高电平,这在示波器上特别明显,一眼就能看到 NACK。
拆帧时留意一个细节:起始条件和重复起始条件长得不一样。起始条件是从空闲高电平开始的 SDA 下降沿;重复起始条件是在通信过程中、SCL 为高时 SDA 降一次。如果程序写的读操作里缺少重复起始条件,波形会完全错乱,解码器也解不出来。
4.3 第三步:锁定 ACK 异常和从机无响应
ACK 相关的故障,可以归纳成三类:
第一类:主机发地址后完全没有 ACK。 这时优先怀疑设备地址错了。I2C 地址是 7 位,但 datasheet 里常写成 8 位形式,比如“0xA0”。这其实是 7 位地址 0x50 左移一位的结果。如果你在代码里用了 0x50,读操作发出去的地址就是 0xA0(写标志附加)。把 0x50 和 0xA0 混用,在带解码的示波器上一眼就能看出地址对不上。
第二类:地址正确,但从机响应部分正确、部分无响应。 这种一般是寄存器地址超出了从机范围,或者从机正处于忙状态。比如 EEPROM 在写操作期间如果被连续写入,内部在完成编程前不会响应新的写命令,示波器上会看到 ACK 缺失。解决办法是加写周期延时(典型 5 ms,不同芯片有差异)。
第三类:主机读数据时,从机发了数据但主机没回 ACK。 很多传感器驱动会要求主机在最后一个字节后回 NACK,但如果驱动写错了,在中间字节就提前回 NACK,从机会立刻停止发送,后续数据全读不到。示波器上能直接看到主机在第 9 个时钟拉高 SDA,而不是拉低,这就是问题所在。
提示:调 ACK 问题最忌讳“猜”。把波形抓下来,把 ACK 位放大到单个时钟周期,确认 SDA 在第 9 个 SCL 高电平期间到底是高还是低,比改十次软件参数都管用。
5. 企业调试中容易载跟头的几个细节
5.1 时序参数和总线电容,不能只靠“能通就行”
很多人调 I2C 的标准是“能读到数据就算成功”,但这在量产和复杂系统里不够。I2C 规范对上升沿时间、下降沿时间、建立时间、保持时间都有要求。快速模式要求上升时间不超过 300 ns,如果上拉电阻太大、总线电容又高,沿会变得很缓,协作方或较老的主机可能就识别不了。
我遇到过一块 PCB,SDA 和 SCL 走线绕了半块板子,加上好几个传感器的寄生电容,400 kHz 下上升沿已经变成了斜坡。示波器上看着电平还能到高,但用 3.3 V 阈值来量,实际有效高电平时间已经短到不满足建立时间。后来把上拉电阻从 4.7 kΩ 换成 2.2 kΩ,问题直接消失。排线、FPC 软板、长飞线都会显著增加总线电容,上拉选择需要留有余量。
测量总线电容的方法也很简单:用 LCR 表或示波器配合已知电阻观察沿变化估算。不过日常调试中,更实用的是看示波器上的上升沿斜率,如果明显不是接近垂直的快速翻转,就要警觉。
5.2 电平域不一致是隐藏杀手
I2C 总线上所有器件必须共用一个电压域,或者通过电平转换芯片隔离。常见的故障是主控是 1.8 V I/O,传感器是 3.3 V,有人省事直接连在一起,结果空闲时传感器把 SDA 拉到 3.3 V,主控 I/O 以为逻辑高但可能已经超压,或者反过来主控根本识别不了传感器的高电平。
示波器上这种情况表现为:总线高电平被拉高或拉低到某个中间值,通信时好时坏。用万用表量静态电压就会非常明显,SDA 或 SCL 的空闲电压不是标准电平,而是取决于谁的内部保护二极管在钳位。
处理方案是加电平转换电路,或者在相连器件之间加合适的电平转换模块。别指望“试一试会不会稳定工作”,电平域问题在量产线上一定会爆发。
5.3 总线死锁和时钟同步问题
I2C 是主从共享时钟的,多主机场景下需要时钟同步和仲裁。如果某个从机异常拉低 SCL 不放,整个总线就锁死了。此时万用表能测到 SCL 一直为低。处理方法通常是给从机断电复位,或者总线锁死后用主机发 9 个多余的时钟脉冲来帮助从机释放 SDA——这是很多控制器驱动里内置的“总线恢复”功能。
如果是我们自己研发的从机固件,注意在状态机里加超时保护,别让状态机停在某个等待 SCL 上升沿的死循环里。硬件上也可以加看门狗或总线超时检测,避免单点故障挂死整个系统。
5.4 示波器测量中的探头效应
测量 I2C 波形时,探头本身会引入电容负载,一般 10× 探头约 10~15 pF,1× 探头可能到 100 pF 以上。对于带长总线的 I2C 系统,接上探头后波形可能变差、沿变慢,甚至通信直接失败。这时候要改用 10× 探头,并尽量用短接地弹簧而不是长地线夹。如果你发现“示波器接上去通信就断、拿掉探头就好了”,基本就是这个原因。
测纹波也是这个道理。示波器标配的接地夹线太长,会形成一个环路天线,测到的高频噪声不少其实是探头本身捡来的,不是板子上的真实纹波。把地线夹换成弹簧地针,测出来的数据才可信。
6. 问题速查表与一页纸排查模板
| 现象 | 可能原因 | 优先检查手段 | 常见处理 |
|---|---|---|---|
| 上电后 SDA 为低、SCL 正常 | 总线被某从机拉死,或上拉失效 | 万用表量静态电平,分段断开 | 查上拉焊接;逐段排查拉低源 |
| SDA/SCL 都低 | 上拉电阻未焊、断路,或电源未供 | 断电测电阻、电压 | 补焊排阻,确认供电 |
| 空闲电平低于电源电压 | 电压域不匹配或总线过载 | 万用表测电平,对比各路电源 | 加电平转换,检查漏电流 |
| 无起始条件波形 | 主机没真正调用 I2C 驱动,或引脚复用未配置 | 示波器触发 SDA 下降沿,查引脚复用电 | 改引脚配置,确认驱动调用 |
| 有波形但全是 NACK | 设备地址错误、从机没上电、从机复位中 | 用 I2C 地址扫描,核对 datasheet | 修正地址,检查从机电源 |
| 部分寄存器能读,部分不能 | 寄存器地址越界,或从机忙 | 对照寄存器表,抓读帧波形 | 修正寄存器地址,加等待 |
| 主机读最后一个字节后不停拉低 SDA | 主机在非最后字节时多回了 ACK | 查看驱动里读字节数逻辑 | 让最后一个字节回 NACK |
| 波形沿明显变缓 | 上拉太大、总线电容太大、探头负载 | 看上升时间,估算总线电容 | 减小上拉,缩短走线 |
| 接示波器后通信中断 | 探头电容负载过大 | 改用 10× 探头短地针 | 减少负载;必要时加缓冲 |
再送你一个排查模板,现场调试时按顺序打勾,基本不会走弯路:
- 确认电源和地:每个涉及器件都单独量电压。
- 确认物理连接:SDA/SCL 无短路、无开路、无错位。
- 确认上拉电阻:阻值正确、焊接可靠、电压正确。
- 确认静态电平:空闲时 SDA/SCL 都是高电平。
- 确认波形存在:单次触发能否抓到起始条件。
- 确认地址和 ACK:抓地址帧,看第 9 个时钟 SDA 是否拉低。
- 确认数据内容:对照 datasheet,逐字节验证读回数据。
- 确认时序规格:上升沿、建立时间、保持时间是否满足规范。
7. 最后分享一点个人判断经验
经过多年踩坑,关于 I2C 测量这件事,我最大的体会是:不要迷信单一工具,也不要盲目升级设备。用万用表查静态,用示波器看时序,用逻辑分析仪做协议解析,三样工具配合着来,绝大多数问题都能在半小时内定位。反倒是那些上来就调参数、改代码的做法,往往绕了远路。
还有一个很实用的习惯:在示波器上抓到一帧正常波形后,一定要保存下来,最好连设置一起存。以后碰到类似问题,先拿异常波形和正常波形对比,哪里多了毛刺、哪里 ACK 缺失,一目了然。我调试时习惯把“正常模板”存在示波器里,换板子、换物料后重新验证时,对比效率非常高。
如果你正准备上示波器抓 I2C,不妨先把这篇文章里“单次触发 + SDA 下降沿 + 电压档 1 V/div + 时基 20 μs/div”这套设置存成示波器预设,下次直接调用。等你习惯了怎么看 ACK,再往下研究总线仲裁、时钟拉伸这些进阶内容,就会顺很多。