ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

I2C调试实战:用万用表与示波器精准定位ACK/NACK故障

I2C调试实战:用万用表与示波器精准定位ACK/NACK故障 做嵌入式这几年I2C 是让我又爱又恨的总线之一。爱的是它简单——只有 SCL 和 SDA 两根线随便一个 MCU 的 GPIO 模拟都能跑起来恨的是它一旦出问题往往不是程序逻辑一眼能看出来的而是藏在波形细节里。你以为是代码问题结果示波器一抓发现从机根本没给出 ACK你以为从机坏了换一颗芯片照样 NACK最后查到主板的上拉电阻焊错了位置。这类问题万用表能帮你排除一半示波器能帮你定位另一半而 ACK 是整条排查线上最关键的信号。这篇内容我会按实际调试的顺序来写先讲清楚 I2C 的信号本质再讲万用表能测什么、不能测什么然后重点讲示波器怎么接线、怎么触发、怎么看 ACK最后整理一条从现象到结论的完整排查流程配上几个我在项目里真实踩过的坑。不管你是刚接触嵌入式的新手还是被某个传感器 I2C 搞得头大的老工程师这套流程应该都能直接用。1. 先弄懂 I2C 的信号本质两根线上的“物理语言”排查任何总线问题第一步永远不是拿表笔乱点而是搞清楚你测的到底是什么信号。I2C 之所以难测不是因为协议复杂恰恰是因为它太简单简单到很多关键信息都藏在“电平”和“时序”的细节里稍不注意就会误判。1.1 两线制背后的开漏结构I2C 只有两条线SCL时钟和 SDA数据。所有设备的主机、从机都通过这两根线通信。但要注意这两根线的物理结构是开漏Open-Drain输出不是推挽输出。也就是说设备只能主动把线拉低到 GND不能主动把线拉高。线要恢复到高电平靠的是外部上拉电阻把电平拉回 VDD。这个结构带来的第一个实测特征就是空闲状态时SCL 和 SDA 都应该接近电源电压。如果总线上有设备把线拉住了或者某个芯片损坏导致内部短路你测到的那根线就会一直处于低电平。我遇到过不少设备“死锁”的情况SDA 被一个没正常复位的加速度计死死拉在低电平主机发什么它都不回应整条总线直接瘫痪。这个现象用万用表就能测出来属于最基础的一类故障。开漏结构还有第二个特征总线支持“线与”逻辑多个设备可以并联在一条总线上。这也是 I2C 能挂十几个传感器的根本原因。但并联意味着总线电容变大上拉电阻的选取就要权衡。这个后面会详细讲。1.2 时序基础起始、停止、数据位I2C 的时序并不复杂通信开始前总线处于空闲态。主机要发起通信时先把 SDA 拉低同时 SCL 保持高电平这个“SCL 高电平期间 SDA 由高变低”的跳变叫起始条件。通信结束时SDA 在 SCL 高电平期间由低变高这叫停止条件。数据位的规则更简单SCL 高电平期间 SDA 必须保持稳定只有 SCL 为低电平时 SDA 才允许翻转。理解这几个规则直接决定了你怎么设置示波器触发。抓起始条件时触发源选 SDA触发沿选下降沿一抓一个准。如果你用 SCL 做触发源抓到的大概率是时钟波形看不到完整的数据帧调试效率会低很多。还有一个关键点I2C 的速率分成几档标准模式 100kbps快速模式 400kbps快速模式 Plus 1Mbps高速模式 3.4Mbps。绝大多数传感器和 MCU 用的都是 100k 或 400k。这个速率决定了示波器带宽需求——400kbps 的 I2C 其实要求很低一台 100MHz 带宽的示波器完全够用关键在于你触发放没放对而不是仪器不够高级。1.3 第 9 个时钟ACK/NACK 的物理位置这是整篇内容最核心的部分。I2C 协议里主机每发送完 8 个数据位包括地址字节从机需要在第 9 个时钟周期回应一个 ACK。具体物理表现是第 9 个时钟的 SCL 高电平期间主机释放 SDA从机如果正常接收到数据会把 SDA 拉低如果从机没回应SDA 会保持高电平这就是 NACK。我见过很多新手在调试时不理解“主机释放 SDA”这一步导致示波器抓出来的波形里第 9 个时钟的 SDA 一直是高就以为是 NACK其实可能是主机自己没释放输出。这个细节在软件模拟 I2C 时特别容易踩坑后面讲 ACK 排查时我会专门展开。2. 万用表排查能测出六成问题但别指望它看时序很多工程师一上来就拿示波器抓波形其实浪费了不少时间。I2C 出问题有很大一部分是供电、短路、虚焊、上拉电阻这类“静态故障”用万用表几分钟就能定位根本不需要上示波器。2.1 接线前先测三样东西把示波器探头放一边第一步用万用表的通断档测以下三个项目第一检查主板供电是不是正常。I2C 设备的上拉电阻通常接到 VDD如果 VDD 没起来SDA 和 SCL 永远不会有高电平。这时候示波器抓不到波形万用表一测电压发现问题方向就对了。第二检查 SDA、SCL 对地有没有短路。贴着 QFN 封装的传感器焊接时引脚容易桥连SDA 和旁边的 GND 短在一起的情况我遇到过不止一次。用万用表通断档测 SDA 对地电阻如果几乎为 0基本可以断定是焊接问题或者芯片损坏。第三检查 SCL 和 SDA 之间有没有短路。这个故障比较隐蔽有时候是 PCB 打样工艺问题有时候是飞线时不小心碰到一起。SCL 和 SDA 一旦短了主机发送的数据和时钟相互干扰波形会乱成一团怎么看都是“总线上有信号但数据不对”实际根源就是这么简单。2.2 静态电平判断总线是否“活着”给系统上电后用万用表直流电压档测 SDA 和 SCL 的静态电压。正常状态下总线空闲时两根线都应接近 VDD。常见的异常情况有这么几种如果 SCL 有电压、SDA 是 0V说明 SDA 被某个设备钳住了。这时候你需要把总线上的从机一个一个断开每断一个测一次就能找到“肇事者”。有些芯片在软件没初始化或者复位不彻底时会把 SDA 误配置为输出低电平这在多设备共用一条总线的板子上特别常见。如果两根线都是 0V优先怀疑上拉电阻没焊好或者焊错了阻值。我曾经遇到一批板子SDA 和 SCL 都被拉到了 0V查了半天发现是贴片电阻封装 0402 和 0603 混贴上拉电阻有一端虚焊总线根本没有高电平来源。如果两根线的电压只有 1V 左右不上不下这通常是上拉电阻太大加上从机太多漏电流把电平拖垮了。这种问题在静态时还能看动态时会更严重往往表现为通信不稳定、时好时坏。2.3 万用表能测出通信活动吗严格来说万用表看不到 I2C 的时序因为数据一直在翻转直流档测出来的只是一个平均值。但我有一个实测可行的小技巧把万用表打在直流电压档去测 SDA 或者 SCL如果总线上有通信活动表上显示的数值会明显低于 VDD而且数字会有轻微跳动。比如 VDD 是 3.3V空闲时测 SDA 是 3.3V主机持续读传感器时测出来可能只有 1.6V 左右这是因为 SDA 有一半时间被拉低平均值被拉下来了。这个方法的用途不是看时序而是快速判断“总线上到底有没有信号在跑”。如果程序跑起来后测 SCL 完全没有电压变化说明主控可能压根没初始化 I2C 外设或者引脚配置错了。用这个方法能帮你把“软件没跑起来”和“硬件有问题”快速区分开为后续用示波器争取时间。3. 示波器测量实操从接线、触发到解码一整套示波器抓 I2C 波形不复杂但有几个关键设置没做好抓出来的波形要么没有要么乱得没法看。这一节我按实操顺序讲你照着做基本不会翻车。3.1 探头连接和通道设置I2C 测量用两通道就够了。探头 1 接 SCL探头 2 接 SDA两个探头的接地夹子都接到同一个 GND 测试点。注意地线夹子的位置要离测量点尽量近如果夹在主板的远端探头地线形成的回路会引入噪声高速信号尤其严重。I2C 本身是慢速信号影响没那么大但养成良好的测量习惯总没错。通道设置方面把通道衰减调到 1x 或者 10x对应探头上的开关保持一致。如果 VDD 是 3.3V垂直档位设在 1V/div 左右比较合适能看到完整的 0V 到 3.3V 电平范围。触发模式我建议用“正常模式”Normal不要用自动模式。自动模式在信号存在时会一直刷新看起来像波形在动但不利于捕捉瞬态异常。Normal 模式配合下降沿触发能在每次起始条件出现时稳定锁定波形。3.2 触发设置为什么选 SDA 下降沿抓 I2C 第一个波形我推荐这样设置触发源选 CH2SDA触发类型选边沿触发沿选下降沿触发电平设在 1.65V 附近VDD 的一半。按这个设置按下“Single”单次触发键然后让程序去跑一次 I2C 读写示波器就能捕捉到从起始条件开始的一整段波形。为什么要选 SDA 下降沿因为 I2C 的起始条件本身就是 SDA 在 SCL 高电平期间的一个下降沿。用 SDA 下降沿触发等于告诉示波器“看到起始条件就刷新”抓到的画面正好是完整通信过程的开头。如果你用 SCL 做触发源由于 SCL 会有几十上百个边沿触发的时刻可能在数据帧中间分析起来非常别扭。如果是第一次抓波形建议在代码里做一个循环反复读写同一个传感器寄存器这样示波器能持续捕捉到同样的波形方便你对比。抓了几帧之后再把代码改成只执行一次读写用单次触发抓取完整的单帧时序。3.3 时基怎么设先估算一帧的时间很多新手会把时基设得太小比如 1us/div结果屏幕上只能看到一两个时钟脉冲完全看不出帧结构。I2C 一帧通信的时间是可以估算的。以 400kbps 快速模式为例每 bit 持续 2.5us一次普通的寄存器写操作包含起始条件、地址字节8bit、寄存器地址字节8bit、数据字节8bit、ACK 位和停止条件总共不到 30 个 bit时间大约 75us。一次读操作因为多了重复起始和从机发送数据段可能到 150us 左右。所以时基设在 50us/div 到 100us/div 之间比较合适屏幕上能看到完整的一帧或者两帧。如果你的总线是 100kbps 标准模式时间要翻 4 倍时基设在 200us/div 左右。抓波形之前先想清楚要看的是一帧还是多个 bit先抓全貌再放大看细节效率最高。3.4 看懂波形上的 ACK 位等你能稳定抓到波形了接下来重点是学会读 ACK。把波形水平放大找到第 9 个时钟的上升沿。正常情况下你应该看到第 8 个数据位结束后SDA 被主机释放变高然后从机在第 9 个时钟的低电平期间把 SDA 拉低在第 9 个时钟的高电平期间保持低电平。这样在第 9 个时钟的上升沿处SDA 是低电平代表 ACK。如果第 9 个时钟的上升沿处 SDA 是高电平那就是 NACK。看到 NACK 不用慌先区分一下是哪个阶段的 NACK。地址阶段的 NACK 说明总线上没有地址匹配的设备数据阶段的 NACK 可能是从机不支持当前操作也可能是从机内部忙没来得及处理。定位到具体阶段排查方向就清晰了。我习惯把 ACK 位置的波形截图保存下来和正常波形做对比。有一个 400kbps 的温湿度传感器项目通信时好时坏抓了几十帧波形才发现偶尔有一帧的地址阶段之后 ACK 变成了 NACK最后查到是从机上电初始化时间太长主机在上电后 10ms 就急着去读从机根本没准备好。把主机初始化延时改成 100ms 就好了。这类问题不抓波形靠看代码基本看不出来。3.5 示波器解码有就用没有也不影响如果你的示波器带 I2C 协议解码功能那会很方便。开启方法一般在“Decode”或“总线”菜单里选择 I2C指定 SCL 和 SDA 对应的通道设置好阈值电平通常设为 VDD 的一半示波器就会自动把地址、数据、ACK/NACK 解析出来显示在波形上方。但我要说的是解码功能不是必须的。I2C 帧结构就那么几种地址、数据、ACK 用肉眼看波形完全能分辨。我见过有工程师依赖解码功能解码失败就不知道怎么排查这反而限制了调试能力。解码功能适合快速确认“通信内容和预期是否一致”排查物理层问题时还是要回到原始波形的边沿和电平上去。4. ACK 机制拆解第 9 个时钟决定通信成败ACK 是 I2C 协议里最容易被忽略也最值得深挖的信号。很多通信失败的现场本质都是 ACK 阶段出了问题只是表现多种多样。4.1 为什么必须有 ACKI2C 没有独立的“数据线反馈机制”主机发送一个字节后它并不知道从机到底有没有正确收到。ACK 就是从机给主机的“我收到了可以继续”信号。这有点像你打电话跟对方说“我把方案发你邮箱了”对方回答“收到了”你才知道可以挂电话。如果对方没回应你就得重发或者排查。ACK 在物理上的实现就是第 9 个时钟周期内 SDA 被拉低。需要注意的是ACK 是从机拉低 SDA主机只负责释放 SDA 并发出时钟。这个主从分工一定要搞明白否则你会误以为波形有问题。4.2 主机在读操作最后必须发 NACK这是一个特别容易踩的坑。主机读从机数据时如果还想继续读主机需要在每个字节之后发送 ACK告诉从机“继续发”。但当主机想要结束读取时最后一个字节的 ACK 位上主机不应该拉低 SDA而是让 SDA 保持高电平也就是发送 NACK然后再产生停止条件。很多人在这里搞错主机在最后一个字节也发了 ACK从机就会继续发送数据导致停止条件的位置错乱后续通信全部对不上。这个“主机主动发 NACK 来终止读取”的机制和“从机 NACK 表示异常”是完全不同的含义调试时不要混为一谈。在软件模拟 I2C 时你需要手动控制这个第 9 个时钟的 SDA 状态在硬件 I2C 外设里通常有对应的寄存器位或者配置选项比如 STM32 HAL 库的读函数内部自动处理了这一点所以你只要确认外设配置正确一般不会再踩这个坑。4.3 排查 NACK 的通用思路遇到 NACK第一步看是哪个阶段的 NACK。如果是从机地址阶段 NACK按这个顺序排查设备地址是不是写错了。7 位地址和 8 位地址的区别很经典很多传感器数据手册写的是 8 位地址比如 0xD0实际放到 I2C 地址字节里要用 0x68左移一位。用示波器抓波形对比实际发送的地址字节和预期地址一眼就能看出来。从机电源是不是正常。从机没供电当然不会 ACK。从机的复位引脚是不是被拉住了。有些传感器需要复位引脚拉高或者拉低才能正常工作复位状态不释放I2C 从机根本不会响应。从机是不是处于 busy 状态。部分传感器在处理内部任务时会暂时不响应 I2C 请求过一会就恢复了。这种“间歇性 NACK”最讨厌示波器抓多次波形对比时间规律能帮你判断是不是 busy 导致的。如果是数据阶段 NACK优先考虑主机是不是发送了从机不支持的寄存器地址或者从机当前处于写保护状态。有个项目读电容触摸芯片PRINT 寄存器地址是 0x1FHAL 库函数里地址参数写成了 0x1E从机回了个 NACK抓波才定位到是地址差了 1。4.4 和 TCP ACK 别搞混网上搜“ACK”会搜出一堆 TCP 的 dup ACK 机制和 I2C 的 ACK 完全是两回事。TCP 的 ACK 是协议栈层面“确认收到数据段”的逻辑承载在报文头里有序号、有确认号是端到端的确认机制。I2C 的 ACK 是物理层第 9 个时钟的电平状态是设备到设备之间的字节级确认。两者本质不同千万不要用什么“重传策略”的思路去套 I2C 的 NACK。I2C 主机对 NACK 的处理策略一般就是停止传输由软件层决定是否重试没有 TCP 那套拥塞控制、超时重传的复杂逻辑。5. 完整排查流程从“设备读不到”到“定位到根因”的实操路线前面讲了工具和原理这一节我把它们串成一条可以直接照做的排查流程。建议你在遇到 I2C 问题时按顺序执行避免东一榔头西一棒子。5.1 六个排查步骤第一步确认供电和静态电平。万用表测 VDD、SDA、SCL 的静态电压排除供电缺失、短路、上拉问题。第二步确认主控配置。检查 I2C 外设的时钟、引脚复用功能GPIO AF、引脚模式是否配置正确。很多 MCU 的引脚默认状态是输入浮空不配置 AF 功能总线自然没波形。用万用表测 SCL 静态电压如果为 0除了上拉问题还要查引脚配置。第三步示波器抓 SCL。先把触发源设为 SCL看有没有时钟翻转。如果没有时钟说明代码没正确启动 I2C 传输或者是总线死于设备拉低时钟时钟延展 SCL stretching 的情况。有些从机处理慢时会把 SCL 拉低主机需要等待它释放波形上表现为 SCL 长时间停在低电平。遇到这种情况不能简单视为故障要从机规格书确认它是否支持时钟延展。第四步抓 SDA 和完整帧。重点观察起始条件、停止条件、数据位和 ACK 位。这里要看的核心问题就是帧结构完不完整ACK 有没有NACK 出现在哪一段第五步最小系统法。如果总线挂多个从机把所有从机都拆掉只留目标设备重新测试。如果只留一个设备时通信正常那就是地址冲突、总线 capacitance 过大或者某个从机异常拉低总线。地址冲突是隐藏很深的问题一总线上挂了两个相同地址的设备主机访问时两个设备都可能回应或者都沉默波形上容易看到奇怪的 SDA 活动。第六步对比验证。用示波器对比一个正常的 I2C 波形和一个异常的 I2C 波形逐段对照从起始条件到停止条件的电平变化。很多时候单独看异常波形看不出问题一对比立刻发现差异点比如第 5 个时钟的高电平时间特别短可能是时钟源不稳或者干扰导致。5.2 排查记录从现象到根因的几个经典案例我在项目里积累了几个高频故障的典型案例整理成表格故障现象示波器波形特征典型根因读传感器无回应地址阶段后第 9 个时钟 SDA 保持高地址写错、设备没上电通信时好时坏有时正常有时地址阶段 NACK从机上电初始化时间过长主机访问太早SCL 有波形SDA 一直低起始条件后 SDA 被拉死无法翻转某设备异常拉低 SDA总线被锁死数据传输错误但 ACK 正常ACK 正常但数据位电平和预期不一致从机地址错误导致访问到错误设备总线空闲时 SDA 电压只有 1V电压偏低波形上升沿变缓上拉电阻过大或负载过多快速模式失败标准模式正常上升沿过缓高电平不能按时到达阈值总线电容太大上拉电阻阻值太高这张表是我长期调试的经验浓缩覆盖了 I2C 排查中至少八成的场景。遇到对应现象时对照表格里的根因方向去查能省下很多时间。5.3 软件层配合排查串口打印和总线扫描除了硬件工具软件层面的辅助手段也非常有用。如果你用的是 Linux 单板比如树莓派或者 RK 系列板卡可以用i2cdetect -y 1扫描总线上有哪些设备它会返回所有有 ACK 回应设备的地址。注意这个命令只能检查“从机是否存在”测不出时序质量但它能快速判断地址是否写错了。MCU 开发时我习惯在 I2C 读写函数中加上状态返回检查。HAL 库里HAL_I2C_Mem_Read返回HAL_OK才认为成功返回HAL_ERROR或者HAL_BUSY时把错误码和当前的 I2C 状态寄存器打印到串口。这样不用插示波器也能初步判断是 ACK 错误、总线忙还是超时。但要注意软件层面的错误提示只能缩小范围最终定位还是要靠示波器看物理波形。5.4 特殊场景休眠唤醒后的 I2C 复位问题有做低功耗产品的朋友经常问MCU 休眠唤醒之后I2C 偶尔就不工作了复位代码也没用。这个问题其实和硬件电平有关。休眠前如果 I2C 传输刚好停在中间状态SCL 或 SDA 可能停在非空闲电平。唤醒后外设重新初始化但如果从机还停留在之前的传输状态总线恢复就会出现异常。ESP32 这类芯片尤其常见有些传感器在 I2C 总线空闲时也会因为内部状态机卡死而拉低 SDA。我的建议是休眠进入前先把 I2C 外设 deinit并把 SCL、SDA 两个 GPIO 配置成输入上拉或者模拟输入确保总线处于空闲状态。唤醒后再重新 init。如果问题依旧可以加一段“总线恢复代码”在初始化之前手动翻转 SCL 九次并发送停止条件让总线上所有从机的状态机复位。这个方法在很多 I2C 从机上都很管用比如 AS5600 磁编码器初始化失败时做一次软件复位总线往往就好了。6. 工具选型与补充技巧示波器不够用的时候怎么办最后聊一下工具选型。很多初学者上来就想买高端示波器其实调试 I2C 这种低速总线工具的关键不在于贵不贵而在于你用得顺不顺手。6.1 示波器、万用表、逻辑分析仪的分工三样工具各有侧重。万用表用来查静态电平、短路、上拉电阻这些物理层问题示波器用来抓波形、看时序、看 ACK 位的动态过程逻辑分析仪则更适合长时间抓取、协议解码和多通道分析。有人会问示波器和逻辑分析仪不是功能重复吗其实差别很大。示波器的优势是能看到真实的电压幅值、上升沿形状、噪声和毛刺适合定位硬件问题。逻辑分析仪的优势是通道数多、存储深度大适合抓长时间的协议交互。比如你要分析 I2C 设备初始化时主机发了哪些寄存器的值用示波器一帧一帧看很痛苦逻辑分析仪挂上去几秒钟解出几百条消息一目了然。市面上几十块钱的逻辑分析仪对 I2C 这种低速总线完全够用搭配开源的 sigrok/PulseView 软件就能解码 I2C、SPI、UART 等协议。如果你的项目长期要和各种传感器打交道我建议示波器和逻辑分析仪各备一台。示波器不必追求顶配100MHz 四通道足够逻辑分析仪买 8 通道以上、采样率 100MHz 左右的型号性价比很高。6.2 没有解码功能时怎么快速判断 ACK如果你的示波器没有 I2C 解码功能也不要紧。用光标功能把水平光标放在第 9 个时钟的上升沿位置另一条光标放在 SDA 波形上直接读电平值。如果显示接近 0V那就是 ACK如果接近 VDD那就是 NACK。这个方法比肉眼看波形可靠得多尤其是总线速度快、波形密集的时候光标测量能避免误判。另外可以配合示波器的测量功能直接测量 SDA 信号在某个时间窗口内的最小值和平均值。ACK 阶段 SDA 被拉低最小值接近 0NACK 阶段 SDA 保持高电平最小值接近 VDD。虽然不够严谨但用来快速区分 ACK/NACK 很有效。6.3 一个抓波形的小技巧用单次触发加循环发送最后分享一个我比较常用的组合拳。在代码里做一个循环让主机每隔 100ms 读一次从机。示波器设置成 SDA 下降沿触发Single 单次模式时基设在 100us/div然后把这段循环跑起来按一下示波器的 Single 键等待捕捉。抓完后用缩放功能逐段观察起始条件、地址、数据、ACK。如果怀疑偶发故障就把循环改成持续发送示波器切换到“滚动模式”Roll mode让波形从左往右平铺长时间观察有没有异常脉冲。这个方法对付间歇性问题是最好用的我靠它抓到过不少“心跳式”的随机 NACK。另外关于示波器其他附加功能比如力科LeCroy或者鼎阳SIGLENT的联网和 SCPI 指令控制对自动化测试有帮助但手动调试中用到的机会不多。建议新手不要折腾这些先把基础测量玩熟等批量验证协议栈时再谈自动化。6.4 调试时的几条习惯性建议根据我的经验有三件事值得养成习惯。第一抓波形时同时记录 VDD 电压值很多信号问题其实是电源噪声引起的不测电源等于少了一个维度第二I2C 波形截图里一定要把时基和触发电平标出来这样排查记录才完整第三每次改动代码或硬件后重新抓一次波形对比不要只看程序跑没跑成功波形才能反映物理层的真实状态。我自己的习惯是在调试记录里建一个“正常波形库”每个器件的 I2C 读、写、校验帧都存一份正常截图。下次同样器件出问题时先和正常波形对比很快能找到差异点。这个习惯帮我省了很多排查时间强烈推荐你也试试。
返回列表