I2C 总线被拉死:传感器读数全部超时

采集终端偶发传感器读数全部超时,根因是从机受电源扰动后把 SDA 拉低锁死总线,主控又缺少恢复机制。加入 9 个时钟脉冲的总线恢复与传感器复位后稳定。

问题背景

公司给一家工厂部署了 200 多台环境采集终端,主控是 STM32F103,通过 I2C 总线挂了三类传感器:温湿度、可燃气体和粉尘。终端每 10 秒采集一轮,数据通过 4G 上报到平台。设备装在车间和仓库,附近有大功率接触器、变频器和行车,电磁环境不算友好。

设备运行一年多,大部分稳定。近两个月,现场陆续反馈 6 台终端“某一个传感器数值不动了”:平台上其余指标正常刷新,唯独温湿度或气体浓度停在某个值上,设备本身仍在线。现场同事断电重启后恢复,过几周又可能出现。

故障现象

  • 平台上单台终端的部分传感器数值长时间不变,时间戳停止更新;其他传感器和 4G 上报正常。
  • 设备没有死机,看门狗没有动作,串口调试口还能打印日志。
  • 日志里反复出现 HAL_I2C_Master_Transmit 返回 HAL_TIMEOUT 或 HAL_BUSY,之后所有 I2C 操作都失败。
  • 用万用表量总线:SCL 保持高电平,SDA 被拉到 0.2V 左右,明显被某个从机按住。
  • 断电重启后总线恢复,设备能正常工作几周,直到下一次偶发。
  • 故障设备集中在接触器动作频繁的车间,仓库里的设备基本没出现。

排查过程

第一步确认故障边界。平台上数据停更,但设备在线、其他传感器正常,说明不是 4G、电源或主控整体死机,问题缩到 I2C 总线这一段。串口日志显示第一笔失败发生在一次传感器读取,之后所有设备地址都访问不了。

第二步量总线电平。正常时 SDA、SCL 都被上拉电阻拉到 3.3V;故障时 SCL 仍是高,SDA 却停在 0.2V。I2C 是开漏总线,任何设备都可以把线拉低,SDA 长期为低意味着某个从机把总线按住了,主控根本无法发出 START 条件。

第三步查固件。驱动用的是 HAL 阻塞式接口,超时参数给的是 1000ms,失败后只打印日志、下一轮继续重试,没有总线恢复动作。更关键的是,HAL_I2C_Master_Transmit 失败后 HAL 内部状态停在 BUSY,后续调用直接返回 HAL_BUSY,永远不会自己恢复。也就是说,只要从机锁死一次,这条总线就废了,直到断电。

第四步找锁死原因。把故障终端拆回实验室,用示波器抓总线,同时用可调电源给传感器做掉电复现:在传感器供电瞬间跌落到 2V 以下再恢复,约十分之一概率出现 SDA 被拉低不放。对照现场环境,接触器吸合瞬间的浪涌和地弹正好能造成这种电源扰动。传感器在传输过程中被打断,内部 I2C 状态机停在“正在输出低电平”的状态,主控再怎么发时钟都不松手。

第五步验证恢复方案。I2C 规范里对这种场景有标准做法:主控把 SCL 切成普通 GPIO,手动输出最多 9 个时钟脉冲,让从机把没发完的字节发完、释放 SDA;然后补一个 STOP 条件,再重新初始化 I2C 外设。实验室里对锁死状态执行这套动作,9 个脉冲内 SDA 全部释放,总线恢复;不执行恢复、只反复重试,则永远失败。

第六步确认硬件侧。检查传感器供电,发现它和主控共用一路 3.3V,传感器端只有一颗 0.1μF 去耦,没有独立稳压和 TVS。接触器动作时的浪涌通过电源和地耦合进来,传感器比主控更敏感,先挂的是它。

解决方案

  1. 加总线恢复函数:检测到 I2C 超时或 BUSY 后,把 SCL 配成开漏 GPIO,输出 9 个时钟脉冲并逐次检查 SDA 是否释放;释放后补 STOP,再 HAL_I2C_DeInit / HAL_I2C_Init 重新初始化外设。恢复失败则记录错误并触发传感器复位。
  2. 缩短超时并区分错误:每笔 I2C 事务超时从 1000ms 改为 10ms,超时立即走恢复流程,不再用长超时阻塞采集任务;对 HAL_TIMEOUT、HAL_BUSY、HAL_ERROR 分别计数上报。
  3. 给传感器加复位能力:硬件上为每路传感器增加 GPIO 控制的负载开关或使能脚,软件在总线恢复失败时给传感器断电 50ms 再上电,让它彻底退出异常状态。
  4. 电源与防护:传感器供电增加独立 LDO 或 RC 滤波,入口加 TVS;I2C 上拉保持 4.7k,长线场景评估总线电容。硬件改动随下一批设备实施,存量设备先靠软件恢复兜底。
  5. 故障注入测试:出厂测试增加“拉低 SDA”“传感器掉电 100ms”两项,验证固件能在 1 秒内恢复总线并继续采集,而不是等现场断电。
  6. 验证:实验室连续注入 500 次故障,恢复成功率 100%,单次恢复耗时小于 200ms;现场跟踪一个月,6 台问题设备未再出现数据停更。

根因分析

直接根因是 传感器在电源扰动下 I2C 状态机异常,把 SDA 持续拉低锁死总线;主控固件没有总线恢复机制,HAL 状态停在 BUSY 后所有后续访问失败,只能靠断电重启恢复。

更深一层,是把 I2C 当成了“不会坏的板内总线”。I2C 的电气特性决定了任何从机都能拉低总线,从机异常、掉电、复位不完整都会把整条总线拖死;而很多驱动示例只演示“读到了数据”,不演示“读不到时怎么把总线救回来”。产品在实验室里没有接触器、没有浪涌,自然测不出这个问题。

还有一层是硬件设计的余量不足。传感器和主控共用电源、去耦不足,等于把现场的电源扰动直接传给最脆弱的从机;软件又没有恢复手段,硬件和软件两道防线同时缺位,故障就只能以“断电重启”收场。

预防措施

  • I2C 驱动模板强制包含三件事:事务超时、总线恢复(9 时钟 + STOP + 重新初始化)、从机复位;新项目直接复用,不再每次手写。
  • 出厂测试加入总线故障注入:拉低 SDA、从机掉电、总线短路,验证恢复时间与成功率,作为量产门槛。
  • 传感器供电与主控分开滤波,关键设备加 TVS 和独立 LDO;长线、多从机场景核算总线电容与上拉。
  • 设备遥测上报 I2C 错误计数、恢复次数、传感器复位次数,把“总线健康度”变成可观测指标。
  • 现场选型时把电磁环境纳入评估,接触器、变频器附近的终端优先选用带隔离或更强防护的接口方案。

总结

I2C 是一条没有仲裁者的共享总线:只要有一个从机按住 SDA,整条总线就停摆,而主控通常只会“再试一次”。这次故障里,传感器没有坏,主控也没有死,坏的是两者之间那条没人负责恢复的线。把总线恢复写进驱动、把故障注入写进测试、把传感器复位做成硬件能力,才能让设备在电磁环境里自己站起来,而不是等人去拔电。

延伸阅读

使用 Hugo 构建
主题 Stack 由 Jimmy 设计