1. 偶发bug为什么比必现bug更折磨人
做嵌入式这行时间长了,你会发现一个规律:必现的bug其实好修,因为你能复现它,能打断点、能抓波形、能一步步缩小范围。真正让人头秃的是那种"偶发"的故障——你盯着它的时候它不来,你一转身去倒杯水,回来一看日志里多了一条异常,或者设备已经死机了。
我这些年处理过的偶发问题里,串口假故障、蓝牙随机断开、烧录偶发失败这三类占了绝大多数。它们的共同特点是:单次现象不足以定位,多次现象之间又缺乏稳定的关联条件。你没法像修必现bug那样"抓现场",因为现场转瞬即逝,而且往往在你不注意的时候才出现。
这篇文章想聊的就是这类问题的处理思路。核心方法论其实就三招:串口假故障用换机排除法、蓝牙断开用录屏取证、烧录问题用新旧批次对照。听起来简单,但每一招背后都有不少细节和坑。我会把这三招拆开讲透,包括为什么这样设计、具体怎么操作、实测中会遇到什么意外,以及我从踩坑里总结出来的经验。
适合谁看?如果你正在做嵌入式开发、上位机联调、蓝牙产品测试,或者你手头正好有个"偶尔抽风"的设备让你寝食难安,那这篇内容应该能帮到你。不需要你有多深的底层功底,但至少得知道串口是什么、烧录是怎么回事,不然有些操作你会看不懂。
先说一个反直觉的结论:偶发bug的排查,重点不在于"找到原因",而在于"排除不可能"。因为偶发问题的根因往往藏在硬件、固件、上位机、线材、供电、环境干扰的交叉地带,你很难直接定位到唯一原因,但你可以通过系统性的排除,把范围压缩到足够小,小到你能针对性地验证。
2. 串口假故障的换机排除法:先分清是"真坏"还是"假坏"
2.1 什么叫串口假故障
串口假故障是我自己起的一个叫法,指的是:现象上表现为串口通信异常(丢包、乱码、无响应、间歇性断开),但根因并不在串口本身。可能是上位机的问题、可能是线材的问题、可能是供电的问题、可能是对端设备的问题,甚至可能是你电脑USB口的问题。
为什么叫"假"?因为如果你直接去查串口配置——波特率、数据位、停止位、校验位——你会发现配置完全正确,串口助手也能正常打开,但数据就是不对。这时候很多人会陷入一个误区:反复检查串口配置,反复重启串口助手,结果什么问题都解决不了。
我遇到过最典型的一个案例:某款GD32F470VET6的板子,通过串口往上位机发数据,上位机偶尔会收到乱码。一开始我以为是波特率不匹配,查了半天配置没问题;又怀疑是DMA搬运的问题,改了DMA配置还是偶发。最后换了一台电脑测试,发现乱码消失了——问题出在原电脑的USB转串口芯片上,那颗芯片在特定数据量下会丢字节。
这就是典型的串口假故障:现象在串口,根因在串口之外。
2.2 换机排除法的完整操作链路
换机排除法的核心逻辑是:用已知正常的设备替换可疑环节,观察现象是否复现。具体操作分四步:
第一步:固定变量,记录基线
在开始换机之前,你必须先做一件事:把当前的现象尽可能量化。不要只写"偶尔乱码",要记录:
- 乱码出现的频率(比如每100帧出现1-2次)
- 乱码的具体表现(是固定字节错,还是整帧错位)
- 出现乱码时的操作(比如刚上电、刚发完一大包数据、刚插拔过线)
- 使用的串口参数(波特率、数据位、停止位、校验位、流控)
- 上位机软件版本和串口助手版本
这一步很多人会跳过,觉得"我知道现象就行了"。但偶发问题的排查,基线记录是后面所有对比的基础。没有基线,你换了设备之后根本不知道现象是变好了还是没变。
第二步:替换上位机侧
先换电脑。找一台配置不同、USB转串口芯片不同的电脑,把同样的固件、同样的线材、同样的上位机软件装上去,跑同样的测试用例。
这里有个细节:USB转串口芯片的型号很关键。常见的芯片有CH340、CP2102、FT232、PL2303等,不同芯片在不同波特率下的稳定性差异很大。我实测下来,CH340在921600这种高波特率下偶发丢包的概率明显高于FT232。所以换机的时候,最好换一个USB转串口芯片型号不同的电脑,这样能更快暴露问题。
如果换了电脑之后现象消失,那基本可以确定问题在原电脑的USB转串口环节。这时候你可以进一步验证:在原电脑上换一个USB口(特别是从USB Hub换到主板直出的口),或者换一根带屏蔽的USB线,看现象是否变化。
第三步:替换线材和供电
如果换电脑没用,下一步换线。串口线(尤其是TTL转USB线)的质量参差不齐,劣质线材在长距离或高波特率下很容易出问题。
换线的时候注意两点:一是线长,尽量用短的;二是屏蔽,如果环境干扰大,用带屏蔽层的线。另外,如果设备是独立供电的,检查一下供电是否稳定——供电纹波大也会导致串口通信异常。
第四步:替换对端设备
如果换电脑、换线都没用,那问题大概率在对端设备(也就是你的目标板)上。这时候换一块同型号的板子,烧同样的固件,跑同样的测试。如果新板子没问题,那问题就在原板子的硬件上(可能是串口引脚虚焊、可能是晶振偏差、可能是电源设计余量不足)。
如果新板子也有问题,那问题就在固件或设计上,跟单块板子无关。
2.3 换机排除法的记录表格
为了让排查过程清晰,我习惯用一张表格记录每次替换的结果:
| 替换环节 | 替换前现象 | 替换后现象 | 结论 |
|---|---|---|---|
| 上位机电脑 | 每100帧乱码1-2次 | 乱码消失 | 原电脑USB转串口芯片问题 |
| 串口线 | 偶发丢包 | 现象不变 | 线材非根因 |
| 目标板 | 偶发丢包 | 新板正常 | 原板硬件问题 |
| 供电 | 偶发丢包 | 现象减轻 | 供电有影响但非唯一根因 |
这张表的好处是,你能一眼看出哪个环节是根因,哪个环节只是影响因素。偶发问题往往不是单一原因,而是多个因素叠加,表格能帮你理清主次。
2.4 一个容易忽略的坑:串口DMA的"假正常"
现在很多MCU用DMA来搬运串口数据,比如STM32、GD32系列。DMA的好处是不占CPU,但有个坑:DMA传输完成中断和串口空闲中断的配合如果没处理好,会出现"看起来正常但偶尔丢最后一字节"的现象。
这个现象特别像串口假故障,因为配置没问题、大部分数据也对,就是偶尔少一个字节。排查的时候如果你只盯着串口配置看,永远找不到原因。正确的做法是:用逻辑分析仪或者示波器抓一下TX/RX引脚的实际波形,看看是MCU没发出来,还是发出来了但上位机没收到。
我踩过这个坑:当时用GD32F470的串口DMA发数据,上位机偶尔少收最后一字节。查了三天,最后发现是DMA传输完成中断里清标志的时机不对,导致最后一字节还没发完就关了DMA。改成用串口TC(传输完成)中断来判断,问题消失。
所以换机排除法里,"换机"不只是换硬件,也包括换排查手段——从"看配置"换成"看波形",往往能豁然开朗。
3. 蓝牙断开的录屏取证:让偶发问题留下证据
3.1 蓝牙断开的特殊性:为什么必须录屏
蓝牙断开和串口假故障不一样。串口问题你还能抓日志、抓波形,蓝牙断开往往是"一瞬间的事"——你看到设备断了,但等你打开日志工具,断开已经发生了,现场没了。
更麻烦的是,蓝牙断开的原因特别多:信号干扰、距离过远、配对信息丢失、固件bug、手机/电脑蓝牙栈的问题、供电波动……你没法靠"猜"来定位。
所以蓝牙偶发断开的排查,核心是取证。而最有效的取证手段,就是录屏。
为什么是录屏而不是日志?因为录屏能同时记录时间、操作、现象三个维度的信息。日志只能记录设备端或主机端单方面的信息,而录屏能让你看到"断开的那一刻,用户做了什么操作、设备指示灯什么状态、屏幕显示什么"。这些信息对于定位根因至关重要。
3.2 录屏取证的标准化流程
录屏不是随便拿手机拍一下就行,得有章法。我总结的流程是这样的:
准备阶段:
- 准备两台设备:一台是被测设备(蓝牙从机),一台是主机(手机或电脑)
- 主机上打开蓝牙日志记录功能(Android开发者选项里有"蓝牙数据包日志",iOS可以用Xcode的Console)
- 准备一个录屏工具,手机自带录屏就行,电脑可以用OBS
- 确保录屏画面里能同时看到:主机屏幕、被测设备的指示灯、时间显示
录制阶段:
- 开始录屏,然后正常操作设备,复现断开现象
- 断开发生后,不要立即停止录屏,继续录10-15秒,记录断开后的状态(比如设备是否自动重连、指示灯是否变化)
- 停止录屏,保存文件,文件名带上日期和现象描述
分析阶段:
- 回放录屏,找到断开的时间点
- 对照主机蓝牙日志,看断开前后有没有异常事件(比如HCI Disconnect、Link Loss、Authentication Failure)
- 对照设备端日志(如果有),看设备端有没有复位、重启、异常
- 记录断开时的操作和状态,作为后续复现的参考
3.3 录屏之外:蓝牙日志的抓取要点
录屏是"看现象",日志是"看原因",两者要结合。蓝牙日志的抓取有几个要点:
Android端:开发者选项里开启"启用蓝牙HCI信息收集日志",日志会保存在/sdcard/btsnoop_hci.log。这个日志是HCI层的,能看到所有蓝牙命令和事件。分析的时候重点看:
HCI_Disconnect事件:谁发起的断开,原因码是什么HCI_Connection_Complete事件:连接建立时的参数HCI_LE_Meta事件:如果是BLE,看连接参数更新
iOS端:需要用Xcode的Console或者Packet Logger。iOS的蓝牙日志相对封闭,但Packet Logger能抓到HCI层的数据。
设备端:如果你的设备固件支持日志输出(比如通过串口打印),一定要打开。设备端的日志能告诉你:是设备主动断开的,还是被动断开的;断开前有没有异常(比如内存不足、任务卡死)。
3.4 一个真实案例:杰理蓝牙的偶发断开
我之前调过一款用杰理蓝牙芯片的产品,现象是:连接手机后,偶尔会断开,断开后设备自动重启,重启后又能连上。频率大概是每天1-2次,完全随机。
用录屏+日志的方法排查:
- 录屏发现:断开时设备指示灯突然熄灭又亮起,说明设备重启了
- 设备端串口日志显示:重启前有一条"看门狗复位"的记录
- 进一步查:看门狗复位是因为某个任务卡死超过阈值
- 最终定位:蓝牙任务在特定情况下会阻塞,导致看门狗超时
如果没有录屏,我可能只会看到"设备重启了",但不知道重启前发生了什么。录屏让我看到了"指示灯熄灭又亮起"这个关键细节,才把方向引到看门狗上。
3.5 录屏取证的注意事项
录屏取证有几个坑要注意:
- 录屏会占资源:手机录屏本身会消耗CPU和内存,可能影响蓝牙性能,导致现象复现不出来。如果遇到这种情况,可以用另一台手机拍屏幕,而不是用被测手机自己录屏。
- 时间同步:录屏的时间要和日志的时间对齐。最好在录屏开始时,让设备做一个明显的动作(比如闪一下灯),作为时间基准。
- 隐私问题:录屏会拍到屏幕上的所有内容,注意不要泄露敏感信息。如果是公司项目,录屏文件要妥善保管。
- 复现频率:偶发问题可能录很久都不出现。我的经验是,如果录了30分钟还没复现,就换个时间段或换个环境再试,不要死磕。
4. 烧录排查的"新旧批次对照":把变量控制到最小
4.1 烧录失败为什么难查
烧录(下载固件到芯片)看起来是个很成熟的操作,但偶发失败特别常见。尤其是量产阶段,几百块板子里总有几块烧不进去,或者烧进去了但运行不正常。
烧录失败难查的原因在于:影响烧录的因素太多,而且很多因素是隐性的。芯片批次、PCB批次、烧录器固件版本、烧录软件版本、连接线材、供电、烧录算法、Flash芯片……任何一个环节有差异,都可能导致烧录失败。
这时候,"新旧批次对照"就是最有效的排查方法。
4.2 新旧批次对照的操作方法
核心思路:用已知能正常烧录的旧批次作为基准,对比新批次,找出差异点。
具体操作:
第一步:确认旧批次正常
找一批之前烧录正常的板子(旧批次),用同样的烧录器、同样的软件、同样的线材,烧同样的固件,确认能稳定烧录。这一步是建立基准。
第二步:新批次用完全相同的条件烧录
拿新批次的板子,用完全相同的烧录器、软件、线材、固件,进行烧录。注意,这里的关键是"完全相同",任何一点差异都可能干扰判断。
第三步:记录失败率和失败现象
统计新批次的烧录失败率,记录失败时的具体现象:
- 是连接不上芯片,还是连接上了但烧录中途失败?
- 失败时烧录软件的报错信息是什么?
- 失败是否集中在某些特定的板子上,还是随机分布?
第四步:交叉验证
如果新批次失败,做交叉验证:
- 用旧批次的板子+新批次的烧录器,看是否失败
- 用新批次的板子+旧批次的烧录器,看是否失败
- 用新批次的板子+新批次的线材,看是否失败
通过交叉验证,你能判断问题出在板子、烧录器还是线材上。
4.3 常见烧录失败原因对照表
| 失败现象 | 可能原因 | 验证方法 |
|---|---|---|
| 连接不上芯片 | 芯片虚焊、复位电路问题、烧录线太长 | 换短line、补焊芯片、检查复位引脚 |
| 连接上但烧录中途失败 | 供电不足、Flash芯片批次差异、烧录算法不匹配 | 换供电、换Flash、更新烧录算法 |
| 烧录成功但运行异常 | 固件不匹配、选项字节配置错误、晶振偏差 | 核对固件版本、检查选项字节、换晶振 |
| 偶发失败,重试能成功 | 接触不良、信号完整性差、烧录器固件bug | 换连接器、加屏蔽、升级烧录器固件 |
4.4 一个典型场景:Keil5烧录失败的批次问题
用Keil5烧录STM32或GD32的时候,偶尔会遇到"Flash Download failed"的报错。如果旧批次正常、新批次偶发失败,大概率是以下几个原因:
原因一:芯片Flash的ID或容量识别异常。不同批次的芯片,Flash的ID可能略有差异,如果Keil的烧录算法里写死了ID,就可能识别失败。解决办法是在Keil的Flash Download设置里,把"Reset and Run"勾上,或者手动指定正确的Flash算法。
原因二:选项字节(Option Bytes)被改过。有些批次的芯片出厂时选项字节配置不同,比如读保护(RDP)等级不一样。如果新批次开了读保护,烧录就会失败。解决办法是用烧录器先解除读保护,再烧录。
原因三:供电电压偏低。新批次的板子如果换了电源芯片或改了供电电路,可能导致烧录时电压不足。用万用表量一下烧录时的VDD,确保在芯片要求的范围内。
4.5 烧录排查的经验心得
做了这么多年的烧录排查,我最大的心得是:不要迷信"同一批板子都一样"。PCB批次、芯片批次、甚至同一批里的不同板子,都可能有细微差异。尤其是量产阶段,供应商换料、工艺波动,都会导致烧录表现不一致。
所以我的习惯是:每批新板子到手,先抽10块做烧录测试,记录失败率。如果失败率超过5%,就暂停量产,先排查原因。这个习惯帮我避免了好几次批量事故。
另外,烧录器和烧录软件也要定期更新。有些烧录器的固件bug会导致偶发失败,升级固件后问题就消失了。但升级之前一定要在旧批次的板子上验证,确认新固件不会引入新问题。
5. 三招背后的通用逻辑:把偶发变成可控
5.1 偶发问题的排查本质是"控制变量"
串口换机、蓝牙录屏、烧录批次对照,这三招看起来针对不同场景,但底层逻辑是一样的:控制变量,逐步缩小范围。
偶发问题的难点在于变量太多,你没法一次性定位。但你可以通过替换、对照、记录,把变量一个个固定下来,最后剩下的那个变量,就是根因。
这个思路说起来简单,做起来难。难在两点:一是要有耐心,不能急于求成;二是要有记录,不能凭记忆。我见过太多人排查偶发问题,查着查着就乱了,就是因为没有系统性地记录和对照。
5.2 建立"排查日志"的习惯
我的建议是:从项目一开始,就建立一份排查日志。不用很正式,一个Markdown文件或者Excel表格就行。每次遇到偶发问题,记录:
- 现象描述(尽量量化)
- 发生时间、环境、操作
- 已尝试的排查步骤和结果
- 下一步计划
这份日志的价值在于:当你排查了几天之后,回头看日志,能清晰地看到自己走过哪些路、排除过哪些可能。偶发问题最怕的就是"重复排查"——查了半天发现这个方向之前已经排除了,白白浪费时间。
5.3 什么时候该放弃"找根因"
说一个可能有点反直觉的观点:不是所有偶发问题都值得追根究底。
有些偶发问题的根因在供应商的芯片里、在操作系统的蓝牙栈里、在用户的使用环境里,你根本控制不了。这时候,与其死磕根因,不如做规避设计:
- 串口偶发丢包?加个校验和重传机制。
- 蓝牙偶发断开?加个自动重连逻辑。
- 烧录偶发失败?加个重试机制,失败三次再报警。
规避设计不是"逃避问题",而是工程上的务实选择。当然,前提是你已经做了足够的排查,确认根因不在自己的可控范围内。
5.4 工具和环境的准备清单
最后列一个我常用的工具清单,处理偶发问题时这些东西能帮你省很多时间:
- 逻辑分析仪:抓串口、SPI、I2C波形,比示波器方便
- USB转串口模块:多备几个不同芯片的(CH340、CP2102、FT232)
- 录屏软件:手机自带录屏、OBS
- 蓝牙日志工具:Android的btsnoop、iOS的Packet Logger
- 烧录器:至少备两个不同品牌或不同固件版本的
- 万用表:量电压、通断
- 替换用的线材:不同长度、不同屏蔽等级的串口线和USB线
这些东西平时看着占地方,但真遇到偶发问题的时候,有它们在手边,排查效率能提高好几倍。
我在实际项目里踩过的坑告诉我,偶发bug从来不是靠"灵光一现"解决的,而是靠系统性的排除和记录。串口假故障的换机排除、蓝牙断开的录屏取证、烧录的新旧批次对照,这三招你只要用熟一招,就能解决大部分偶发问题。剩下的,就是耐心和细心了。