做嵌入式开发最怕听到的词,就是“偶发”。不是完全复现不了,也不是一点没规律,而是那种“你盯着它的时候它就好好的,你一转身它就犯错”的bug。串口连着连着丢一帧,蓝牙配对好好的莫名断开,同一套固件烧进上一批板子一切正常、烧进新批次就起不来。这类问题最折磨人,因为它不给你一个稳定的出发点,你甚至没法判断是自己代码的问题、工具链的问题还是硬件本身的问题。
这篇内容我结合自己在调试室里跟这类“偶发故障”死磕的经历,重点聊三件事:串口假故障怎么用换机排除法锁定真凶,蓝牙偶发断连怎么用录屏取证建立可追溯的证据链,以及“新旧批次对照”这种烧录排查思路在实际操作中怎么落地。如果你正在被某个时好时坏的板子折磨,希望这篇能帮你少走几条弯路。
1. 偶发 bug 的底层逻辑:证据优先,直觉靠边
先说个我自己的感受。刚工作那两年,遇到这种偶发问题,第一反应往往是怀疑自己代码写错了,于是翻来覆去查中断、查时序、查缓冲区。查了几天没结果,开始怀疑芯片有问题,然后换芯片、换板子,最后发现是调试器接触不良——这种“查到最后发现是最不可能的地方出问题”的经历,估计不少同行都有过。
之所以偶发 bug 难查,核心在于它违背了人的直觉。我们习惯把故障当作“非黑即白”的确定性事件,但偶发问题本质上是概率事件,背后通常存在一个或多个月经不调的触发条件。这个条件可能是时序上的、电压上的、温度上的,也可能是工具链版本差异带来的,甚至可能是你换了一根 USB 线导致的。
所以在展开三个具体案例之前,我想先把排查方法论立起来。总结下来就是三句话:
- 先拿到证据,再谈定位。能录屏就录屏,能抓日志就抓日志,别靠记忆去猜。
- 每次只改变一个变量。换机器、换芯片、换固件、换线材,一次只动一个,否则你永远不知道是谁起作用。
- 建立“对照组”。同一套代码烧进不同板子,同一块板子烧不同固件,对比结果能帮你快速划定问题边界。
这三个原则贯穿本文的三个案例。你对照着看会发现,换机排除、录屏取证、新旧批次对照,本质上都是为了让“偶发”变成“可观测的、可复现的差异”。
2. 串口假故障的换机排除
2.1 什么是“串口假故障”
所谓假故障,就是表面上看是串口坏了,实际上坏的不是串口本身。我遇到过几种典型表现:
- 串口调试助手打开端口报“打开失败”或“被占用”,但设备管理器里明明枚举正常。
- 偶尔能收到数据,但数据乱码,或者丢字节丢得厉害。
- 程序跑得好好的,串口突然一整段没输出,过几秒又自己恢复了。
- 板子换到同事电脑上一切正常,回到自己电脑上就不行。
这些现象很容易让人怀疑是 MCU 的 UART 外设出了问题,或者代码里串口初始化有缺陷。但实际上,这里面相当一部分是“串口链路”的问题,跟芯片本身没关系。最常见的就是 USB 转串口适配器接触不良、驱动冲突、DMA 配置和空闲中断打架、电气电平不匹配。
2.2 换机排除法的操作步骤
遇到这种问题,我向来不急着改代码,先做一轮物理层和链路层的排除。换机排除法听起来笨,但极其有效,尤其适合“手上有多台电脑、多个适配器”的调试环境。
第一步,先把当前这套“板子 + USB 线 + 适配器 + 电脑”的组合整体换掉。拿一台确认健好的电脑、一根确认没有问题的 USB 线、一个全新的 USB 转串口模块,重新连接板子,看问题是否复现。如果问题消失了,说明问题就出在原先的链路组合里,而不是板子本身。
第二步,只换电脑,不换适配器;再只换适配器,不换电脑。这样交叉两次,基本能定位是电脑端的问题还是适配器的问题。如果换电脑后问题消失,多半是驱动或 USB 控制器兼容性问题;如果换适配器后问题消失,那就是适配器芯片老化、虚焊或者劣质线材导致信号质量差。
第三步,把板子上的 TX、RX 对地电阻和连接方式检查一遍。很多假故障是电压摆幅不够造成的,比如两个设备一个用 3.3V 电平、一个用 5V 电平,直接连就会偶尔正常偶尔异常。这种情况换成板子供电统一,或者加电平转换模块,问题立刻就没了。
2.3 假故障背后的“真凶”清单
按我踩过的坑和帮别人排查过的经验,串口假故障最常见的几种根因排个序:
- USB 转串口芯片驱动冲突,尤其是 CH340 和 CP210x 同时存在、或老版本驱动和新芯片不匹配。
- 劣质 USB 线,线芯太细导致供电不足或信号衰减,表现就是“动一下线就断开,不动就正常”。
- 串口助手软件的端口缓存机制,比如某些助手在缓冲区满了之后会直接丢数据,而不是暂停接收。
- 嵌入式端的 DMA 配置问题。如果使用了 DMA 接收但空闲中断处理不当,高波特率下容易丢包,看起来就像偶发故障。
- 电气干扰。电机、继电器这类强干扰源一启动,串口就开始乱码,干扰源一停就恢复。
我印象最深的一次是帮朋友查一块 GD32F470 板子,串口每隔几分钟就掉线一次。代码检查了好几遍找不到问题,最后发现是适配器插在电脑前面板的 USB 口上,供电不稳导致芯片偶尔复位。换到后面板原生 USB 口,问题消失。这种坑,你不做换机排除,光看代码永远找不到原因。
提示:换机排除不是“瞎换”,而是通过逐渐缩小链路范围来确定故障边界。每次只换一个环节,不要同时换一堆东西,否则你无法判断是谁生效。
3. 蓝牙断开的录屏取证
3.1 蓝牙“偶发断连”为什么说不清
蓝牙的偶发断连比串口更让人头疼。串口至少还有线连着,你还能拿示波器去量波形;蓝牙是无线链路,断开之后一点痕迹都不留。客户报障说“蓝牙键盘用着用着一会儿就断开”,你问“多久断一次”“断的时候在干什么”“手机还是电脑端”,对方也说不清楚。这种时候,单纯靠人去盯现场是不现实的,指望靠记忆去复述更不现实。
我自己的做法是“录屏取证”。核心思路很简单:让现场环境自己留下证据。在电脑上架一个录屏工具,连续录制整个操作过程,等到问题复现后,通过录屏回放去定位断连的时间点、当时的界面状态、蓝牙指示灯状态等等。这个方法听上去简单,但真正做起来有几个细节需要注意。
3.2 录屏工具的选择与配置
录屏工具不需要多花哨,能长时间稳定录制就行。我平时主要用两种:一个是 OCam,一个是 ShareX。OCam 的好处是界面简单、编码效率高,适合长时间录制;ShareX 的好处是开源免费、支持自定义区域录制,而且能跟截图、文件上传联动。两者都能输出 mp4 格式,体积可控。
配置上有几个关键参数,直接关系到取证效果:
- 码率不要设得太低。视频码率建议 8Mbps 以上,否则蓝牙断开瞬间屏幕上可能出现的细微提示文字会糊掉。
- 帧率至少 15fps,建议 30fps。蓝牙断开、重连这个过程往往只有几秒钟,帧率低了看不到中间状态。
- 关闭麦克风录音。录屏取证要的是画面和系统声音,环境噪音反而干扰后期分析。
- 录制时长和磁盘空间要做预估。一个 1080p 30fps 的视频,每小时大约 1~2GB。建议录制前清空磁盘,或者录制完及时转存。
3.3 录屏 + 日志联动抓取
光有录屏还不够,我一般在录屏的同时,把系统蓝牙日志也打开。Windows 上可以用事件查看器里的 Bluetooth 日志,Android 上可以开开发者选项里的蓝牙 HCI 日志,这样录屏负责记录“表面现象”,日志负责记录“底层事件”,两者一对应,很多问题就清楚了。
实际操作流程是这样的:
- 启动录屏软件,选择录制区域,开始录制。
- 同时开启蓝牙 HCI 日志或系统日志抓取。
- 让测试人员或自己正常使用设备,尽量复现偶发断连。过程中不要手动去开关蓝牙,让它自然断。
- 断连发生后,停止录屏,保存日志。
- 回放录屏,找到断连的时间点;再回到日志里查看同一时间段的蓝牙事件。
我在一次排查 HC05 蓝牙模块连接不上的问题时就用了这个方法。同事反馈模块连上一会儿就断,每次断开的时间点毫无规律。通过录屏回放发现,断开前的瞬间屏幕右下角都会弹出一个 USB 设备断开连接的提示,才意识到 HC05 模块是用 USB 转 TTL 供电的,而那个 USB 转 TTL 芯片因为供电不足会偶发性掉电,蓝牙自然跟着断。这种问题,单靠蓝牙日志是查不出来的,必须录屏才能发现那个一闪而过的 USB 提示。
3.4 从录屏证据中能读到什么
录屏取证最大的价值,除了帮你定位问题,还能帮你“证明”问题不是出在你的设备上。很多时候客户报障,你的第一反应是对方操作姿势不对,但你不能直接这么说。录屏回放能客观还原整个过程的每一个细节:是用户主动切换了蓝牙设备导致的断开,还是系统的省电策略把蓝牙休眠了,还是设备本身确实异常重启了。有了视频证据,内部讨论、跟客户沟通都会轻松很多。
另外,录屏取证还有一个隐藏好处:它能帮你发现那些平时没注意到的“环境变量”。比如断开瞬间是不是有某个软件恰好弹窗抢占焦点,是不是系统刚好在后台运行了某个更新任务,这些在做完录屏回放之前,你是完全无感的。
提示:录屏取证的关键不是“录到画面”,而是“记录问题发生的上下文”。断连前 10 秒的画面往往比断开瞬间更重要。
4. “新旧批次对照”的烧录排查
4.1 同一固件、两批板子,结果不一样
第三种典型场景是烧录相关。明明代码没改、编译环境没动、烧录步骤也一样,上一批板子烧进去跑得好好的,新一批板子烧进去要么起不来,要么运行过程中随机死机。这种问题最容易让人抓狂,因为你找不到任何逻辑上的“改动点”,所有变量看起来都是一样的。
这里就要用到“新旧批次对照”的排查思路。说白了就是找两块板子:一块是以前跑得好好的老批次,一块是现在出问题的新批次。然后拿同一个固件、同一台烧录器、同一个 IDE 工程,分别烧进去对比现象。这步看似简单,但实际操作中有几个变量是容易被忽略的。
4.2 烧录环节的差异可能比你想象的更隐蔽
烧录看似是个“一键完成”的操作,实际上涉及很多环节:烧录器的型号和固件版本、烧录软件(比如 Keil、J-Flash、IAR)的版本、烧录方式(比如 SWD、JTAG、串口 ISP)、烧录地址、时钟配置、Flash 算法,甚至烧录时 USB 线的供电质量。
我遇到过一次印象很深的案例。用 JLink 给一批 STM32 板子烧录固件,老批次一切正常,新批次烧录完成后偶尔出现程序不启动的现象。反复排查后发现,新批次板子上的复位电路电容值变了,导致 JLink 烧录完成后复位时序不匹配,程序没有被正确复位启动。这个问题不去对比新旧批次板子的硬件细节,光看烧录日志是发现不了的。换了烧录器复位方式、延迟复位时间,问题解决。
还有一次情况类似,但根因完全不同。一个客户用 ESP32 模块做产品,新批次模块烧录 ESPHome 或自定义固件后,Wi-Fi/蓝牙功能时好时坏。用新旧批次对照法,把老批次模块和新批次模块放一起烧录同一个固件,发现新批次模块烧录时需要不同的 Flash 频率或模式(比如 DIO 换成 QIO),否则启动后射频部分不稳定。问题根源是 Flash 芯片型号变化、烧录算法不一致,并不是双方谁的责任,单纯属于批次差异带来的调试适配需求。
4.3 对照实验的正确姿势:一次只改一个变量
做新旧批次对照,最大的忌讳是“同时对比太多东西”。如果新批次板子不仅改了 Flash 型号,还换了晶振,还改了 PCB 布局,你会发现做对比实验根本没法定位问题。正确的做法是:
- 第一步:老批次板子 + 旧固件,确认能正常运行,作为基准。
- 第二步:老批次板子 + 新固件,确认固件本身没有退化。
- 第三步:新批次板子 + 旧固件,确认是不是硬件批次差异导致的问题。
- 如果第三步有问题,说明问题出在硬件批次差异;如果第三步没问题、第四步新批次板子加新固件出问题,那就要怀疑固件跟新硬件的兼容性。
这套流程里,核心是三个字——控制变量。每一轮只改动一个环节,然后明确记录现象是否变化。我把这些现象记录在一个表格里,每一轮对照都追加一行,几个小时后基本上就能把问题定位到某一个具体变量上。
另外还有一个容易被忽略的点:烧录器本身的兼容性。比如 J-Link 的驱动版本、Keil 的 pack 包版本,甚至不同批次的芯片内部 Flash 算法略有差异,都可能导致烧录行为不同。当你做对照实验的时候,尽量固定同一台烧录器、同一个软件版本,否则你对比出来的“差异”可能只是工具差异。
注意:烧录排查时,不要只盯着烧录成功与否。烧录成功的日志只代表 Flash 写入完成,不代表程序能正常启动。用“烧录成功”作为合格标准,会漏掉很多启动层面的偶发问题。
4.4 烧录失败类问题同样适用对照法
除了启动异常,烧录本身失败的问题也很常见。Keil5 报烧录失败、JLink 连接不稳定、串口 ISP 烧录到一半卡死,这些看着像工具问题,其实也可以用新旧批次对照法来排查。比如拿老批次板子试同一个烧录指令,老批次能成功、新批次失败,那问题大概率在硬件;两个批次都失败,则优先检查工具链配置。
我见过一个很典型的案例,用串口给 Arduino Uno 烧录引导程序,烧录到一半总是失败。老批次板子没事,新批次板子必现。最后发现是新批次板子的自动复位电路上,电容容值和复位时间不匹配,导致烧录过程中 DTR 信号控制复位时序不对。这个案例里,如果你只盯着烧录软件配置,哪怕调一百遍参数也解决不了问题;必须通过“新旧比对”把视线引到硬件差异上,才能真正找到出口。
5. 把三招串起来:一个完整的偶发 bug 排查流程
换机排除、录屏取证、新旧批次对照,这三招不是孤立的。现实中的问题往往不按单一类型出现,很可能串口、蓝牙、烧录的问题交织在一起。这时候你需要一个通用的排查步骤。
5.1 排查前先做“环境快照”
无论问题多诡异,开始排查之前,先把当前的软硬件版本信息记录下来:电脑型号、操作系统版本、IDE 版本、编译器版本、烧录器型号和固件版本、芯片批次批号、外围模块型号。这步看似繁琐,但能帮你避免查了半天之后发现“原来是系统自动更新改了驱动”这种乌龙。
环境快照的记录形式,我建议用简单的 Markdown 或纯文本文档,按时间点逐条记录。后面每次改动一个变量,就在文档里追加一条。这个习惯帮我节省了大量重复劳动。
5.2 先复现,再定位,后修复
不管面对的是串口问题、蓝牙问题还是烧录问题,顺序永远是:先想办法复现,复现不了就上录屏、上日志,尽量获取现场证据;拿到证据后,缩小怀疑范围;锁定了范围后,用控制变量法做验证;验证通过后再修复,修复后还要回归测试几轮确认没有引入新问题。
这个过程没有捷径。偶发 bug 排查本来就是个体力活,但它也有它的浪漫:当你最终找到原因的那一刻,你会觉得之前熬的夜都值得。我也经常提醒自己,不要因为某个问题看起来很“偶发”就草率地用“运气不好”来收场,绝大多数偶发问题背后都有一个可以被定位的必然因素,只是我们还没有找到那个触发条件罢了。
5.3 建立自己的故障排查工具箱
最后说点务实的。调试偶发 bug,工具和习惯同样重要。我自己的桌子上会常备以下几样东西,遇到问题顺手就能用上:
- 至少两台电脑,一台主用一台备用,方便做换机对比。
- 两三个不同品牌的 USB 转串口模块,备着交叉验证。
- 一根质量靠得住、供电充足的 USB 线,并标记好日期,避免拿到劣质线浪费时间。
- 至少一款录屏软件(OCam 或 ShareX),保证随时能打开录制。
- 一个专门记录调试日志的文档模板,包括日期、环境、现象、改动项、结果。
- 每块板子贴好批次标签,这个习惯在“新旧批次对照”时能省掉很多麻烦。
5.4 回归测试不能省
找到原因、修好之后,千万别急着收工。偶发问题的根因往往是概率性的,修复方案是否真正有效,需要经过一定时长的回归验证才能确认。比如串口假故障修复后,建议连续跑 24 小时以上的压力通信测试;蓝牙断连修复后,建议连续开关设备、频繁连接断开至少几十轮;烧录问题修复后,建议连续烧录多片板子确认稳定。
回归测试的结果也要记录在排查文档里,作为后续问题回溯的依据。这不仅是工程素养的问题,更是对自己工作时间的一种保护。今天不记录,三个月后同样的偶发问题再出现,你又要从零开始查一遍。
6. 我的个人总结与建议
做了这么多年开发和调试,我最大的体会是:偶发 bug 不是靠“聪明”解决的,而是靠“方法”解决的。聪明可能会让你更快地想到某些可能性,但真正让你从一团乱麻中走出来的,是老老实实的证据采集、变量控制和对比验证。
我也越来越习惯用“最笨”的方式来处理疑难问题:先录屏、先记录、先做对照实验,而不是先改代码。有很多次,我改了十几行代码觉得终于修好了,结果第二天问题照旧;反而是耐下心来做了一轮换机对比,才发现在某个不起眼的环节上有一个硬件差异在作祟。
如果你手上正好有一个让你头疼的偶发问题,我建议你照着这篇文章里的方法试一轮。先做环境快照,再搭好录屏和日志工具,然后按控制变量的思路逐步排查。不要指望一口气解决,但只要你每一步都留下了可靠的证据,答案通常会在你预想不到的地方等着你。