1. 偶发故障为什么让人头大:先从“假故障”说起
干嵌入式、硬件调试这行的朋友,应该都体会过这种绝望:某个 bug 一周出现一次,你盯着它的时候它不发脾气,你刚把工装收起来它立刻给你颜色看。更气人的是,你用万用表、示波器、逻辑分析仪轮番上阵,数据全是对的,代码逻辑翻了三遍也没毛病,最后发现是串口线松了、USB 转串口芯片虚焊、甚至是隔壁工位的大功率电机一启动就干扰了通信。
这类问题,圈内通常叫“偶发故障”。它的特点就是三个字:不、稳、定。你的直觉告诉你问题出在某个模块上,但你的工具告诉你一切正常,这时最危险的决策就是“凭感觉换件”——换了十几块板子也没找到根因,最后发现是治标不治本。
我自己的习惯是,遇到偶发故障先别急着动手,而是先做一个分类:到底是硬件层面的物理连接问题、固件层面的逻辑时序问题,还是环境层面的电磁干扰问题?三个方向对应完全不同的排查手段。就拿标题里提到的三个场景来说——串口假故障、蓝牙断开的取证、烧录的新旧批次对照,恰好覆盖了这三类问题中最典型的还原思路。下面我把三个案例串起来讲讲,每一步我都尽量还原当时的判断过程,方便你以后遇到类似问题能直接套用。
先说一个最重要的原则:偶发故障的排查,本质是“固定变量”的过程——把你能控制的全固定住,让你的操作可复现,然后一次只改变一个变量,观察故障是否跟着变。这句话听起来像废话,但真到现场,多数人一紧张就把这个原则丢了。
2. 串口“假故障”:换机排除法怎么用才能不冤枉硬件
串口通信出问题,是嵌入式开发里最常见的偶发故障。现象往往是这样:板子跑着跑着,串口助手突然不吐数据了;或者你发一条命令,回包要等十来秒;再或者连上之后收发都正常,但一旦拔插 USB 线就再也枚举不出来。
遇到这种情况,很多人的第一反应是“单片机死机了”,然后就开始查代码、查看门狗、查中断优先级。我踩过不止一次坑,最后发现根本不是芯片的问题——是USB 转串口芯片的驱动状态出了问题。尤其是 CH340、CP2102 这类常见的转换芯片,在反复拔插或者电脑休眠唤醒之后,驱动会进入一种半死不活的状态。设备管理器里看着一切正常,但数据就是不通。
2.1 换机排除法的正确打开方式
换机排除,不是让你直接换一块新板子,而是分两步走:
第一步,换“机”不换“芯”。把 USB 线从电脑这头拔下来,换一个 USB 口插。注意,要换到不同的 USB 控制器上,比如前置面板插到后置主板接口,而不是从 USB2 换到 USB3——同一个控制器下的口子,问题往往还在。插上之后打开设备管理器看看 COM 口号有没有变化,再打开串口调试助手发一条测试指令。
第二步,换“机”换“线”。如果换口没用,那就换一根 USB 线。很多串口假故障其实是线材内部的屏蔽层断裂或接触不良导致的,外表看不出任何损伤,但插到某些供电稍弱的接口上就开始随机丢包。这里有个很容易被忽略的点:线的长度。超过 1.5 米的 USB 线,在没有铁氧体磁环的情况下,抗干扰能力会明显下降。你要是恰好用的是那种 3 米长的打印机线,那串口出怪问题的概率就很高了。
这两步做下来,如果故障依旧,才轮到怀疑目标板。但即便是板子的问题,也别一上来就动芯片。先量一下板子上的 3.3V 和 GND 之间的纹波,很多“串口假故障”的本质是供电不稳导致电平阈值被噪声踩来踩去。你拿示波器看 TX 引脚的波形可能完全是正常的,但对方的 RX 引脚因为参考地电位漂移,就是收不到正确的数据。
2.2 如何判断是不是 DMA 和中断配置的锅
软件层面的串口问题就更有意思了。热搜词里有一堆“串口 DMA”“AT32 串口 DMA 发送”“GD32F470 串口”之类的内容,说明大家在这上面栽跟头的不少。DMA 发送有个经典的偶发故障:数据长度不是 4 字节对齐时,最后一次传输可能没有被正确触发。这类故障的表现就是“偶尔多发一个字节”或者“最后一个字节丢了”,而且往往在系统长时间运行后才出现。
我排查官方库的 DMA 发送接口时发现,不少人忽略了 DMA 传输完成中断和串口发送完成标志(TC)之间的时序关系。假设你的代码逻辑是这样的:往 DMA 里灌数据 -> 启动 DMA -> DMA 搬运完成触发中断 -> 在中断里关掉 DMA -> 认为发送完毕。这个逻辑看起来没问题,但它有一个隐藏风险——DMA 搬运完成并不等于串口移位寄存器已经把数据全部发出去了。如果此时你立刻操作 GPIO 或者切换串口的复用功能,最后几个字节就会被截断。
正确做法是在 DMA 传输完成中断里先判断一下串口的 TC 标志,等它置位后再做收尾操作。你可以在发送完成标志置位前加一个超时等待,超时时间设为当前波特率下发送 2 个字节所需的时间。比如 115200 波特率下,1 个字节约 86.8 微秒,2 个字节就是约 174 微秒,你等个 200 微秒完全够。
再补一个我印象很深的案例:有个项目用 GD32F470,串口接收偶尔会卡死,主程序里的事件循环明明跑得好好的,但串口中断就是不进来。查来查去,最后定位到问题是USART 的 RXNE(接收非空)标志被错误地提前清零。代码里有人写了一句“读 SR 寄存器再读 DR 寄存器”,但在 GD32 的库函数封装里,这两个步骤之间插了别的操作,导致 RXNE 被意外清掉,数据就一直积压在移位寄存器里,后面的数据全被覆盖。
这种问题靠看代码很难发现,但有一个很土但很有效的取证手段:在串口中断里加一个 IO 翻转。进中断拉高、出中断拉低,用示波器测这个引脚,能直观地看到中断触发频率和实际收发数据是否匹配。如果翻转频率远低于你发送的数据帧率,那基本可以确定是中断被吞掉了。
2.3 串口假故障的取证清单
为了方便你现场操作,我把串口假故障的排查步骤整理成一个固定的操作流程,每次遇到类似问题就照着跑一遍,基本能覆盖八成的可疑点:
- 换 USB 口、换 USB 线,排除物理层和驱动层问题。
- 用串口调试助手,分别测试自发自收(把 TX 和 RX 短接)和与目标板通信,判断故障在哪一侧。
- 用示波器看目标板 TX 引脚的空闲电平是否正常,对比度不要低于 2V。
- 量目标板的供电纹波,特别关注串口芯片的 VCC 引脚,纹波大于 100mV 就值得怀疑。
- 检查代码里 DMA 发送的完成回调,确认是否等待了 TC 标志。
- 检查接收中断里是否误清了 RXNE,必要时用 IO 翻转做探针。
- 如果以上都正常,把波特率降低一个档位(比如从 115200 降到 57600)跑 24 小时,看故障是否消失。若降速后稳定,基本可以断定是设计裕量不足或干扰问题。
3. 蓝牙断开的录屏取证:把“抓不到”的现场变成可分析的证据
蓝牙偶发断开这类问题,比串口更折磨人。串口好歹能用示波器、逻辑分析仪去逮信号,蓝牙是无线通信,你看不见摸不着,而且它的协议栈状态变化非常快——从连接、配对、加密到休眠,任何一个环节出问题都可能导致断开。
我一开始调试蓝牙断开问题时,犯过一个典型错误:只用串口日志记录目标板的运行状态,发现断开瞬间的日志只有一句“Disconnected”,其他什么都看不到。后来才意识到,蓝牙断开的根因往往不在目标板,而在对端的 Host 设备上——可能是手机、PC 或者另一个嵌入式设备的蓝牙栈在某个时刻主动发起了断开请求。目标板只是被动地收到了断开指令而已。
这就引出了一个关键的排查原则:蓝牙问题要取证,必须至少记录两端的视角。目标板记录自己收到的 HCI 事件,对端记录自己的操作日志或系统日志,两边的时间戳对齐,才能还原断开那一刻到底是谁先动手的。
3.1 录屏取证具体怎么做
热搜词里有“realme 7 蓝牙日志”“蓝牙 A2DP 切 SCO 模式”“蓝牙键盘”这类内容。我拿一个具体的例子说:有个外设项目,连接手机之后,播放音乐偶尔会卡顿,严重时就断连。一开始怀疑是板子的射频性能不行,天线匹配没调好。后来通过录屏取证发现,问题不在射频,而是A2DP 协议栈在媒体播放和通话模式切换时行为不正常——手机一端发起 SCO 连接(通话模式)时,目标板没能正确处理,导致协议栈状态错乱,然后手机主动踢掉了连接。
取证方式很简单,就是把你手机屏幕的“开发者选项”里的蓝牙日志打开,然后录屏。具体步骤如下:
- 在手机的开发者选项里打开“蓝牙 HCI 信息日志”和“蓝牙调试日志”。不同手机路径不同,有的在“开发者选项-蓝牙”子菜单里。
- 打开系统自带的录屏功能,或者用电脑连手机,用投屏软件录屏。
- 复现故障场景,比如播放音乐、来回切歌、锁屏、解锁、接打电话。
- 故障复现后,停止录屏,同时从手机里导出蓝牙日志(通常在 /data/log 下,需要 adb pull)。
- 用 Wireshark 打开导出的 BTSnoop 文件,按时间戳分析断开前后的 HCI 事件序列。
这套流程操作起来并不复杂,但价值极高。在 BTSnoop 日志里,你能清楚地看到 Link Supervision Timeout 是谁触发的、断开连接的原因码是多少(比如 0x08 是连接超时,0x13 是远端设备主动断开)、以及在此之前有没有发生过 Encryption Change 之类的异常事件。
特别提醒一下:很多人拿着 BTSnoop 日志看不懂,因为里面全是 HCI 层的原始事件。这时候你需要关注的关键字段其实是下面这几个:
- 原因码(Reason):直接告诉你断开是哪一端发起的。
- 断开前的最后一个 ACL 数据包:往往包含了协议栈在断开前处理的最后一个业务数据,能帮你判断是不是某个数据包触发了对端的异常。
- 连接参数更新请求:如果对端频繁请求更新连接间隔(Connection Interval),说明链路质量可能在下滑,底层正在尝试增加重传机会。
3.2 录屏取证之外的第二落点:射频底噪
如果 HCI 日志显示断开原因是对端发起的 Link Supervision Timeout,那就回到硬件层面了。很多蓝牙偶发断开的根子其实是射频底噪抬高——板子上某个高速信号线或者 DC-DC 转换器在特定负载下产生电磁辐射,恰好落在 2.4GHz 频段附近,把蓝牙的接收灵敏度拉低了好几个 dB,导致对端一段时间内收不到足够的正确数据包,最终判定链路超时。
这种问题往往是“换板子才能复现”的典型——你拿三块板子测,可能只有一块有问题,因为每一块板子在装配过程中,天线区域的接地、屏蔽罩的焊接情况都有细微差异,这些差异在传导测试里根本看不出来,但一放到真实环境里就区别明显了。
我之前遇到过类似情况:某款板子在实验室环境下连接稳定,但一拿到客户现场的产线上就频繁断连。后来发现是产线工位上有一个大功率的变频设备,它的开关频率谐波正好覆盖了 2.4GHz。这个问题你靠改软件是没法解决的,只能调整机构的放置位置,或者给蓝牙天线区域加屏蔽罩。
所以,当你拿到一条“蓝牙偶尔断开”的售后反馈时,先别急着改代码,先问三个问题:断开发生在什么场景(通话、播放、还是待机)?断开之后是自动重连还是需要手动重连?换一个设备(比如换一台手机)连接是否同样断开?这三个答案能帮你把问题快速分流到“协议栈”“射频”“干扰环境”三个方向。
3.3 蓝牙 HID 设备的特殊排查点
搜索结果里有一堆“蓝牙 HID”“蓝牙键盘”的搜索词,说明做蓝牙外设的朋友也不少。蓝牙 HID 设备有个特殊的坑:HID 设备通常使用中断端点进行数据传输,为了省电常把连接间隔拉得很长——比如从标准 7.5ms 拉到 30ms 甚至更长。连接间隔拉长之后,链路层的重传窗口变小,如果环境里有少量持续干扰,碰撞概率会明显上升,于是你就能看到“键盘偶尔卡键”“鼠标偶尔飘一下”这类问题。
排查这类问题,除了录屏取证,还可以做一个很简单的实验:把设备拿到一个没有任何其他 2.4GHz 设备的房间,用一条很短的 USB 延长线把 USB 接收器(或者蓝牙适配器)靠近设备,如果问题消失,基本确定是干扰。如果问题依旧,那就是设备自身射频能力不足,得回炉重调天线匹配。
我还会建议所有做蓝牙外设的项目组,在产测阶段加一道“Firmware RSSI 上报”的工序——让设备在测试模式下每分钟上报一次自己和手机之间的 RSSI 值。这道工序能帮你筛掉 90% 的天线焊接虚焊问题,别问我是怎么知道的,问就是焊台温度没调好导致一整批天线焊盘都是冷焊。
4. “新旧批次对照”的烧录排查:烧录失败为什么总在换批之后爆发
第三个案例是烧录问题。做量产的朋友应该深有感触:一款产品在研发阶段烧录了几百块板子都好好的,一进量产线,突然烧录失败率飙升。这时候最经典的做法就是拿研发阶段的板和量产批次的板子做对照,逐项比差异。但我要说的是,对照不是简单地“换一块新板试试”,而是要系统地隔离变量,否则很容易被表面上的“批次问题”带偏。
4.1 烧录失败的第一块试金石:换工具、换软件版本
烧录失败的高频原因,往往不是芯片本身坏了,而是工具链和芯片的兼容性出了问题。搜索结果里有“Keil5 烧录失败”“JLink 烧录 SPI 速度”“IAR 烧录外部 BIN”“SDKManager 烧录 super 模式”这些词,基本覆盖了大家常踩的区域。
我在量产现场排查烧录失败时,第一步永远是直接换一台烧录工具——不是换烧录器品牌,而是换同一个品牌型号但确认固件版本不同的另一台。原因很简单:烧录器内部的固件也可能有 bug。你手里这台 JLink 可能用了两年没升过级,而新批次的芯片内核对 SWD 时序要求更严格(比如某些新内核把复位脚的时序窗口缩短了),旧固件的时序就是踩不进去,于是批量失败。
搜索词里有一个很典型的场景“JLink 烧录 SPI 速度”,说的是用 JLink 给 SPI Flash 烧录时速度设太高导致失败。JLink 的 SPI 模式最高能跑到 30MHz 以上,但很多 SPI Flash 在 3.3V 供电下的最高工作频率低于这个值,尤其是国产 Flash,标称 25MHz 的芯片实际可能只稳定工作在 20MHz。你的烧录配置是以前的老项目复制来的,Flash 型号却换了,于是烧录时偶发校验失败或写入超时。解决方案很简单:把 SPI 时钟频率降一半,观察是否恢复稳定。
所以我的建议是,遇到批量烧录失败,先别换芯片,先把烧录工具、烧录软件版本、烧录频率这三个变量全部记录下来,然后逐项降级测试。记录变量很重要——很多时候你换了工具就好了,但你不做记录,过两个月同样的问题又会以另一种形式冒出来。
4.2 新旧批次对照的完整步骤
所谓“新旧批次对照”,正确的打开方式应该是这样:
- 确认新批次芯片的具体型号丝印、封装批次号(date code),查一下是否为替代料、国产替代料或者修订版本。新批次的芯片可能已经悄悄改过内部 Bootloader 的实现,导致你之前的外部烧录时序失效。
- 确认烧录工具的固件版本,检查官方发布说明里是否提到对新芯片的支持补丁。
- 确认烧录软件(Keil、IAR 或专门的烧录工具)的版本,有时候 Debug 驱动和 Flash 算法也跟新批次的内核不完全兼容。
- 用最小化烧录方式做交叉实验:用新批次芯片烧旧项目的固件,用旧批次芯片烧新项目的固件,看是芯片批次问题还是固件与工具链的联合问题。
- 如果最小化实验正常,那就把烧录速度逐渐提高,找到临界点。比如从 1MHz 开始,每次翻倍,直到烧录开始失败,记录这个临界速度,然后把量产配置设定在临界速度的一半以下。
这个流程里最容易漏掉的环节是第 2 步——烧录器固件版本。很多老工程师用习惯了某款烧录器,从不升级固件,认为“能用就不动”。但芯片原厂(比如某 MCU 厂商)在发布新批次芯片时,有时会踩到旧烧录器固件的坑,官方会在新版本里修复。你如果不升级,就只能干瞪眼。
4.3 一个让我印象深刻的烧录排查案例
有一回,某个量产项目突然出现约 3% 的烧录失败率,现象是“校验失败,地址不一致”。当时承包产线的兄弟公司一口咬定是我们 MCU 的问题,让我们把芯片退回原厂分析。我没急着退,先让产线停掉烧录工位,把烧录器换成了另一台备用机,结果失败率立刻降到 0.1% 以下。
然后我拿失败的板子和成功的板子做对照,量了烧录接口的波形,发现失败板子的 SWDIO 波形上升沿有轻微回勾,对应的源头是板子上 SWDIO 走线过长且没有加终端电阻。产线用的那台烧录器输出驱动能力较弱,遇到走线较长的板子时信号反射导致时序裕量不足;而备用机输出驱动能力强,直接把波形“压”平了,所以能够正常烧录。
这个案例说明,新旧批次对照不能只停留在芯片层面,还要把 PCB 工艺的差异算进去。新批次的 PCB 如果换了一家板厂,或者在同一家板厂改了叠层、换了油墨,走线的阻抗特性都可能变化。你可以要求产线把新旧两批次的 PCB 板各拿一块,量一下 SWD 信号线的对地电容,如果差异超过 40%,就说明工艺差异已经大到足以影响烧录时序了。
4.4 烧录排查的现场速查表
结合踩过的坑,我在现场一般按下面这张表排查烧录失败:
| 排查项 | 操作 | 判定标准 |
|---|---|---|
| 烧录器固件 | 对比新旧两台烧录器的固件版本 | 不一致则优先用新版固件重试 |
| 烧录线长度与质量 | 换短而粗的杜邦线或排线 | 用 10cm 内的线重试,故障率下降即为线材问题 |
| 烧录速度 | 从最高速逐级下调 | 找到临界速度,量产配置用其一半 |
| 板级供电 | 测量烧录时目标板核心电压纹波 | 纹波 > 50mV 则排查供电电容、LDO |
| SWD 信号波形 | 示波器测 SWDIO 上升沿 | 有明显振铃或回勾时加终端电阻 |
| 芯片批次 | 换几个不同日期批次的芯片做对照 | 某个批次全部失败而其他正常,重点排查替代料 |
这张表运行起来很快,最重要的是每一轮只改一个变量,不要一会儿换线一会儿降速度,以免糊成一片找不到方向。
5. 偶发故障排查的三个底层能力:记录、对照、复现
讲完三个具体案例,我想把视角拉高一点,聊一聊偶发故障排查背后的几个底层能力。这些东西不会写在任何手册里,但恰恰是区分“老手”和“新手”的分水岭。
5.1 现场记录的颗粒度决定你的返工次数
偶发故障的第一杀手是“凭记忆排查”。你当时在现场看到的现象,两小时后可能就记不准确了;你临时改的那行代码,第二天可能就忘了。所以我的习惯是,从接手偶发故障的第一分钟开始,就用本子(或者一个专门的文本文件)记录每一个操作步骤、每一个观察结果、每一条命令的输出。
记录颗粒度要细到这种程度:某年某月某日某时,用哪台电脑、哪个 USB 口、哪根线,连接哪块编号的板子,使用的软件版本号是多少,执行了哪条命令,出现了什么现象。这些信息看似琐碎,但在你做新旧批次对照、变量隔离的时候,是唯一可靠的依据。
我见过太多同事在排查时“感觉好像”“我记得当时”这样含糊的描述,最后花了一个星期也没定位到问题。而你如果从一开始就把记录做细,可能三天就能从记录里发现规律——比如“故障都是出现在连续运行 47 分钟左右”,顺着这个线索就能找到某个定时器的溢出配置错误。
5.2 对照法:没有对照就没有结论
偶发故障排查里,一个关键思维就是对照。串口假故障是“换机对照”——换 USB 口看是不是控制器的问题;蓝牙断连是“换端对照”——换手机看是不是目标板的问题;烧录失败是“新旧批次对照”——换芯片、换工具、换速度,逐项对比变化量。
对照法的核心是“一次只变一个量”。很多新手一上来就同时换工具、换线、换芯片、改配置,结果故障消失了,但你不知道到底是哪个操作起了作用。这就像同时吃了三种药,病好了也不知道是谁的功劳。你要做的是把可能的变量列成一张清单,从最可疑的开始,一个一个排除,每排除一个变量都要做一个“反向实验”——也就是把上一个变量的状态恢复原样,再测试一次,以确保之前的变化确实有效。
这个反向实验很关键,它能避免你被巧合欺骗。比如你换了根线之后故障消失了,你很高兴,但如果你只是换了线没有做反向验证,你可能会忽略另一个事实——故障本来就自己消失了,跟换线没关系。偶发故障的特点就是时好时坏,不做好反向验证,很容易把假关联当成根因。
5.3 复现:不可能三角里的最优解
所有偶发故障排查里,最难的一步是复现。复现需要三个条件同时满足:正确的环境、正确的状态、正确的时间窗口。这三个条件往往构成一个不可能三角——你控制了环境,状态就不对;你等到了正确的状态,环境又变了;你终于抓到正确的现场,时间窗口又已经过去了。
我的应对策略是“不追求完整复现,只追求局部复现”。比如蓝牙断连问题,如果在办公室无法稳定复现,那就跑到客户现场,把客户的手机、客户的椅子位置、客户的网络环境都尽量复制过来;如果还是无法复现,那就退一步,只复现其中一个子问题——比如让设备在特定信号强度下进入低功耗模式,观察它的协议栈行为。
局部复现的价值在于,即使你无法触发完整故障,也能收集到足够多的中间状态数据。这些数据结合你之前的记录,往往能帮你发现一些平时没注意到的规律——比如 RSSI 掉到某个阈值以下时,协议栈的状态转换时间会超时;再比如某个特定型号的手机在连接成功后,会周期性发送一个长度异常的数据包。
5.4 排查工具的正确用法:日志要分层、取证要可回放
最后一个底层能力是工具的使用。很多人用串口调试助手、Wireshark、逻辑分析仪,但不知道“分层取证”这个思路。所谓分层,就是同一个问题要从多个层面同时记录:
- 应用层:记录业务数据和用户操作的日志。
- 协议层:记录协议栈的状态变化,比如蓝牙的 HCI 事件、TCP 的握手状态。
- 物理层:记录信号波形、电压值、频谱底噪。
三层数据缺一不可。只有应用层的日志,你只能看到“断了”;加上协议层的日志,你能知道“谁断的”;再加上物理层的测量,你才能回答“为什么断的”。这三个层面全部对齐,才算是拿到了一件完整的证据链。
另外一个习惯是,给取证工具加时间戳同步。比如你用示波器抓波形的同时,用手机录屏记录操作过程,两个设备的时间基准要同步。我自己会在开始排查前,先拍一张手机时钟和电脑时钟的对照照片,用来做时间偏移校准。这看起来有点笨,但在多设备协同取证时非常有用。
6. 从一次偶发故障,到一套可复用的排查体系
说实话,写完整篇文章,我最想强调的其实不是具体的换机步骤,也不是烧录速度调到多少,而是排查偶发故障时的那套思维框架。工程项目里碰到的各种偶发问题,几乎都能映射到“物理连接、协议栈逻辑、环境干扰”这三个方向里。你只要养成了记录变量、逐项对照、局部复现、分层取证这四种习惯,即使完全没接触过的故障类型,也能在短时间内理出个头绪来。
最后分享一个我个人的习惯:每次解决完一个棘手的偶发故障,我都会把过程整理成一页纸的排查记录,放进项目文档的最后。格式极简——现象、排查链路、根因、修复方案、预防建议,五段式。等到下一次遇到“似曾相识”的故障,我直接翻这一页,几分钟就能找到上次的结论,而不是又从头开始。这些来自一线的碎片化经验,攒多了之后,就是团队内部最硬核的知识库。