<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>总线锁死 on Zero Day Notes</title>
        <link>https://blog.5772447.xyz/tags/%E6%80%BB%E7%BA%BF%E9%94%81%E6%AD%BB/</link>
        <description>Recent content in 总线锁死 on Zero Day Notes</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>zh</language>
        <lastBuildDate>Sun, 01 Nov 2026 09:00:00 +0800</lastBuildDate><atom:link href="https://blog.5772447.xyz/tags/%E6%80%BB%E7%BA%BF%E9%94%81%E6%AD%BB/index.xml" rel="self" type="application/rss+xml" /><item>
            <title>I2C 总线被拉死：传感器读数全部超时</title>
            <link>https://blog.5772447.xyz/posts/65c53b2c/</link>
            <pubDate>Sun, 01 Nov 2026 09:00:00 +0800</pubDate>
            <guid>https://blog.5772447.xyz/posts/65c53b2c/</guid>
            <description>&lt;h2 id=&#34;问题背景&#34;&gt;&lt;a href=&#34;#%e9%97%ae%e9%a2%98%e8%83%8c%e6%99%af&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;问题背景&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;公司给一家工厂部署了 200 多台环境采集终端，主控是 STM32F103，通过 I2C 总线挂了三类传感器：温湿度、可燃气体和粉尘。终端每 10 秒采集一轮，数据通过 4G 上报到平台。设备装在车间和仓库，附近有大功率接触器、变频器和行车，电磁环境不算友好。&lt;/p&gt;&#xA;&lt;p&gt;设备运行一年多，大部分稳定。近两个月，现场陆续反馈 6 台终端“某一个传感器数值不动了”：平台上其余指标正常刷新，唯独温湿度或气体浓度停在某个值上，设备本身仍在线。现场同事断电重启后恢复，过几周又可能出现。&lt;/p&gt;&#xA;&lt;h2 id=&#34;故障现象&#34;&gt;&lt;a href=&#34;#%e6%95%85%e9%9a%9c%e7%8e%b0%e8%b1%a1&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;故障现象&#xD;&#xA;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;平台上单台终端的部分传感器数值长时间不变，时间戳停止更新；其他传感器和 4G 上报正常。&lt;/li&gt;&#xA;&lt;li&gt;设备没有死机，看门狗没有动作，串口调试口还能打印日志。&lt;/li&gt;&#xA;&lt;li&gt;日志里反复出现 &lt;code&gt;HAL_I2C_Master_Transmit&lt;/code&gt; 返回 &lt;code&gt;HAL_TIMEOUT&lt;/code&gt; 或 &lt;code&gt;HAL_BUSY&lt;/code&gt;，之后所有 I2C 操作都失败。&lt;/li&gt;&#xA;&lt;li&gt;用万用表量总线：SCL 保持高电平，SDA 被拉到 0.2V 左右，明显被某个从机按住。&lt;/li&gt;&#xA;&lt;li&gt;断电重启后总线恢复，设备能正常工作几周，直到下一次偶发。&lt;/li&gt;&#xA;&lt;li&gt;故障设备集中在接触器动作频繁的车间，仓库里的设备基本没出现。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;排查过程&#34;&gt;&lt;a href=&#34;#%e6%8e%92%e6%9f%a5%e8%bf%87%e7%a8%8b&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;排查过程&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;第一步确认故障边界。平台上数据停更，但设备在线、其他传感器正常，说明不是 4G、电源或主控整体死机，问题缩到 I2C 总线这一段。串口日志显示第一笔失败发生在一次传感器读取，之后所有设备地址都访问不了。&lt;/p&gt;&#xA;&lt;p&gt;第二步量总线电平。正常时 SDA、SCL 都被上拉电阻拉到 3.3V；故障时 SCL 仍是高，SDA 却停在 0.2V。I2C 是开漏总线，任何设备都可以把线拉低，SDA 长期为低意味着某个从机把总线按住了，主控根本无法发出 START 条件。&lt;/p&gt;&#xA;&lt;p&gt;第三步查固件。驱动用的是 HAL 阻塞式接口，超时参数给的是 1000ms，失败后只打印日志、下一轮继续重试，没有总线恢复动作。更关键的是，&lt;code&gt;HAL_I2C_Master_Transmit&lt;/code&gt; 失败后 HAL 内部状态停在 BUSY，后续调用直接返回 &lt;code&gt;HAL_BUSY&lt;/code&gt;，永远不会自己恢复。也就是说，只要从机锁死一次，这条总线就废了，直到断电。&lt;/p&gt;&#xA;&lt;p&gt;第四步找锁死原因。把故障终端拆回实验室，用示波器抓总线，同时用可调电源给传感器做掉电复现：在传感器供电瞬间跌落到 2V 以下再恢复，约十分之一概率出现 SDA 被拉低不放。对照现场环境，接触器吸合瞬间的浪涌和地弹正好能造成这种电源扰动。传感器在传输过程中被打断，内部 I2C 状态机停在“正在输出低电平”的状态，主控再怎么发时钟都不松手。&lt;/p&gt;&#xA;&lt;p&gt;第五步验证恢复方案。I2C 规范里对这种场景有标准做法：主控把 SCL 切成普通 GPIO，手动输出最多 9 个时钟脉冲，让从机把没发完的字节发完、释放 SDA；然后补一个 STOP 条件，再重新初始化 I2C 外设。实验室里对锁死状态执行这套动作，9 个脉冲内 SDA 全部释放，总线恢复；不执行恢复、只反复重试，则永远失败。&lt;/p&gt;&#xA;&lt;p&gt;第六步确认硬件侧。检查传感器供电，发现它和主控共用一路 3.3V，传感器端只有一颗 0.1μF 去耦，没有独立稳压和 TVS。接触器动作时的浪涌通过电源和地耦合进来，传感器比主控更敏感，先挂的是它。&lt;/p&gt;&#xA;&lt;h2 id=&#34;解决方案&#34;&gt;&lt;a href=&#34;#%e8%a7%a3%e5%86%b3%e6%96%b9%e6%a1%88&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;解决方案&#xD;&#xA;&lt;/h2&gt;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;加总线恢复函数&lt;/strong&gt;：检测到 I2C 超时或 BUSY 后，把 SCL 配成开漏 GPIO，输出 9 个时钟脉冲并逐次检查 SDA 是否释放；释放后补 STOP，再 &lt;code&gt;HAL_I2C_DeInit&lt;/code&gt; / &lt;code&gt;HAL_I2C_Init&lt;/code&gt; 重新初始化外设。恢复失败则记录错误并触发传感器复位。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;缩短超时并区分错误&lt;/strong&gt;：每笔 I2C 事务超时从 1000ms 改为 10ms，超时立即走恢复流程，不再用长超时阻塞采集任务；对 &lt;code&gt;HAL_TIMEOUT&lt;/code&gt;、&lt;code&gt;HAL_BUSY&lt;/code&gt;、&lt;code&gt;HAL_ERROR&lt;/code&gt; 分别计数上报。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;给传感器加复位能力&lt;/strong&gt;：硬件上为每路传感器增加 GPIO 控制的负载开关或使能脚，软件在总线恢复失败时给传感器断电 50ms 再上电，让它彻底退出异常状态。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;电源与防护&lt;/strong&gt;：传感器供电增加独立 LDO 或 RC 滤波，入口加 TVS；I2C 上拉保持 4.7k，长线场景评估总线电容。硬件改动随下一批设备实施，存量设备先靠软件恢复兜底。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;故障注入测试&lt;/strong&gt;：出厂测试增加“拉低 SDA”“传感器掉电 100ms”两项，验证固件能在 1 秒内恢复总线并继续采集，而不是等现场断电。&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;验证&lt;/strong&gt;：实验室连续注入 500 次故障，恢复成功率 100%，单次恢复耗时小于 200ms；现场跟踪一个月，6 台问题设备未再出现数据停更。&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;根因分析&#34;&gt;&lt;a href=&#34;#%e6%a0%b9%e5%9b%a0%e5%88%86%e6%9e%90&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;根因分析&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;直接根因是 &lt;strong&gt;传感器在电源扰动下 I2C 状态机异常，把 SDA 持续拉低锁死总线；主控固件没有总线恢复机制，HAL 状态停在 BUSY 后所有后续访问失败，只能靠断电重启恢复&lt;/strong&gt;。&lt;/p&gt;&#xA;&lt;p&gt;更深一层，是把 I2C 当成了“不会坏的板内总线”。I2C 的电气特性决定了任何从机都能拉低总线，从机异常、掉电、复位不完整都会把整条总线拖死；而很多驱动示例只演示“读到了数据”，不演示“读不到时怎么把总线救回来”。产品在实验室里没有接触器、没有浪涌，自然测不出这个问题。&lt;/p&gt;&#xA;&lt;p&gt;还有一层是硬件设计的余量不足。传感器和主控共用电源、去耦不足，等于把现场的电源扰动直接传给最脆弱的从机；软件又没有恢复手段，硬件和软件两道防线同时缺位，故障就只能以“断电重启”收场。&lt;/p&gt;&#xA;&lt;h2 id=&#34;预防措施&#34;&gt;&lt;a href=&#34;#%e9%a2%84%e9%98%b2%e6%8e%aa%e6%96%bd&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;预防措施&#xD;&#xA;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;I2C 驱动模板强制包含三件事：事务超时、总线恢复（9 时钟 + STOP + 重新初始化）、从机复位；新项目直接复用，不再每次手写。&lt;/li&gt;&#xA;&lt;li&gt;出厂测试加入总线故障注入：拉低 SDA、从机掉电、总线短路，验证恢复时间与成功率，作为量产门槛。&lt;/li&gt;&#xA;&lt;li&gt;传感器供电与主控分开滤波，关键设备加 TVS 和独立 LDO；长线、多从机场景核算总线电容与上拉。&lt;/li&gt;&#xA;&lt;li&gt;设备遥测上报 I2C 错误计数、恢复次数、传感器复位次数，把“总线健康度”变成可观测指标。&lt;/li&gt;&#xA;&lt;li&gt;现场选型时把电磁环境纳入评估，接触器、变频器附近的终端优先选用带隔离或更强防护的接口方案。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;总结&#34;&gt;&lt;a href=&#34;#%e6%80%bb%e7%bb%93&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;总结&#xD;&#xA;&lt;/h2&gt;&lt;p&gt;I2C 是一条没有仲裁者的共享总线：只要有一个从机按住 SDA，整条总线就停摆，而主控通常只会“再试一次”。这次故障里，传感器没有坏，主控也没有死，坏的是两者之间那条没人负责恢复的线。把总线恢复写进驱动、把故障注入写进测试、把传感器复位做成硬件能力，才能让设备在电磁环境里自己站起来，而不是等人去拔电。&lt;/p&gt;&#xA;&lt;h2 id=&#34;延伸阅读&#34;&gt;&lt;a href=&#34;#%e5%bb%b6%e4%bc%b8%e9%98%85%e8%af%bb&#34; class=&#34;header-anchor&#34;&gt;&lt;/a&gt;延伸阅读&#xD;&#xA;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://blog.5772447.xyz/posts/10494fcc/&#34; &gt;RS485 总线干扰致采集网关每隔几小时集体掉线&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a class=&#34;link&#34; href=&#34;https://blog.5772447.xyz/posts/3e0783d8/&#34; &gt;任务栈溢出致随机死机：HardFault 现场取证&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
        </item></channel>
</rss>
