过去这一个月,我被三个偶发 bug 磨掉了半层皮:串口打印偶尔顿住、蓝牙连着连着就断、还有一批板子在烧录时随机失败。三个问题看起来毫无关联,但真查下来,用的都是同一套思路——先别急着改代码,先把“偶发”变成“必现”,再用换机、录屏、对照批次的手段固定证据。这篇就聊聊我在这三类问题上的完整排查过程,包括那些走不通的路和最后真正有效的做法。
1. 偶发 bug 的底层逻辑:先让“鬼”现形,再谈定位
1.1 为什么偶发 bug 总是“重启就好”?复现率才是第一突破口
干嵌入式这行的,八成都被“偶发 bug”坑过。产品交到客户手里,一个星期崩一次;测试那边复现了,一到研发手里就装乖;你盯着串口日志盯了半天,它偏偏在你转身倒水的瞬间跑出一个异常,然后一切恢复正常。最气人的是,这种问题你拿去问领导,领导只会说“再观察观察”,问测试,测试说“概率很低”,问硬件,硬件说“波形没问题”。
我的经验是:偶发 bug 之所以难搞,不是因为原因藏得深,而是因为触发条件太多,像一团乱麻。你不知道它在什么电压、什么温度、什么操作序列下才会跑出来,所以每次复现都像抽奖。这时候最忌讳的就是“猜”,猜是看门狗复位,猜是蓝牙天线干扰,猜是 Flash 烧录参数不对。猜十次能猜中一次都算运气好,绝大多数时候是改了一版代码,问题还在原地等你。
所以我的第一步永远是同一件事:把复现率提上去。复现率上去了,才有资格谈定位。怎么提?三个老办法:压测、降频、记录环境变化。比如串口问题就加大通信频率、长时间跑压力脚本;蓝牙问题就缩短连接间隔、在模块旁边反复开关手机蓝牙;烧录问题就连续烧几百次,把概率从“千分之一”放大到“几十分之一”。压测的目的不是折磨设备,而是让随机性暴露到肉眼可见的程度。
1.2 建档立卡:时间、环境、操作序列一个都不能少
在开始任何手段之前,我强烈建议先建一份“问题档案”。别嫌麻烦,这份档案是后面所有排查的锚点。具体记什么?四点:
- 时间:现象发生的确切时间点,持续多长时间,多久恢复。
- 环境:当时的供电方式(USB 供电还是适配器)、温度、设备摆放位置、附近有没有其他无线设备。
- 操作序列:用户或测试人员在这个现象出现前做了什么动作,精确到“打开某个 App”“插入某个 USB 口”“按下某个按键”。
- 记录方式:串口日志存没存?录屏有没有?拍照拍了没有?
这套东西看着简单,但能救命。我曾经排查一个蓝牙偶发断开,用户一口咬定是“什么都没干,自己断的”。结果回看操作序列,发现他每次都是先打开微信语音通话,再戴上蓝牙耳机,断开都发生在语音通话切换听筒的瞬间。这个现象如果不建档,根本不可能从聊天记录里挖出来。建档之后,问题就从“偶发”变成了“有条件复现”,方位立刻缩小了。
1.3 从“偶发”到“必现”的常见杠杆:压力、温度、时序
除了压低复现率,还要学会用“杠杆”撬开问题的壳。我见过太多工程师一上来就查代码,其实是方向错了。偶发 bug 常见的三个杠杆是压力、温度、时序。
- 压力杠杆:通信就加长数据包、提高波特率;蓝牙就增大数据吞吐、缩短广播间隔;烧录就取消校验重试、强行降速。压力能放大临界状态,比如电源纹波在低速时无所谓,高速一拉就原形毕露。
- 温度杠杆:有些批次问题就是低温下 Flash 读写出错、高温下蓝牙射频失锁。用热风枪吹一吹、用冰袋贴一贴,看现象是否跟着温度走。不用精确控温,能看出趋势就行。
- 时序杠杆:上电时序、复位时序、使能引脚时序,这一块最容易出问题。比如 STM32 的 BOOT0 引脚在烧录瞬间被拉高,但上电顺序不对就导致偶尔进不了 Bootloader。这种问题只有靠反复上下电、插拔 USB 来诱发。
杠杆的作用不是“修复问题”,而是把问题逼到墙角。等你能做到“只要做某个动作,现象必现”,后面的换机、录屏、对照批次才有意义。
2. 串口假故障:换机排除的第一步不是查固件,而是查工具链
2.1 什么是串口假故障?它为什么能骗过所有人
串口是嵌入式调试的“眼睛”,可有时候这双眼睛本身就有白内障。所谓“假故障”,指的是产品实际工作正常,但因为调试链路坏了,让你误以为设备出了问题。
最典型的表现:串口打印突然中断,几秒钟后自己恢复;打印内容出现乱码;一插上 USB 转串口模块电脑就开始丢 COM 口;数据收发时有大量超时。你会下意识地怀疑固件是不是跑飞了,DMA 配置是不是有问题,结果反复查代码,查不出任何毛病。问题不在产品,而在你手里那根线、那个调试小板、那台电脑的 USB 控制器。
为什么它能骗过所有人?因为调试链路是“隐形的”。你会默认 USB 线是好的,默认 CH340 驱动是好的,默认串口助手的缓冲区设置是对的。而这些默认恰恰是假故障的温床。尤其当你的测试工装用了非常便宜的 USB 转串口模块时,问题更多——不是不能用,而是偶发丢数、偶发断流,让你以为是设备端的稳定性问题。
2.2 换机排除的标准剧本:USB转串口模块、线材、驱动、电平
“换机排除”这个词听起来很土,但它是消除调试链路变量的金标准。我通常按下面顺序执行,每一步都只改变一个变量:
- 换 USB 转串口模块。把 CH340 换成 FT232,或者换成 CP2102,再跑一遍压力测试。不同芯片的缓冲策略、驱动稳定性差异很大。便宜的 PL2303 在某些电脑上一旦 CPU 休眠,醒来后串口直接挂死,这种情况换模块立刻见分晓。
- 换 USB 线。优先换短而粗的线,最好是带屏蔽磁环的。长线在高速通信时线间串扰会放大,尤其是 115200 以上波特率时更明显。USB 供电也受线阻影响,线太长会导致模块供电不足。
- 换 USB 口。台式机后面的 USB 口和前面板的 USB 口供电质量完全不同,笔记本左侧和右侧的口也可能出自不同的控制器。串口偶尔断,换到另一个口就好了,这种我碰到过不止一次。
- 换驱动版本。CH340 的驱动在不同 Windows 版本上表现差异很大,有些版本的驱动在收到大量数据时会疯狂报警。官方驱动和系统自动安装的驱动也可能行为不同。
- 检查电平匹配。如果你的设备是 3.3V 逻辑,而调试小板是 5V 的,偶尔能通但长时间跑就会不稳。尤其是引脚没有做电平转换直接硬连的时候,可能出现边缘过冲、波形畸变。这时候不是换机,而是得加转接板。
这套流程做完,如果现象消失,那基本可以断定是调试链路的问题,产品固件可以暂时放手。
2.3 一次串口乱码的完整排查记录:换到第三个小板才真相大白
说个真实案例。一个设备在产线上测试时,串口打印末级信息偶尔出现乱码,几秒钟后恢复正常。硬件同事拿示波器打波形,说 UART 波形没问题,肯定是我们固件的问题。我一个一个模块换,前两个 USB 转串口模块都复现,换到第三个旧款 FT232 小板之后,乱码消失了。
当时我也觉得奇怪,同样是 USB 转串口,为什么前两个会乱码?后来把两个模块拆了,发现都用的是 CH340G,但一个是某宝上一块多钱的裸片焊的,晶振旁边滤波电容都省了;另一个是正规封装但板子布线很差。第三个是原装 FT232RL 小板,电源和地处理得干净。再用示波器看 CH340 的 TX 脚波形,发现高电平幅度只有 2.4V 左右,而且有振铃,到了设备端的 RX 引脚时已经被削得不成样子。FT232 模块输出 3.3V 干净方波,一发入魂。
这件事给我的教训是:串口乱码、偶尔断流的优先级,永远是先换物理链路,再动软件。不是固件工程师不自信,而是调试工具的可信度远没有你想象得那么高。那之后,我给测试工装定了规矩:串口调试一律用带隔离的 FT232 或 CP2102,USB 线长度不超过 1 米,驱动统一安装官方版本。
3. 蓝牙断开的录屏取证:用时间戳把责任钉死在链路的某一端
3.1 蓝牙问题为什么最容易扯皮:应用层、协议栈、射频谁都可能
蓝牙问题排在偶发 bug 排行榜前三名不是没道理的。因为链路长,环节多,而且看不见。手机这边有系统蓝牙协议栈,有 App 处理逻辑;设备那边有射频前端、协议栈、应用层回调;中间还有空中接口,受干扰、距离、遮挡影响。任何一个环节出问题,表现都是“蓝牙断了”,但你根本不知道是哪一环先动手的。
这种问题一旦到了团队内部,大概率演变成“软件说射频不行,硬件说 App 乱发指令,测试说你们别吵了”。如果拿不出硬证据,最后只能谁声音大谁有理。所以我处理蓝牙问题,第一个原则就是:先把证据固定下来,再开讨论会。证据最有效的形式不是截图,不是测试报告,而是带时间戳的录屏加日志。
3.2 录屏取证的正确姿势:屏幕、串口日志、协议日志三路对齐
具体怎么做?我推荐“三路对齐”:
- 第一路:录屏。手机端开系统录屏,把蓝牙设置页、App 界面、系统通知栏都录进去。重点记录断开瞬间的 UI 状态:是 App 提示“设备已断开”,还是系统蓝牙列表里设备消失,还是根本没有任何提示,只是功能没反应了。这三种现象指向的问题层次完全不同。
- 第二路:设备端串口日志。设备通过 UART 输出蓝牙事件,包括连接建立、断连回调、重连开始。断开瞬间设备端打印了什么?是收到了远端断连请求,还是自己这边触发了超时?这能直接区分是哪一端主动断的。
- 第三路:系统蓝牙日志。Android 可以打开开发者选项里的 Bluetooth HCI snoop log,抓下来的日志能分析空中包的收发情况;iOS 也可以通过 Profile 抓包。如果不想搞得这么深,至少也要用系统自带的功能导出一份日志,记录底层连接状态变化。
三路数据最关键的是时间戳对齐。录屏里的时间、串口日志打印的时间、HCI 日志的时间,可能各自用不同的时钟源。我的做法是:开始测试前,先用手机拍一下电脑屏幕上的串口日志窗口,让录屏画面里同时出现系统时间和串口打印的时间戳,制造一个“时间锚点”。后面分析时,根据这个锚点把三路数据的时间轴对齐。不要相信任何人“感觉上是先怎么样”的说法,时间戳对不上,一切结论作废。
3.3 我踩过的坑:没有录屏的“听用户说”,都是盲人摸象
我之前处理过一个蓝牙耳机项目,用户反馈“听音乐的时候声音断断续续,然后连接就消失了”。研发那边怀疑是 A2DP 切换到 SCO 模式导致的音频异常。因为没有录屏,只能靠用户描述,大家补了各种代码防范,问题还是复现。后来我让用户开着录屏复现,回看录像才发现:声音其实是先卡顿,然后手机顶部状态栏的蓝牙图标跳了一下,再过两秒 App 才提示断开。
关键点在于“蓝牙图标跳了一下”这个瞬间,恰好是手机来了一通骚扰电话,系统把音频链路从 A2DP 强制切到了 SCO。挂断电话后链路没有正确恢复,才触发了后续的断开。这个触发源如果不是看录屏,根本不可能从日志里单独读出来——因为日志里只有链路断开的结果,没有电话事件的记录。录屏把用户在干什么、系统在干什么、App 在干什么全部串起来了,问题的根因立刻就清楚了。
所以我现在做蓝牙问题,有个不成文的规定:复现时必须录屏,宁可多录三分钟垃圾,也不要漏掉关键的三秒。录屏不是给领导交差的,是给分析器用的。很多偶发问题的触发条件,恰恰藏在你以为“什么也没发生”的那几秒里。
4. “新旧批次对照”的烧录排查:固件没变,为什么这批板子不行
4.1 同一份 hex,不同批次表现不同:先分清物料、烧录配置、芯片版本
“新旧批次对照”是我在烧录问题上最常用的一招。现象通常很统一:老批次板子烧录一切正常,新批次板子偶尔烧录失败,或者烧录成功但运行一段时间后程序行为不对。代码是同一份,编译产物是同一个 hex,问题就只能出在“为烧录和运行提供基础环境的那些变量”上。
首先要把“批次差异”拆清楚。常见差异来源有四类:
- 主控芯片版本:同一型号的芯片,硅片版本可能更新。比如 GD32、APM32 这类国产芯片,不同批次丝印可能一样,但 Flash 特性可能微调过。老的烧录算法参数不一定适合新批次。
- 外部物料批次:Flash、晶振、复位芯片、电源芯片,任何一颗换料都可能影响烧录时序。
- PCB 工艺变化:板厂做了阻抗调整、焊盘改动、元器件位置微调,都可能让烧录接口的走线特性发生变化。
- 烧录环境变化:产线换了烧录器、换了电脑、换了下线软件版本,甚至换了烧录治具的线长,都会导致结果不同。
注意,这四类不是互斥的,可能同时存在。所以不要一上来就断定是芯片批次问题,先用排除法筛掉最容易混淆的“烧录配置差异”。
4.2 设计一张批次对照表,不用猜,直接比
我习惯把新旧批次所有相关参数列成一张表,做单向差异对比。表格大致长这样:
| 对比项 | 老批次(正常) | 新批次(异常) | 差异影响评估 |
|---|---|---|---|
| 烧录工具 | J-Link V9 | J-Link V9 | 无差异 |
| 烧录软件 | Keil MDK 5.30 | Keil MDK 5.31 | 烧录算法版本变了 |
| SWD 接口线长 | 10cm | 30cm | 时钟频率高时可能不稳定 |
| 烧录时钟 | 4MHz | 2MHz | 暂时降低,问题依旧 |
| 目标芯片批次 | 丝印 A 批次 | 丝印 B 批次 | 重点怀疑对象 |
| 供电电压 | 3.30V | 3.28V | 在规格范围内 |
| Flash 校验结果 | 通过 | 偶尔失败 | 可能擦除/写入不稳定 |
| 复位电路 | 上电复位 | 上电复位 | 无差异 |
这张表的价值不在于列得多全,而在于把“谈感觉”变成“看差异”。你会发现很多以前没注意的变量,比如 Keil 自动升级后烧录算法变了、产线换了批新线导致 SWD 信号质量下降。把表列完,至少能排除掉一半无意义的争论。
4.3 烧录环节的隐藏变量:速度、供电、复位时序、驱动版本
具体到烧录动作本身,有几个变量特别容易被人忽略:
- 烧录速度。SWD 接口不是越慢越好,但太快确实容易在走线不理想时翻车。老批次 4MHz 没问题,新批次可能因为引脚走线变化,需要降到 1MHz 才能稳定烧录。这不是“治标不治本”,而是确认硬件余量的一个手段。
- 供电稳定性。烧录器通过目标板取电时,如果电源是 LDO 且负载刚好接近极限,Flash 写操作瞬间电流拉高,电压跌落就会导致写失败。用示波器抓一下烧录瞬间的 VDD 波形,这种问题一眼就能看出来。
- 复位时序。很多 MCU 在 SWD 烧录时要求复位引脚时序配合。新批次如果换了复位电容容值,上电复位时间变长,烧录器连接时芯片还卡在复位状态,握手就会失败。这种偶发失败很符合“随机失败”的特征。
- 烧录器驱动和软件版本。J-Link 的 DLL 版本、Keil 的 pack 版本、串口 ISP 软件的版本,都可能影响烧录算法生成。新批次正好赶上软件升级,很容易背锅。把老版本装回去试试,是最快的验证方式。
我曾经遇到一次新批次 ESP32 烧录失败率陡增,经验做法都试了一圈没用,最后发现是产线的烧录工具参数里默认勾了“加密烧录”,新批次芯片的 eFuse 区域和旧批次不一样,加密流程额外多花了时间导致上位机超时。这种问题,如果不做新旧对照,光看代码永远找不出来。
4.4 烧录工具与驱动的坑:不要忽略“烧录器兼容性清单”
还有一个容易被甩锅的点是烧录器本身。同型号不同批次的烧录器,比如某宝的 J-Link 兼容版,固件可以被重新刷写,有的版本对某些芯片的时序兼容性极差。我见过一个产线用“J-Link clone”烧 STM32G0,老批次正常,新批次偶尔找不到核心,换一个正版 J-Link PLUS 后稳定烧了两千片。不是所有“同型号”都真的“同性能”。
我的建议是:一旦确认是烧录问题,先把烧录器列进“嫌疑名单”,不要默认它没问题。测试方法很简单,用同一个烧录器分别烧老批次和新批次各五十片,记录成功率;再换一个不同品牌的烧录器,重复同样测试。如果换烧录器后新旧批次的差异消失,那问题就在烧录器、线缆或上位机软件,而不是芯片或 PCB。
5. 三板斧怎么组合用:一套能让团队停止“互甩锅”的排查流程
5.1 三类问题的边界与先后顺序
把三招摆在一起看,它们其实分别对应不同层级:
- 串口假故障的换机排除,解决的是“调试工具引入了假信号”的问题,属于链路症状。
- 蓝牙断开的录屏取证,解决的是“多方接口模糊、责任不清”的问题,属于现场重建。
- 新旧批次对照的烧录排查,解决的是“生产变量导致行为差异”的问题,属于横向对比。
它们的共同点是:先用最可控的手段清理变量,再用独立证据固定现场,最后用横向对比锁定批次差异。所以实际项目里,我建议的顺序永远是从“工具不可靠”开始排除,再到“现场不可信”的取证,最后才是“批次不一致”的对照。如果一上来就做新旧批次对照,而实际只是调试链路坏了,那相当于把很好查的问题复杂化。
5.2 排查记录模板与团队协作的“接口约定”
最后分享一个很实用的协作模板,我称之为“单问题单页报告”。无论谁在排查偶发 bug,都要求用一页 PPT 回答五个问题:
- 现象是什么?(尽量附录屏或串口日志片段)
- 复现条件是什么?(时间、环境、操作序列)
- 我们已经排除了哪些变量?(换过什么、测过什么、对比过什么)
- 目前最可疑的两个方向是什么?(不许超过两个)
- 下一步计划做什么?由谁做?预期什么时候完成?
这个模板救了我不止一次。因为它强制把所有信息收敛到一页纸上,避免了群里几十条语音、十几张截图来回轰炸的混乱。测试、固件、硬件、产线,大家围绕同一份报告做更新和评审,谁也不会跑偏。
5.3 一点个人体会:偶发 bug 是“确定性”藏在“混沌”里
做了这么多年调试,我越来越觉得:偶发 bug 并不“偶发”,只是我们还没找到它的边界条件。每一个看起来随机的现象,背后都有一套确定的物理过程,只是变量太多、交互太复杂,暂时超出了直觉能跟踪的范围。
所以每次遇到“随机抽风”的 bug,我不再急着改代码,而是先问自己三句话:我的调试工具是不是可信的?我的现场证据是不是完整的?我有没有做过批次或环境的横向对比?这三句话问完,方向通常就清晰了一大半。
写这篇文章的初衷,也是希望大家少走一点弯路。换机排除、录屏取证、新旧批次对照这三招,表面上是三种技术,本质上是一种态度:先信证据,再信感觉;先控制变量,再谈修改。偶发 bug 最怕的不是难查,而是那句“算了,重启一下就好了”。每一次重启,都是在掩盖一次真相。下一次再遇到莫名其妙的故障,不妨先停下来,把链条上的每个环节都摆在桌上,一个一个地换、一遍一遍地录、一版一版地比。你会发现的真相,往往比想象中朴素得多。