☰
嵌入式偶发问题排查:串口、蓝牙与固件调试实战
2026/10/5 6:11:44 网站建设 项目流程

1. 偶发 bug 的复盘纪律:不急着改代码,先锁住变量

调试这行干久了,最怕听到的就是一句话:“这个 bug 是偶发的,刚才还在,现在又不出来了。”

这句话一出来,就意味着前面几个小时的排查动作大概率全是白做的。偶发问题真正的难度不在于“修”,而在于“复现”。你连现场都抓不住,改什么代码都是在蒙着眼睛拆弹,改完也没法验证到底是不是这个原因。

我自己踩过的坑太多了。有一回是产品端的串口偶发收不到数据,客户那边隔几天报一次,每次都是重启一下就好,换个人测试就死活复现不了。后来折腾了大半个月,最后发现不是代码逻辑问题,而是 USB 转串口线在特定电脑上供电不足导致电平不稳。你看,方向偏了,时间全浪费在“猜”上。

所以我现在处理偶发 bug,第一反应不是开调试器,而是先做三件事:建时间线、锁环境差、留证据快照。

1.1 偶发问题最难的不是修,而是复现

复现偶发 bug 有一个基本共识:它不是完全随机,一定有个触发条件,只是这个条件平时很难碰到。这个条件可能是某个温度区间、某路供电电压的波动、某个外设中断来得太密集、某条指令执行到了特定时序,甚至只是某根线接触不良的抖动。

我一般会让反馈问题的人回答四个问题:

  1. 第一次出现是什么时候?最近一次出现又是什么时候?中间隔了多久?
  2. 出现时是在做什么操作?待机、通信、刷机、还是批量数据处理?
  3. 最近有没有动过硬件?换过模块、改过线序、更新过工具?
  4. 现场能不能留下点东西?截图、录屏、日志、串口输出,什么都有用。

这四个问题卡住任何一个,后面的排查就很容易走弯路。尤其是“最近有没有动过硬件”这条,很多人默认排除,但实际上一大半偶发问题的根子就在这。

1.2 现场信息的三件套:时间线、环境差异、证据快照

我在项目里会强制自己养成一套“现场信息三件套”的习惯,每次都按这个顺序来收集,不跳步:

信息类型具体内容为什么重要
时间线首次出现、频次、持续时长、恢复方式判断问题是瞬态还是稳态,是否与定时任务相关
环境差异当时用的电脑、电源、线材、固件版本、周边设备锁定变量,排除“换了一个条件就变了”的情况
证据快照录屏、串口日志、寄存器读数、拍照记录接线事后复盘有据可依,不靠记忆

这套方法最直接的价值就是逼着你把“偶发”变成“可描述”,把“模糊”变成“可查”。等到信息收集全了,你往往已经能圈出两到三个可疑变量,剩下的就是逐一排除。

1.3 一个值得养成的习惯:改动前先归档

这里我必须多说一句:每一次准备动手修改之前,先把当前能正常工作的版本完整归档。这不是废话,而是很多人到了紧急时刻根本想不起来做。

有一次我调 ESP32 的蓝牙连接,为了测试一个猜想改了一行初始化参数,结果改完更糟,想回去改原来的配置,发现早忘了原值是什么。那一行参数找了半天才从 Git 记录里翻出来,白白浪费了时间。

所以我现在的做法是:任何 firmware 改动之前,先在本地做一次完整的状态备份,包括编译产物、配置文件、烧录工具版本。这样不管改出了什么结果,随时能退回去对照。尤其是后面要讲到的“新旧批次对照”排查法,没有干净的归档对照,你根本没资格谈“对比”两个字。

2. 串口假故障的换机排除:换电脑有效不等于电脑坏了

串口调试应该是最常见的嵌入式开发场景了,但串口问题也是“假故障”重灾区。所谓假故障,就是现象上看着是设备坏了、芯片通信异常,实际上问题出在链路某个不起眼的环节,甚至出在调试工具和电脑的配合上。

我先说一个几乎每个人都遇到过的情形:串口调试助手打开,发指令,设备没反应,数据收不到。换一台电脑插上,测试,一切正常。第一反应往往是“原来那台电脑有问题”。这个结论不完整——换电脑有效不等于那台电脑坏了,更不代表项目里的设备没问题,它只是帮你把问题范围切掉了一块。

2.1 为什么“换台电脑”常常能治好串口假故障

我在多个项目里验证过,换电脑之后串口恢复正常的背后,往往藏着下面这些真实原因:

原因具体表现为什么换电脑就“好”了
USB 主机控制器差异老台式机前置 USB 口供电不足,设备枚举失败或丢数据换台电脑供电充足,枚举稳定
转串口芯片驱动版本不同CH340、CP2102 在不同版本驱动下,缓冲机制有差异新电脑驱动版本匹配,行为完全不同
地线电位差设备地、电脑地之间存在压差,导致电平判断错误另一台电脑接地更干净,压差被掩盖
USB 转串口线材老化线缆内部断股、屏蔽差,传输不稳定换电脑时顺手换了线,但没意识到
串口被占用残留上次程序异常退出,驱动没释放串口句柄新电脑没有历史残留信息

这个表看懂之后你就能明白,换电脑其实是一个“整体替换变量”的操作——你同时换了 USB 控制器、驱动、供电环境、接地环境、历史状态。它非常适合用来做排查,但绝不能当成最终结论。正确的做法是:确认换电脑能解决问题之后,再回头用排除法找出具体是哪个变量导致的问题。

2.2 换机只是起点:串口链路排查清单

串口链路看似简单,就是 TX、RX、GND 三根线,但实际排查起来有固定套路。我一贯的顺序如下:

  1. 先测电平。拿万用表量 TX、RX 对 GND 的电压,正常空闲状态应该是接近供电电压(3.3V 或 5V),如果量出来只有零点几伏,那大概率是芯片没正常工作或者引脚被复用。
  2. 再查接线。TX 要接到对端的 RX,RX 接对端的 TX,GND 必须共地。这个简单到不能再简单的错误,我见过新手犯,也见过老手在跳线堆里翻车。
  3. 接着排除流控。很多 USB 转串口工具默认开启了 RTS/CTS 流控,但目标板硬件上根本没接这两个脚。这时候表现就是:数据发出去收不到回包,或者收回包时断断续续。直接把流控关闭,问题可能当场消失。
  4. 然后看设备枚举。在电脑的设备管理器里确认串口号、驱动是否正常。如果枚举出来是未知设备,先别怀疑芯片,把 USB 线换一根短的再试。
  5. 最后看波特率误差。串口通信是有容错范围的,一般要求在正负百分之二以内。如果你的 MCU 用了内部 RC 时钟,和上位机设置波特率偏差稍大一点,正常温度下没问题,温度一高就可能偶发乱码和丢包。

有一次排查一个 GD32F470VET6 开发板的串口,偶发收不到数据,折腾了三天,最后发现是开发板上的 USB 转串口芯片 VCC 脚被一根杜邦线短路到了 GND,电压被拉低,电平逻辑混乱。这种问题,换几台电脑都没用,必须回到链路本身去量。

2.3 MCU 侧容易漏掉的点:DMA 与中断优先级

链路全部检查完之后,问题如果还在,那就要往 MCU 内部看了。串口收发偶发异常,常见内因有两个:DMA 配置不当和中断优先级冲突。

用 DMA 接收串口数据时,通常配合空闲中断来判断一帧数据的结束。很多人只在初始化里开了 DMA,但忘了配置 DMA 通道的优先级,导致在 CAN、定时器中断密集时,串口数据迟迟搬不走,环形缓冲被覆盖。表现出来就是偶发丢帧,频率不高,但每次一来就是一批数据里丢几个字节。

我给一个排查思路:第一,确认 DMA 接收通道确实处于开启状态,很多人初始化了 DMA 但忘了调用HAL_UART_Receive_DMA这类启动函数;第二,检查串口全局中断的抢占优先级,不要让它在中断风暴里被饿死;第三,如果用过LL库和HAL库,明确函数内部的处理差异,不要把两套机制混用。

另一类常见情况是:中断服务函数里做了耗时操作,比如打印、延时、写 Flash,导致本应很快完成的数据接收被拖慢。串口在高速率下跑起来,中断稍微慢一点,硬件接收缓冲区就溢出了。你把中断服务函数精简到只做读数据和置标志位,问题往往就消失了。

总结一句话:串口假故障的排查顺序必须是“链路 → 工具 → 芯片内部”,永远别倒过来。

3. 蓝牙断开的录屏取证:把“偶尔掉线”变成一条可复盘的时间轴

蓝牙问题比串口问题更让人头疼,因为它的现场往往不在你的工位上,而是在客户手里、用户手里、路上跑的手持设备里。蓝牙断开的偶发性非常高,而且很多人反馈的时候只会说“连不上”“总掉线”,给不出任何依据。

这时候录屏就是最好用的取证手段。别觉得录屏是“甩锅”或“留后路”,实际上录屏能把模糊问题变成一条有时间轴、有操作路径、有现象界面的完整记录,后面分析起来效率能翻几倍。

3.1 录屏录什么:状态栏、信号强度、操作路径三要素

很多人一说录屏,就是拿手机从头录到尾,录完了发现全是垃圾信息。真正有效的蓝牙故障录屏至少要包含三个要素:

要素说明作用
状态栏全程记录蓝牙图标是否消失、信号格数变化直接反映 RF 链路是否中断
信号强度开启开发者选项里显示 dBm 数值判断是信号弱导致掉线,还是信号正常但协议层异常
操作路径从打开蓝牙、配对、连接、使用到掉线的完整操作复现触发条件,确认掉线和某个动作是否相关

以 Android 为例,在开发者选项里可以勾选“显示蓝牙设备信号强度”和“蓝牙 HCI 信息采集器”。前者能在状态栏显示实时的 RSSI,后者会把蓝牙芯片收发的 HCI 数据包记录下来。录屏的同时把这两项打开,掉线前后到底发生了什么,基本就藏不住了。

我要求客户录屏的时候会说清楚:第一,不要中途暂停;第二,掉线后继续录三十秒,方便我看到重连过程;第三,如果能再截一张系统“关于蓝牙”或“蓝牙日志”的图更好。

3.2 系统日志与 HCI 抓包怎么配合

录屏是给人看的,日志是给机器挖的。单独用录屏只能看到“掉了”这个现象,要定位“为什么掉”,必须配合日志和协议层抓包。

常用的组合是这三样:

  1. Android 的 “蓝牙 HCI 信息采集器”。开启后,系统会自动生成 btsnoop 文件,导出后可以用 Wireshark 打开,直接看到 LMP、LL、ACL 层的帧。掉线原因在协议层几乎是明摆着的:是对方发了断连请求(reason code),还是本机超时判定链路丢失,都会在 HCI 包里有体现。
  2. dumpsys bluetooth_manager或厂商自带的蓝牙日志。能查到当前连接状态、休眠策略、是否被系统回收。
  3. 应用侧的代码日志。能确认掉线时应用还在做什么操作,比如正在发送音频数据还是处于后台休眠。

实际配合方法是:先将录屏的时间轴和系统日志的时间戳对齐,在录屏画面里故意做一次“看时间”的动作,比如点开系统时钟,这样后期可以准确对齐到秒级。然后在日志里从掉线时间点往前推两秒,看有没有异常状态变更。

3.3 三个典型场景的取证重点

我这几年遇到的蓝牙断开问题,翻来覆去无非三种典型情况,每种取证重点都不同。

第一种:串口透传模块掉线,比如 HC-05。这种模块出问题多半和配置有关:主从模式设错、波特率不匹配、连接间隔过大导致对端超时。取证时重点录清楚模块上的 LED 状态:快闪表示可配对,慢闪表示已连接。掉线时如果是快闪,说明连接真的断了;如果模块侧慢闪但手机侧显示断开,那多半是手机系统把蓝牙“静默回收”了。

第二种:蓝牙音频 A2DP/SCO 切换掉线。典型表现是听歌正常,一来电话就断。这时候要看 HCI 日志里是否存在 A2DP 断开后尝试切 SCO 失败的过程。很多国产蓝牙芯片在 A2DP 和 SCO 之间切换需要重新配置链路参数,参数不对就掉线。取证时要明确记录:掉线发生是在电话接通的瞬间,还是在挂断瞬间。

第三种:BLE HID 设备,比如蓝牙键盘、自拍杆。这类设备为省电会进入休眠,必然导致连接暂时断开,这是正常现象。但如果每次唤醒都要十几秒才能重连,那就是从机广播参数或主机扫描策略有问题。取证重点是把“按键唤醒后到重新输入生效”的秒数录下来,连续测三次,基本能确认问题程度。

蓝牙问题里还有一个容易被忽视的因素:信道干扰。在办公区、地铁站这种 2.4GHz 极其拥挤的环境,BLE 掉线概率会明显上升。如果用户反馈“在公司经常掉,在家里不掉”,那就先在录屏里记下环境和时间点,然后再考虑是不是需要进行跳频或调整广播信道设置。

4. “新旧批次对照”查烧录:同一份固件,新批次进不了程序

烧录问题也是嵌入式开发里的高频场景,尤其是量产阶段。最常见的一句话是:“这固件在上一批板子上随便烧,新到的板子死活连不上烧录器,J-Link 都识别不到。”

出现这种情况,很多人的第一反应是怀疑芯片坏了。但“整批芯片都坏了”在概率上非常低,更合理的解释是:这批板子和上一批之间,发生了什么你没注意到的硬件差异。

这就是“新旧批次对照”排查法的核心思路:用新旧板卡互相交叉验证,把问题定位到具体差异上。

4.1 “新旧批次对照”到底对照什么

对照不是把新旧板子摆在一起看外观,而是对照这几个维度:

对照维度具体操作目的
MCU 型号与封装确认新旧批次物料是否完全一致,包括丝印、批次号查清是否换了同系列但不同型号的芯片
引脚与原理图对比新版原理图改动、网络表差异排除板级设计变化带来的影响
烧录工具与配置确认 J-Link 固件版本、Keil 配置、烧录速率是否一致排除工具侧变量
核心供电与时钟实测新板 VCC、复位脚、晶振起振情况排查硬件工作条件差异
芯片内部状态读取 ID Code、Option Byte、读保护级别排查芯片被锁或配置异常

这四个维度对照下来,绝大多数烧录异常都能圈出范围。注意,新旧批次对照排查时最忌讳的是一上来就怀疑某一方“绝对没问题”,要保持两边条件完全可互换,结论才可信。

4.2 四组交叉实验的经典剧本

把新旧变量分开,最标准的做法是四组交叉实验:

  1. 旧板 + 旧烧录工具:确认基线可用。
  2. 旧板 + 新烧录工具:判断烧录工具是否被换坏。
  3. 新板 + 旧烧录工具:验证是不是新板子本身的问题。
  4. 新板 + 新烧录工具:复现客户的完整现场。

如果只有第 3、4 组失败,那问题几乎可以锁定在新板子硬件上。这时再针对 MCU 的供电、复位、时钟、烧录引脚逐项测量。如果第 4 组失败但第 3 组成功,那问题在烧录工具或软件配置上,优先排查驱动版本和烧录器固件。

有一次我们遇到新批次板子 J-Link 能识别内核,但一烧录就报错,用旧板测什么事都没有。交叉实验做完后,发现新旧板子的供电设计完全一样,但新板子的一颗去耦电容换了个容值更小的规格,导致烧录时内核瞬间电流拉低电压,烧录器直接掉线。这种问题不通过对照实验,单纯盯代码绝对查不出来。

4.3 新批次里最容易变的五个隐藏项

交叉实验确定是新板子问题之后,再把范围从 Board 收窄到具体原因。我做这类排查时,会优先检查下面五个点,优先级从上到下:

  1. 芯片读保护(RDP)级别。很多芯片出厂自带的 RDP 级联不一样,或者上一批贴片前被烧录过工厂测试代码并开启了读保护,新批没有正确解除。表现是:J-Link 能连上,但读取 ID 码或擦除 Flash 时被拒绝。解决路径是执行整片擦除或切换到烧录器的“unsecure”模式。
  2. Option Byte 配置。新批次的 Option Byte 可能和旧批次不同,比如把 BOOT0 引脚配成了别的功能、看门狗进了硬启动状态。表现复杂多样,但读一次目标芯片内部寄存器就能对比出来。
  3. SWD 引脚被复用。如果新版代码上电后立刻将 SWDIO/SWCLK 配成普通 GPIO,烧录器就再也连不上了。这个情况在“前一版能烧,新固件烧不了”的问题里特别常见。处理办法是烧录前让 MCU 先进入复位状态,再使用“Connect under Reset”模式连接。
  4. 供电能力不足。新批次板子如果带了更多外设,或者 DC-DC 输出电流余量不够,烧录器通过调试接口向芯片索取电流时电压就会塌陷。看现象是烧录到一半报错断开,重试偶尔成功。
  5. 晶振参数变化。更换了晶振品牌或负载电容,导致内部时钟不稳定,进而影响烧录器与芯片的同步时序。这种问题可以通过降低烧录器速率临时验证,如果降低速率后烧录稳定,就重点查时钟回路。

4.4 一次 J-Link 连不上新批次的完整复盘

分享一个我做过的实际案例。当时项目用 Keil5 + J-Link 给一批新板烧录 GD32 固件,现象是旧板秒连,新板报“Cannot connect to target”。

我按交叉实验跑了一遍,锁定是板子差异。然后量了供电,3.3V 正常,复位脚电压稳定,SWDIO/SWCLK 对应 MCU 引脚的波形在空闲时正常。最后用 J-Link Commander 尝试连接,发现设备能枚举,但读取 ID 码失败。

这时我不再反复连接,而是把两块板并排放在桌上,用万用表直接量新旧板 SWDIO 到 MCU 引脚的导通性。结果发现:新板上 MCU 的 SWDIO 引脚虚焊,探针点在测试点上正常,但内部根本没和芯片脚连通。补焊之后,一次通过。

那个批次其实只有这一块板子虚焊,但排查路径是完整的。虚焊问题在烧录故障里通常最隐蔽,因为它不像供电异常有波形可看,也不像读保护有寄存器可查,只能靠导通性测试去验证。所以每次测烧录问题,我强烈建议备一根万用表笔,把烧录引脚逐个量一遍,这个动作花不了两分钟,但能省下半天时间。

5. 我在现场养成的几个工作习惯,分享给你

说回最初的问题:偶发的 bug 怎么办?我的答案其实一直都没变——不要急着修,先让问题变得能复现、能描述、能对比。串口假故障用换机排除,蓝牙断开用录屏取证,烧录失败用新旧批次对照,本质上都在做同一件事:把模糊的偶发问题,改造成清晰的、有对照组的、可验证的问题。

这几条习惯是我这几年踩坑踩出来的,分享给同样被偶发问题折磨过的人:

  1. 排查记录永远写在纸面上或文档里,不要写在脑子里。复盘的时候,你唯一可信的参考就是当时的记录。
  2. 工具链版本一定要记录清楚。驱动版本、烧录器固件、IDE 版本,任何一个升级都可能改变行为。
  3. 同一项目里同时出现多个问题时,一次只处理一个。很多人把偶发问题越查越乱的原因,就是同时动了多根线。
  4. 怀疑“玄学”之前,先把地线、供电、接线这种最基础的东西重新检查一遍。我见过太多所谓玄学问题,最终都死在这三件事上。
  5. 不要害怕向别人求助时给对方“完整背景”。很多人报 bug 时只说现象,不说自己改过什么。学会把改动历史交代清楚,排查效率能提升一大截。

排查偶发问题,本质上就是一场和不确定性的拉锯战。你能做的不是消除所有未知,而是把未知范围一步步压缩到可控区域。希望这篇记录能帮你下次遇到类似问题时,少走几段弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询