搞嵌入式或者说做硬件调试的,应该都有过这种经历:一个bug挂在测试列表里好几天,你盯着它的时候它老老实实,你一松懈它就冒出来,而且往往只出现一次。串口不过数据、蓝牙握手失败、烧录校验不通过,这类“偶发”问题最容易让人心态崩掉。因为偶发意味着两件事:复现路径不清楚,现场信息不可重复。这篇文章把三个常见场景串起来讲——串口假故障的换机排除、蓝牙断开的录屏取证、烧录阶段的新旧批次对照排查,核心思路就一条:让证据先于结论。不管是做单片机应用、产品测试,还是产线跟线,这套方法都能直接套用。
1. 偶发bug为什么难查:先分清“假故障”“证据链”和“批次差异”
偶发问题最坑的地方不是问题本身有多难,而是你根本不知道从哪里下手。同样是串口偶发不回复,可能是USB转串口芯片在特定波特率下丢包,可能是目标板供电瞬时跌落导致主控重启,也可能是对方串口助手配置错了。同样是烧录失败一次,可能是SWD线绕过了电感导致时序毛刺,也可能就是这一片芯片批次不一样,选项字默认值有差异。
我处理这类问题的原则是:不急着修,先归类。偶发bug大致能归成三类。第一类是“假故障”,设备本质是好的,问题出在工具链、连接线、环境干扰或者操作流程上,换一套外围设备就恢复正常,这类占了我碰到问题的一半以上。第二类是“真偶发”,设备确实有不稳定因素,但触发条件很苛刻,比如某个中断嵌套时序、某次供电跌落、某次蓝牙重连握手失败,需要靠录屏和日志把现场钉死。第三类是“批次性差异”,软件逻辑没变,但新做的板子或者新买的芯片某个参数和以前不一样,烧录进去的固件跑出完全不同的行为,需要新旧批次一起比对才能看出来。
对应的方法论也就三条。最小变更原则,每次只换一个变量,换线就只换线,换软件就只换软件,不要同时把芯片、杜邦线、串口助手全换掉,不然你根本不知道是哪个改动生效的。记录优先原则,遇到偶发问题先记录现场,日志、录屏、照片、版本号、时间戳,能记的都记下来,再动手做任何修改,因为偶发问题最大的敌人是重启之后一切恢复正常。对照组原则,新旧批次对比、正常设备与异常设备对比、不同烧录工具对比,通过差异化来锁定差异点,而不是对着一个问题反复猜。
这三条原则听起来简单,但实际执行的时候很多人做不到。最常见的翻车方式是:问题出现一次之后手忙脚乱地拔线重插,然后问题没再出现,就当作“可能是接触不良”过去了。结果过了两周产线又出现一次,所有先前的现场全部丢失,只能从头排查。所以我现在的习惯是,哪怕是一次性偶发,也先截屏保存日志、拍下接线照片、记录设备序列号和固件版本,再做任何拆卸动作。
2. 串口假故障的换机排除:三步定位USB转串口模块与目标板
2.1 哪些情况算“假故障”,别一上来就怀疑芯片
串口问题里,真故障和假故障的比例大约是二八开。真故障是主控芯片的串口外设本身坏了,引脚烧了,或者内部时钟配置异常,这种问题往往是持续性的,不是偶发的。假故障则是链路里的某个环节不稳定,表现成偶发现象,最常见的几种来源我列个表:
| 可疑环节 | 典型表现 | 快速判断方法 |
|---|---|---|
| USB转串口模块 | 偶发首字节丢失、乱码、数据断流 | 换另一块模块对比测试 |
| TXD/RXD接线 | 接触不良偶发不通,碰一下就好 | 焊接或更换杜邦线后测试 |
| 共地问题 | 数据收发正常但偶尔错位 | 单独接一根地线验证 |
| 供电不足 | 目标板刚上电时串口无响应 | 用外部稳压源供电测试 |
| 波特率误差 | 收发字节偶发出错,低波特率正常 | 降低波特率或测量实际波特率 |
| 串口助手配置 | 偶发不显示数据,DTR/RTS被钩选 | 恢复默认配置验证 |
假故障的“假”,不是说问题不存在,而是说责任方不在目标设备上。比如我曾经遇到过一台设备串口偶发无响应,排查了一大圈,最后发现是电脑的USB口供电波动,插到主板后置USB口之后问题消失。还有一个更典型的例子:USB转串口线用的芯片本身不稳定,在115200波特率下每隔几十秒丢一个字节,在9600波特率下完全正常,极易被误判成目标板的DMA配置有问题。
2.2 换机排除的实操流程:外围工具先行,目标板殿后
所谓的“换机排除”,不是一上来就换主控芯片。我的流程是先换最容易替换的外围设备,逐步逼近故障本体。第一步换USB转串口模块,手头至少备两到三个不同方案的模块,CH340、CP2102、FT232各留一块,出现偶发问题直接换模块测试,这一步能排除掉60%以上的链路干扰。第二步换连接线,杜邦线换排线或者直接烙铁焊线,排除接触电阻和线缆断裂的问题。第三步换调试软件,从串口助手换到另一个软件,或者用Python的pyserial写个小脚本连续收发数据,排除上位机本身的缓冲和处理逻辑问题。
走完这三步之后,如果问题还在,才开始怀疑目标板。这时候不要急着量芯片引脚,先看两块位置的关键信号。用示波器抓一下TXD引脚的空闲电平,正常应该是高电平,如果发现电平被拉低或者有异常毛刺,再往下查。再用万用表确认目标板和USB转串口模块是否共地,特别是用独立供电的板子,不共地会导致偶发性收不到数据。
我在实测中发现一个非常容易被忽略的细节:USB转串口模块插在笔记本的USB Hub上,比直接插电脑主板USB口更容易出现偶发断流。尤其是共用电源的USB Hub,当其他设备电流突变时,串口模块会瞬间掉电或丢数据。所以排查串口偶发问题时,我会把模块直接插到设备端的USB口,有条件就外接一个带屏蔽的USB线。
2.3 一次串口“假故障”的实录:换机排除的全过程记录
有一次我调一块GD32F470VET6的板子,用USB转串口模块看日志,现象是每隔一段时间上位机就停住不动,过几秒又恢复。第一次出现时我以为是目标板死机,量了供电、看了状态灯都正常,又怀疑是串口DMA和空闲中断配置有问题,把代码来回翻了两遍,甚至把串口中断优先级都改了,问题照样出现。
后来冷静下来,先换了一根USB线,故障依旧;再把模块从USB Hub换到主板后置USB口,跑了半小时没有复现;重新插回USB Hub,半小时后果然又出现了。到这里基本确定问题不在目标板,而在USB Hub的供电质量。换了一个带独立电源的Hub或者直接插主板,问题彻底消失。整个排查过程如果一开始就盯着GD32的串口配置改,怕是三天也搞不定。
所以串口偶发问题,我现在的排查顺序固定为:先怀疑链路,再怀疑工具,最后才怀疑代码和芯片。不要觉得换线换模块很“低级”,很多疑难杂症就是这么低级的原因,只是大家习惯性认为问题一定出在自己的板子上。
3. 蓝牙断开的录屏取证:先拿到现场,再谈复现和修复
3.1 为什么蓝牙断开必须录屏
蓝牙问题比串口更让人头疼,因为无线链路的干扰因素太多,而且断开往往只发生在特定环境下。设备A和手机连得好好的,到了另一个房间就断开;昨天测试没复现,今天又断一次。这种问题如果只靠事后看日志,很难还原现场。日志可以记录蓝牙协议栈的状态,但记录不了使用者当时的动作,比如用户是正在滑动界面、按下某个按键,还是单纯把设备放在桌上没动。这时候录屏就是最低成本、信息密度最高的取证手段。
录屏能同时记录三类信息:界面上的连接状态变化、操作者的实际操作行为、以及系统状态栏里的时间信息。配合蓝牙日志的时间戳,可以精确还原断开前后的操作序列。我处理过一个HC-05蓝牙模块与手机配对后偶发掉线的问题,看日志只能看到“链路已断开”这一条记录,但回看录屏发现,每次断开前用户都会切到另一个App,触发手机蓝牙栈的某种电源管理策略。如果没有录屏,这个结论几乎不可能拿到。
3.2 完整取证需要记录什么:别只录一个画面
很多人录屏就是拿手机对着屏幕录一段,实际上这种录屏质量很差。完整的蓝牙断开取证,应该同时记录几个维度的信息。手机端录屏要覆盖蓝牙开关状态、当前连接的设备名称、连接持续时间、断开后的自动重连情况;如果设备有配套的上位机App,界面上的信号强度、收发计数也要录进去。PC端则要打开系统的蓝牙日志或者厂商的日志工具,把HCI层的连接事件抓下来,同时用录屏软件记录屏幕内容。
时间对齐是关键。手机录屏的时间戳和PC日志的时间戳往往不是同一时区,或者存在几秒的偏移,取证时最好在开始时手动对一下表,比如同时打开系统设置页拍一下当前时间。我在实际处理过的一起ESP32经典蓝牙与手机偶发断开事件中,就是靠录屏里状态栏的秒数变化,把断开时间精确到秒,再去日志里找到对应的Disconnect Reason,最终定位到是手机侧在后台清理了蓝牙资源,而不是设备端主动断开。
最常见的三种蓝牙断开原因,我从日志角度做一个判读表:
| 日志特征 | 断开类型 | 常见触发场景 |
|---|---|---|
| HCI层出现Remote User Terminated Connection | 对端主动断开 | 手机蓝牙栈休眠或用户手动断开 |
| HCI层出现Connection Timeout | 链路监督超时 | 距离过远、干扰强、设备被遮挡 |
| 协议栈无错误日志但连接消失 | 本地电源或协议栈问题 | 模块供电跌落、系统休眠策略、固件bug |
| 反复出现Authentication Failure | 配对信息失效 | 对端删除配对、加密密钥丢失 |
3.3 经典蓝牙与BLE要区别对待
排查蓝牙断开问题时,一定要先确定你用的是经典蓝牙还是BLE。经典蓝牙的断连大多表现为“链路层断开”,日志里能看到明确的Disconnect原因代码;BLE的断开则要复杂得多,可能是连接参数更新失败、从机长时间没有回应事件、也可能是广播间隔和扫描窗口不匹配导致的偶发连接失败。
一个是经典蓝牙模块HC-05/HC-06的场景,这类模块使用串口透传,出现偶发断连时,除了无线链路问题,还要检查模块的AT指令配置里的绑定、认证参数。有些HC-05模块默认开启了绑定模式,重启后配对信息丢失,表现成“连接正常几秒钟然后断开”。另一个是ESP32做BLE从机时,要关注连接间隔、从机延迟这两个参数。我遇到过ESP32广播正常、手机能搜到,但连接后几秒内必断的情况,排查到最后是ESP32的休眠定时器把BLE栈的时钟给关了,触发了一次异常断开。这些如果只看应用层日志,根本看不出来,必须有协议栈级别的日志或者录屏辅助判断。
录屏取证的操作本身不难,难的是养成习惯。我的做法是:凡是蓝牙相关的偶发问题,一律先录屏,哪怕后续发现是距离太远导致的,录屏也不会白录,至少能证明断开时周围环境是什么样的。很多工程师习惯出了问题先看代码,代码看半天没头绪,回头想找现场已经没有。录屏取证其实是给这些无法复现的问题上一道“保险”。
4. “新旧批次对照”的烧录排查:烧录失败不一定是烧录器坏了
4.1 烧录问题最常见的三种伪装方式
烧录阶段的偶发失败,很多人第一反应是换一根下载线、换一个烧录器、或者重装烧录软件。这些动作有效,但只对工具类问题有效。真正难缠的是另外三种情况。
第一种是烧录地址或下载算法的问题。换了一块新批次的芯片,或者换了另一家代理的芯片,flash的页大小、扇区划分可能有细微差异,烧录软件默认的地址算法没更新,导致偶发校验错误。这种情况最像“偶发”,因为旧的片子一直没问题,新片子上线几天后才开始报错。第二种是Option Bytes配置差异。STM32这类芯片的选项字里包含了看门狗配置、读保护、BOOT地址等,新旧批次的默认值可能不一样。新批次的芯片如果默认开了读保护,烧录器连上后读不出芯片内容,误报成烧录失败。
第三种最隐蔽——硬件批次差异造成的运行异常,被误判成烧录失败。传感器批次差异导致某个引脚的默认电平不同,程序启动后进了不同的分支,表现成“烧录进去跑不通”,而实际上烧录本身是成功的。遇到这种问题,单纯反复烧录没有意义,必须把新旧两块板子放在一起对比。
4.2 新旧批次对照排查的具体操作流程
我现在处理烧录偶发失败的固定流程是四步对照法。第一步记下烧录工具和软件版本,J-Link和DAP-Link、不同版本的Keil或者烧录软件,都可能影响结果,先统一工具链再做对比。第二步读回芯片信息,包括Device ID、flash大小、选项字节配置,把这个信息存成文本,方便新旧批次对比。第三步拿一块旧批次正常板和一块新批次故障板,分别用同一个烧录工具、同一份固件、同一根下载线烧录三次,记录成功失败的情况。第四步如果新批次失败而旧批次正常,重点看两者的芯片丝印批次、选项字节和芯片内部固件版本。
这里特别要强调“同一根下载线”这个细节。我有一次排查烧录偶发失败,新旧批次各两块板子,来回测了十几次,发现新旧批次都有失败。后来换了一根新的SWD线,问题马上消失,原来那根线内部已经接触不良,但没到完全断的程度——这也是一种典型的偶发bug伪装。所以对照实验里每个变量都要严格控制,不然你得不出有效结论。
新旧批次对照时,最该先比的是固件本身,而不是硬件。把烧录器读取出来的固件和源工程重新编译的固件做一次哈希比对,如果每次烧录后读回的哈希值都不一致,说明写flash环节不稳定;如果哈希值一致但运行行为有区别,那问题基本在烧录地址、选项字节或者外部晶体/供电差异上。哈希对比这一步很多人会跳过,但它是区分“flash没写对”和“写对了但跑不对”的最快方法。
4.3 烧录排查中的常见坑:速度、供电、下载线都不容忽视
烧录软件里的下载速度选项是最容易被忽略的。默认的“自动”档位不一定适合每一块板子,特别是SWD走线比较长或者电路板上干扰比较大的时候,高速烧录就会出现偶发校验失败。我的习惯是最开始用4MHz以下的速度烧录,确认没问题再逐步调高,一旦出现偶发失败,优先降低烧录速度再试,不要一上来就怀疑芯片。
供电问题在烧录过程中的表现也经常被误判。目标板如果用烧录器供电,USB口的电流波动会导致烧录中途电压跌落,尤其在flash擦写瞬间电流较大时。解决方法是给目标板外接独立稳压电源,烧录器和目标板之间只走数据线。我在烧录STM32G0系列时遇到过同样的问题:J-Link能识别芯片,但擦除后写入老是校验失败,最后发现是USB转出的3.3V在擦写瞬间压降超过0.3V,独立供电后问题消失。
关于下载线,SWD线最好不要超过15cm,而且四根线(SWDIO、SWCLK、GND、VCC)最好是一体式的排线,不要用杜邦线散着插。杜邦线之间会引入了cross talk,高速切换时钟时非常容易干扰数据线。我见过烧录失败率高达30%的产线,最后排查原因就是一线员工贪方便用了几根长度不一致的杜邦线。把这些细节固定下来之后,烧录失败率直线下降。
排除了硬件、工具和线缆之后,再回到新旧批次本身。最常见的差异是芯片厂商在不同批次调整了默认选项字节。比如STM32F1系列的读保护默认值,老批次可能是0xFFFF,新批次换成了0x55,相当于上电就开启了读保护,烧录器当然会报错。把新芯片的选项字节读出来和老芯片对比一下,基本一眼就能看出区别。还有一个容易被忽略的是芯片丝印上的版本号,很多芯片A版本和B版本在电气特性上有微小差异,对烧录时序的影响也不同,但这种差异通常只有读回Device ID或版本寄存器才能发现。
5. 处理偶发bug的通用记录模板与经验清单
5.1 一套可以直接抄的排查记录模板
排查偶发问题,最重要的不是技巧,而是记录习惯。我给自己定的规矩是:每次处理一个偶发bug,必须填完下面的记录再动手修,哪怕最后发现是小问题也要填完。这个模板帮我省下了大量重复排查的时间。
| 记录项 | 内容示例 | 填写理由 |
|---|---|---|
| 问题现象 | 串口偶发不回复,间隔30分钟到2小时不等 | 明确现象,避免事后记忆偏差 |
| 复现频率 | 3小时内出现2次 | 评估问题严重程度 |
| 环境信息 | 室温25℃,USB Hub供电,串口线长1.5m | 排查外部干扰因素 |
| 设备信息 | 硬件版本V1.2,固件版本0.5.1,芯片批次N78 | 为批次对比做备份 |
| 工具信息 | USB转串口CH340,软件v1.0,波特率115200 | 排查工具链因素 |
| 操作动作 | 断开后重新插拔USB后恢复 | 建立复现路径 |
| 日志/录屏 | 见附件20240115_串口日志.txt | 保留原始证据 |
| 初步结论阶段 | 待排查 | 随时更新,避免忘记 |
这套模板看起来琐碎,但在处理那种“一周出现一次”的问题时特别有用。每个字段都是一条可能的排查线索,你永远不知道最终问题的根源藏在哪一行里。有一次我排查蓝牙断开问题,所有技术手段都用上了,最后发现设备信息和工具信息里记录的蓝牙适配器型号不一样,两台电脑内置的蓝牙芯片版本不同,导致连接参数协商结果不同。如果没有记录环境信息的习惯,这种结论根本推不出来。
5.2 我现在处理偶发bug的几条土经验
土经验一:出了问题先截图、先录屏、先保存日志,再碰任何硬件。不要相信自己的记忆力,偶发问题的现场只能存一次,重启之后就再也拿不回来了。
土经验二:每次只做一项变更。换线就只换线,换软件就只换软件。同时换掉三个变量,问题好了你也不知道是哪个起的效,问题没好了你更不知道是哪个没起作用。
土经验三:给每台出问题的设备贴“隔离贴纸”,贴上之后不许别人再动。偶发问题最大的敌人是四面八方伸来的手,你上午刚测出问题,下午同事拿过去烧了个新固件,现场又全没了。
土经验四:不要迷信“多试几次就好了”。偶发问题如果连续出现三次以上,大概率不是运气问题,而是有稳定的触发条件没有找到。与其反复试,不如停下来把环境变量逐项分解。
土经验五:烧录类的偶发问题,优先检查速度、供电和线缆这三个固定项,其次才是芯片和软件配置。不要一上来就怀疑烧录器坏了,烧录器在多数情况下都是背锅侠。
把这些经验串起来看,偶发bug的排查本质上就是一个不断缩小嫌疑人范围的过程。串口假故障靠换机排除快速筛查外围,蓝牙断开靠录屏取证锁死现场,烧录问题靠新旧批次对照区分软件和硬件的差异。这三条路径背后共同的东西,是对现场证据的尊重和对排查纪律的坚持。下次再遇到“偶发的bug”,先把这句话默念一遍:证据先于结论,每次只动一个变量。