
做嵌入式开发和硬件联调时间长了最怕遇到的就是“偶发的 bug”。功能测试跑一整天都好好的偏偏在客户现场、在给领导演示的时候冒出来串口偶尔乱码、蓝牙用着用着断开、烧录十次有两三次失败——这类问题最折磨人因为它不像必现 bug 那样有清晰的复现路径。你问它为什么它自己也说不清你盯着它看它反而不出来。今天我想分享的不是什么高深技巧而是三个我自己踩过的坑和沉淀下来的排查方法串口假故障的换机排除、蓝牙断开的录屏取证、烧录问题的“新旧批次对照”。这三个方法有一个共同点把偶发问题变成可控的、可观察的、可对比的问题剩下的就是水到渠成。1. 偶发Bug排查的第一课先把“信息不足”当成根因1.1 “偶发”到底意味着什么每一行代码、每一个信号在物理世界都有确定的原因。偶发 bug 的本质不是玄学而是触发条件太复杂、太隐蔽我们手上的信息不足以定位它。串口乱码背后可能是 USB 转串口的时序问题可能是目标板波特率误差超标也可能是电源纹波把信号毛刺打到了接收端蓝牙断连背后可能是空口调度冲突、连接参数不匹配、休眠策略把链路饿死烧录失败背后可能是供电跌落、复位时序不对、芯片进入 bootloader 的状态随机。一个 bug 如果每 100 次出现 1 次说明存在一个至少 1% 概率出现的外界干扰或状态组合。这时候你盯着屏幕等它再犯一次效率极低。更聪明的做法是提高信息密度比如用逻辑分析仪抓波形、用 HCI 日志记录蓝牙空口事件、用录屏把用户操作动作变成时间轴。我用一个生活化的比喻给刚入行的朋友讲偶发 bug 就像家里偶尔听到的滴水声白天环境噪声大你听不见凌晨安静了声音就变得很清楚。问题不在于那滴水是否存在而在于你有没有在正确的时间、正确的位置去监听。我自己排查时有个习惯不管多急先写清楚“什么条件下出现、什么条件下不出现”再动手改代码。很多时候光是记录现象这个动作就能把问题概率从 1/100 提到 1/3。因为你很快会注意到规律总是在开机后第几分钟出现总是在特定波特率下出现总是在另一台设备靠近时出现。1.2 三个案例背后共同的排查逻辑串口假故障的换机排除、蓝牙断开的录屏取证、烧录问题的“新旧批次对照”表面上是三种不同手段底层逻辑其实是同一个要么控制变量要么补充证据要么对比差异。换机排除是为了快速切分故障域是电脑的问题、线材的问题还是目标板的问题录屏取证是为了把“用户说的现象”变成我可以反复回放的现场补上日志里缺失的用户操作时间线“新旧批次对照”则是把“烧录时好时坏”这个结果拆到不同硬件版本上用批次差异来逼近根因。这也是我这几年越来越深的体会偶发 bug 排查肯定绕不开三个动作——留证据、控变量、做对照。只留证据不控变量你会被一堆假线索淹没只控变量不留证据你没法向别人证明你真的修对了只做对照不复现你连对照的基线都没有。把这三件事同时做好偶发 bug 迟早会现形。2. 串口“假故障”怎么定位换机排除法拆掉工具背的锅2.1 现场还原串口助手卡死、乱码和端口丢失先讲一个特别常见的场景。你在用串口调试助手调试一块 STM32 或者 ESP32 开发板波特率 115200。跑着跑着串口助手界面突然卡住数据停在一个奇怪的位置关掉重开又能继续工作。有时候现象更隐蔽是“十次连接有两次提示端口被占用”——设备管理器里明明没有任何程序占用可端口就是打不开。在 Linux 下则是打开/dev/ttyUSB0时报失败或者dmesg里出现和 USB 串口相关的异常。很多工程师的第一反应是怀疑固件是不是串口中断优先级配错了是不是 DMA 和空闲中断的配合有问题于是开始改代码、加超时重发、调整 FIFO。这些工作不是毫无意义但在动手改固件之前有一个更值得做的动作先确认工具链本身是否可靠。这里要单独提一下“串口 DMA”。不少项目为了提高吞吐量会在单片机上开启串口 DMA 接收。本身没有问题但 DMA 和某些 USB 虚拟串口驱动的流控机制叠加在一起会放大工具侧的偶发故障。等你查完固件回头再看会发现最初几次“卡死”根本就是 USB 转串口模块被电脑的电源管理策略挂起了。2.2 换机测试的两个关键动作交叉换口、换整机所谓换机排除不是简单地把“设备拿到另一台电脑上试试”而是要设计成一套可记录、可对比的测试。第一步先做交叉换口。把 USB 线从电脑前面板换到主板后置 USB 口或者从集线器换到主板直出接口。这个动作可以排除前面板 USB 供电不足、机箱内电磁干扰、劣质集线器这几个变量。第二步做换整机。找一台干净的电脑装最新的官方驱动用同一款串口调试助手软件、同一根 USB 线、同一个开发板连续跑半小时压力测试。如果换机后完全不出现说明问题大概率在原电脑的 USB 链路、驱动程序或电源设置上。第三步如果换机后依然偶发再换 USB 转串口线/模块。我一般会准备三种不同的模块CH340、CP2102、FT232来回来换着试。不用迷信某一款但一般来说 FT232 的驱动稳定性和抗干扰能力会好一些。测试过程建议记一张表比如测试对象测试方法观察结果初步结论原电脑 前面板 USB原配置跑 30 分钟卡死 2 次问题频率基线原电脑 后置 USB换口跑 30 分钟卡死 1 次有缓解但未根除干净电脑 同板换机跑 60 分钟0 次异常问题指向原电脑环境原电脑关闭 USB 节能改系统设置跑 120 分钟0 次异常根因锁定为省电策略这张表的意义在于每一步都有记录每一条结论都有数据支撑。排查到后面不管是自己复盘还是向同事解释都能拿得出手。2.3 真凶往往在“工具链”里USB节能与劣质线材我遇到最典型的一次“假故障”前面换机测试已经明显指向原电脑了最后发现是 Windows 的“USB 选择性暂停”设置。系统在空闲一段时间后会自动把 USB 设备挂起而串口助手打开端口时驱动已经和设备建立了状态这时候设备被系统暂停、再恢复驱动状态就乱了。表现就是串口长时间不通信第一次收发必丢数据或者端口直接不可用必须断开重连。解决办法很简单控制面板 - 电源选项 - 更改计划设置 - 更改高级电源设置 - USB 设置 - USB 选择性暂停设为“禁用”。再顺手把设备管理器里每个 USB Root Hub 的“电源管理”选项卡中“允许计算机关闭此设备以节约电源”的勾选去掉。Linux 下则可以用udevadm monitor观察插拔事件也可以写 udev 规则关闭 autosuspend。另一个藏得很深的真凶是劣质 USB 线或杜邦线。有些线材本身阻抗不稳用万用表量电阻是正常的但数据量一上来就时不时产生毛刺。这种问题换机排除特别好使你把同样一块开发板从“电脑A 线A”换到“电脑B 线A”如果还偶发再换一根线就稳定了结论立刻清晰。所以我的建议是遇到串口偶发问题先默认工具链有嫌疑用换机法把工具链排除掉再回头查固件。这能帮你省掉大量“改代码改到怀疑人生”的时间。2.4 开发阶段怎么预防串口假故障排查经验沉淀下来其实可以从源头减少踩坑。第一开发调试阶段不要省 USB 转串口模块的钱尽量选带隔离方案的模块至少选驱动稳定的主流芯片。隔离模块能阻断地环路干扰在很多噪音比较大的现场环境下有奇效。第二养成关闭系统 USB 节能的习惯。不管 Windows 还是 Linux拿到开发机第一件事就是关掉 USB 节电策略能避掉一大类“用着用着断连”的问题。第三串口调试工具不要同时开太多。尤其是安装了某些虚拟串口软件后多个工具同时抢占同一个虚拟 COM 端口会让问题变得极其诡异。我自己用串口调试助手一般只保留一个并且优先选支持时间戳显示的版本。有了时间戳日志回看时才能和操作动作对齐。3. 蓝牙用着用着就断开录屏取证还原真实现场3.1 为什么蓝牙断连特别难查蓝牙断连是典型的“偶发、难复现、主观描述模糊”三类问题的集合体。蓝牙链路是无线链路受距离、遮挡、干扰、双方蓝牙栈调度、功耗策略影响任何一个环节偶发异常都可能表现为“闪断”。更麻烦的是很多蓝牙断连并没有让设备弹出任何报错用户只会说一句“用着用着就断了”。开发时你拿一块开发板连着手机测试可能跑十分钟都正常用户拿回家放在口袋里绕一圈就断了。你问他“断开前做了什么”他大概率回答“什么都没做”。但设备不会无缘无故断开一定有什么触发条件。比如手机息屏后蓝牙芯片进入低调度的调度策略、设备端连接间隔过长导致超时、或者某个 App 在后台抢占了蓝牙权限。另外一个难点在于蓝牙日志非常不直观。Android 的 HCI snoop log 导出来是一大段十六进制你不把它和用户操作时间轴放在一起看根本不知道这段日志对应哪个场景。所以我们需要录屏录的不是屏幕本身而是一条可以对齐时间线索。3.2 录屏取证的完整流程我用 Android 手机加 BLE 设备举例流程基本通用。第一步打开开发者选项找到“蓝牙 HCI 日志”这类选项开启抓取。不同品牌名字略有差异有的叫“启用蓝牙 HCI 信息收集日志”有的藏在“调试”菜单里。第二步开启系统录屏。要求测试人员从“点击连接”开始一直录到“断开后十秒”中间不要停。关键是在操作时口述动作比如“我现在息屏了”“我打开了一个 App”“我走到房间另一头了”。这些口述会变成录屏里的时间锚点。第三步复现断开后立刻关闭 HCI 日志抓取导出日志。Android 导出的文件通常是btsnoop_hci.log用 Wireshark 打开找到Disconnect Complete事件记下断开原因码。第四步把录屏时间轴和 HCI 日志时间轴对齐。Android snoop log 自带时间戳你只需要把录屏里的“息屏动作”时间和日志里的“断开事件”时间做一下换算就能知道断开前几秒钟发生了什么。这一步是整个流程的灵魂。断开原因码含义常见触发场景0x08Connection Timeout链路长时间无数据包连接参数或睡眠策略有误0x13Remote User Terminated对端主动断开需要查对端应用逻辑0x16Connection Terminated by Local Host本机主动断开查本机蓝牙栈或 App 逻辑3.3 真实案例息屏后90秒必断我之前遇到一个 BLE 数据采集设备用户的反馈是手机息屏后放一会儿再解锁设备已经断了需要手动重连。开发那边一直复现不了因为测试人员都是亮着屏连着电源在测。后来我们按前面的流程做了一次完整取证。录屏显示断开发生前用户只是把手机息屏放到桌面上HCI 日志里的断原因是 0x08也就是 Connection Timeout。再看日志里的时间戳从息屏到断开大约是 90 秒。这 90 秒里设备侧一个数据包都没有发手机侧也没有发起任何事件。回到固件查真相很快浮出水面设备端设置了很长的连接间隔并且在空闲时进入了低功耗模式把连接事件当成无关紧要的定时器来对待而手机侧因为长时间收不到任何包按协议栈超时把链路断开了。这不是蓝牙“偶尔抽风”而是功耗策略和连接参数不匹配导致的必然结果只是触发条件恰好是“息屏”。修复也简单调整设备端的连接间隔让低功耗状态下依然保持最低频率的连接事件或者在进入睡眠前主动向手机发起断开并设计好重连逻辑。如果当时没有录屏取证的这组时间轴我们可能会在距离、天线、电磁干扰这些方向白费很多功夫。3.4 蓝牙取证中的几个延伸思路有些时候模块不自带 HCI 日志比如 HC05/HC06 这种经典蓝牙串口模块你可能只能看到 UART 上的 AT 指令和错误响应。这时候可以用逻辑分析仪去抓模块 TX/RX 引脚的电平再看断开前主控发了什么指令、模块回了什么。信号和指令一对上很多疑问就清楚了。如果你是做协议层面的排查可以找蓝牙 Core Spec 相关章节看连接事件、断连原因的说明内容虽然多但按目录找对应问题并不难。小到连接参数更新大到配对绑定流程官方文档里都有明确定义。还要注意一类容易被忽略的问题蓝牙设备名或 MAC 冲突。比如两批设备使用同一个克隆 MAC或者同一个名称手机自动连接时可能挂到另一台设备上导致原设备显示断开。录屏里如果能看到手机蓝牙扫描列表的顺序和名称这类问题往往一眼就能识别。4. 烧录时好时坏用“新旧批次对照”找出隐藏硬件变量4.1 烧录失败的现象与常见误判分享一个很典型的“烧录时好时坏”案例用 Keil5 配合 ST-Link 或者 J-Link 给 GD32、CH32、STM32 这类 MCU 下载程序编译明明没有问题点下载却偶尔报Cannot access target或Flash Download failed。有时候重复点几次又能成功有时候必须断电重来。这种问题如果只靠换机排除很容易陷入死循环换一个烧录器还是时好时坏换一根 USB 线还是时好时坏换一台电脑依旧时好时坏。到这里基本可以判断问题不在烧录器而在目标板或板子之外的供电环境。这时候“新旧批次对照”是最有效率的一招条件是你手头恰好有旧批次样机。没有旧批次就找两块当前批次中“稳定”和“不稳定”的板子做对比。4.2 新旧批次对照怎么设计先建立基线。拿旧批次板子固定烧录器、固定同一根 USB 线、固定同一台电脑、固定同一个固件 hex 文件连续烧录 10 次记录成功率。然后再拿新批次板子用完全相同的环境连续烧录 10 次。如果旧批次 10 次全过新批次只过 7 次那问题大概率是批次差异引入的硬件变量而不是烧录器和电脑的偶发状态。得到这个结论后不要急着改板子先把“批次差异”拆成更细的变量。具体可以按这三步来用示波器量新批次板子在烧录瞬间的关键波形nRST 引脚、VDD 供电、SWDIO/SWCLK 时钟线对比新旧批次的原理图、PCB 版本变更记录重点看电源去耦电容、BOOT 引脚的上下拉电阻、晶振负载电容这些和启动、烧录相关的部分如果硬件查不到明显问题再做固件交叉测试把旧固件烧到新板子上看是否仍然失败。如果旧固件在新板上稳定说明问题可能和新固件的 Flash 占用、保护配置有关。4.3 找到变量电容、Boot引脚与固件变更这里讲两个我实际遇到过的根因。第一个根因是电源去耦电容的批次变更。新批次板子因为物料到货问题换了一种 ESR 更高的陶瓷电容容量还不变。表面看规格相同但烧录时 MCU 要往 Flash 里写数据这个瞬态电流比正常跑代码时大不少新电容在瞬态下没能把电压稳住VDD 跌落超过 300mV芯片直接复位烧录器自然就报错了。示波器一量那个跌落波形非常明显。修复也简单换回低 ESR 电容或者适当降低烧录接口速度也能缓解但治本还是改物料。第二个根因是 BOOT 引脚的上拉电阻被调整。新批次板子把 BOOT0 的上拉电阻从 10K 改成了 100K抗干扰能力下降。上电瞬间BOOT0 引脚电平受电磁环境干扰有时随机拉高芯片就直接进了 bootloader导致烧录器识别不到目标芯片。后来的规避办法是“按住复位键点下载再松开复位”来提高成功率但根因还是引脚状态不确定。最后恢复 10K 上拉问题绝迹。还有一个常见变量在固件侧比如新版固件启用了读保护或者 Flash 加密旧的烧录脚本没有处理 RDP 或解锁流程就会出现“十次能成功七次”的现象。这种也要通过新旧批次对照加新旧固件交叉测试才能定位。4.4 烧录排查的其他常见坑烧录这类问题的变量非常多我把自己踩过和听同行提过的常见坑都列在这里。第一USB 线供电问题。很多烧录器是直接从 USB 取电再供给目标板的如果线材过细、过长目标板电流一大电压就掉。换一根粗短 USB 线成功率可能立刻就不一样。第二接触电阻。开发板的排针、母座在空气中氧化后尤其在冬天热胀冷缩接触电阻会变大。这种问题极其隐蔽因为每次插拔的位置不同接触质量就不同。遇到烧录偶发失败可以先换一个插拔位置或者用酒精清洁触点。第三Windows 下的杀毒软件或系统服务干扰。听起来像玄学但确实有人遇到过杀毒软件周期性扫描 USB 设备正好撞上烧录握手过程导致失败。关闭杀毒软件后成功率明显提升。这不算常规排查项但如果你把其他变量都排干净了可以留意一下。第四烧录器和 IDE 的版本记录。J-Link 固件升级前后对旧芯片的适配可能出现差异Keil5 的 DFP 版本不对也可能导致 Flash 算法异常。所以每次排查都要把烧录器版本、IDE 版本、目标板批次号、固件构建号完整记下来否则你根本不知道哪个变量变了。5. 沉淀一套自己的偶发Bug排查流程留证、控变量、做对照5.1 把三个案例的方法抽象成通用动作这三个案例本质上分别是三个通用动作的教学样本。串口的换机排除对应的是故障域切分把问题可能所在的区域分成电脑、线材、工具软件、目标板几块然后用替换法一块一块排除。蓝牙的录屏取证对应的是高密度信息采集 时间轴对齐单纯靠日志找不到用户操作触点就把录屏变成第二条时间轴。烧录的新旧批次对照对应的是批次变量对比 单变量验证把“时好时坏”拆到两个群体上再逐步拆出真正元凶。不管以后遇到的是哪种偶发 bug先问自己三个问题第一我手上有什么证据没有就补。第二我能控制哪些变量把它们逐个固定。第三有没有可对比的基线没有就制造一个。这三个问题问完排查路径基本清晰了。5.2 我的取证工具箱与“病案本”习惯给大家看看我现在常用的取证工具不一定多高端但足够覆盖大部分偶发问题。串口类支持时间戳的串口调试助手、一个稳定可靠的 USB 转串口模块、一个 24MHz 采样率的逻辑分析仪。示波器属于通用必备有条件就上。蓝牙类Android 手机的蓝牙 HCI 日志配合 Wireshark 分析嵌入式侧用逻辑分析仪抓 UART 上的 HCI 或 AT 指令如果涉及到信号强度变化可以用手机装一个蓝牙扫描器看 RSSI。烧录类J-Link Commander 命令行工具或者 OpenOCD 写一个循环烧录脚本。脚本的好处是可以连续跑 50 次自动统计成功率比自己手动点击可靠得多。我经常写一个小脚本每烧录一次记录时间戳和结果失败时打印异常输出方便回看。文件也好、脚本也好最重要的是所有采集到的数据都必须带上时间戳。没有时间戳的日志只能用来确认“发生过”无法用来回答“为什么发生”。录屏、日志、测试记录三个时间轴一旦对齐偶发问题通常都会露出马脚。另外我特别想推荐一个习惯维护一份“病案本”。每次排查完无论有没有找到根因都把现象、现场记录、怀疑列表、验证动作、最终结论写进去。这个动作麻烦不了几分钟但长期价值极高。我遇到过好几次新问题排查到一半翻到旧记录发现现象和两年前某次一模一样直接省掉一整天的摸索。5.3 给偶发Bug一份“耐心预算”排查偶发 bug最忌讳的是焦虑和瞎试。一次改一堆变量成功了也不知道是哪个动作起效失败了更惨可能把原本正常的部分改坏了。我现在的节奏是“先侦察再打仗”先花半天时间收集信息、布置记录工具、设计对照方案再动手改任何东西。前期的侦察越充分后期的修复越精准。给自己设一个“耐心预算”也很重要。一个偶发问题如果连续三个小时没有任何进展我会主动停手去睡一觉或者跑个步回来再看日志。很多时候信息就那么多一直在屏幕前死磕只会让思路越来越窄。换个状态回来反而能注意到之前忽略的时间戳、某个边缘条件。最后再分享一个小技巧我习惯在桌面留一个“排查模板”的 Markdown 文件遇到问题就复制一份里面固定字段是时间、现象、操作、观察结果、疑点、下一步验证。用不了两分钟就能填完但比任何记性都可靠。排查偶发 bug本质上是一个耐心收集证据的过程只要证据链完整它迟早会现形。