资讯动态

嵌入式调试中偶发bug的三个核心排查法:换机、取证、批次对照

发布时间:2026/10/3 6:51:39 来源:尧图企业网站定制
偶发 bug 是硬件调试里最磨人的一类问题。你连续测了一上午它都没事刚把代码包好准备提交它就在老化测试或者客户现场冒出来一次等你连上调试器想抓现场它又像什么都没发生过一样。串口偶尔丢一帧、蓝牙用着用着断开、烧录后表现不正常这类问题如果只盯着代码逻辑改很容易白白耗掉几周时间。我这些年做嵌入式软硬件调试遇到最多的并不是那种“必现”缺陷而是这种“可能一天出现一次、也可能一周都不出现的偶发问题”。面对这类问题我的经验是先不要急着改代码而是先把“链路”“证据”“差异”三个词刻在脑子里。串口假故障可以通过换机排除来确认是设备问题还是上位机问题蓝牙断开要靠录屏和日志取证才能定位是协议切换还是低功耗掉线烧录后用新旧批次对照才能区分是芯片批次差异、烧录工具差异还是固件本身的问题。这套方法看起来很笨但真的能少走很多弯路。1. 偶发 bug 的本质先怀疑链路还是先怀疑代码1.1 为什么偶发问题最费工时偶发 bug 之所以磨人核心在于三个不确定复现路径不确定、触发条件不确定、判断标准不确定。复现路径不确定就是你不知道是操作顺序、时间窗口还是外部干扰触发的触发条件不确定意味着你以为无关的变量其实才是关键判断标准不确定则是在“偶尔丢一帧”这类场景里连什么叫做“故障”都需要先定义清楚。比如“串口偶发丢包”这个问题在不同人眼里定义就完全不同。上位机开发同事觉得“数据没收到”就是问题设备端同事觉得“只要重发几次能收到就不算问题”硬件同事又可能认为“示波器上看不到波形异常就是没问题”。如果没有统一的取证和判断标准排查还没开始几个角色之间就已经在互相甩锅了。我再补充一个容易被忽略的点偶发问题的背景噪声往往来自环境变量。代码没变、硬件没换但编译服务器的依赖版本变了或者办公区的Wi-Fi信道变了就可能让问题从“两天一次”变成“一天两次”。这也是为什么先分层、先控变量比一上来就改代码重要得多。1.2 排查主线换机排除、录屏取证、新旧批次对照换机排除的逻辑很简单把可疑设备从链路中摘出去换一台已知正常的设备接入同一个环境如果故障消失说明问题在设备侧如果故障还在说明问题在环境或上位机侧。录屏取证则是为了让偶发问题“可见化”——把蓝牙断开瞬间的画面、时间点、系统状态记录下来避免“我说断了他偏说没断”的扯皮。新旧批次对照则专门针对“之前好好的这批开始出问题”的情况用两批硬件/固件做并排烧录和运行对比找出差异变量。这三招不是独立使用的。串口排查时我会先换机再考虑录证据蓝牙排查时先录屏取证再考虑换设备烧录排查时新旧批次对照几乎是必做项。1.3 先给自己定一个“故障分层”的规则我把故障粗略分成三层最底层是硬件链路中间层是协议栈或驱动最上层才是业务逻辑。串口假故障、蓝牙断开、烧录异常绝大多数都出在硬件链路和中间层而不是业务逻辑。如果一上来就打开业务代码逐行审查很容易陷入“看哪都觉得可疑、改哪里都不确定”的状态。我自己的习惯是看一眼现象先归个类如果是上电初期或者线缆插拔后出现的优先查链路如果是运行一段时间后出现的优先查驱动状态和协议栈超时如果是偶发且没有任何操作规律优先准备取证工具先记录几组现场数据再说。这个分层规则看着简单但能帮你过滤掉一半以上的无效猜测。2. 串口“假故障”设备没坏却反复出问题2.1 串口假故障的典型表现与成因先给“假故障”下个定义设备本身没有硬件损坏逻辑上也大概率没有直接bug但串口通信就是表现出异常——比如偶发丢包、首次上电收不到数据、运行一段时间后假死、上位机显示乱码等。这类问题之所以叫“假故障”是因为它往往不是你代码里的逻辑错误而是链路其他环节在捣乱。常见成因我列过一张清单顺手分享出来现象可能原因排查方向偶发丢一帧USB转串口芯片驱动自动挂起检查驱动节电设置、换USB口乱码波特率不一致或时钟偏差用示波器或逻辑分析仪实测波特率首次上电连不上设备侧DTR/RTS脚电平抖动检查上位机串口打开时的流控信号运行一段时间后假死接收FIFO溢出或DMA配置异常检查串口DMA、中断优先级换一台电脑就正常上位机串口软件/驱动不同换机排除确认环境变量我记得有一次客户反馈设备串口会“偶尔死掉”要重启才能恢复。我一开始也怀疑是驱动代码问题后来用换机排除法把设备接到另一台电脑上连续跑了两天一次都没出问题回到客户那台电脑就复现。最后查下来是那台电脑的USB转串口芯片在空闲时进入低功耗状态恢复时丢掉了一两个字节把设备侧协议帧给打乱了。这种问题改设备代码完全没用。2.2 换机排除法怎么操作才有效换机排除不是简单地把线插到另一台电脑上跑一下而是要控制变量。我的实操顺序是保持设备侧不变换一台不同的上位机电脑使用不同的串口调试助手或上位机软件观察问题是否复现。这一步排除了上位机软件和USB驱动的影响。保持上位机不变换一条全新的、带屏蔽层的串口线或USB转串口线观察问题是否复现。这一步排除了线材老化、屏蔽不良和接口接触问题。在同一个环境中换一台同型号的设备接上去观察问题是否复现。如果旧设备出问题、新设备正常说明是设备个体差异如果两台都出问题说明是链路或环境问题。条件允许时用一个串口回环头把设备的TX和RX短接让数据在设备侧自发自收。如果自发自收也有丢包问题基本锁定在设备自身如果自发自收正常问题就在外部链路。这里有个容易犯的错换机排除时把设备、线材、电脑一次性全换了结果问题消失了你仍然不知道是哪个变量导致的。正确做法是一次只换一个变量并把每次实验的结果记录成表格。我踩过这个坑曾经一口气换了三样东西问题消失结果客户后续又复现才意识到当时根本没定位到根因。2.3 串口链路检测的实操细节除了换机我还会用几个比较土但有效的办法验证串口链路。第一个是回环测试。把TX和RX短接然后用串口调试助手定时发送数据看是否全部回显。别小看这个动作它能快速区分“设备往外发数据有问题”和“设备收数据有问题”。如果是USB转串口方案回环还可以帮你看芯片本身的数据通路是否正常。第二个是用示波器或逻辑分析仪看波形。千分之二三十的波特率误差在通信上很容易被忽略但在高温或者线缆较长的时候就会变成偶发乱码。用逻辑分析仪解出实际波特率和配置的波特率比较一下误差超过2%就要考虑时钟源问题。比如GD32F470VET6这类芯片内部RC和外部晶振的精度差异在低温环境下会比较明显串口DMA加上高频中断的时候尤其容易踩坑。第三个是检查流控信号。很多硬件串口模块上的CTS/RTS并没有真正接出来但上位机软件默认打开了硬件流控双方信号对不上就会出现“有时能收有时不能收”的诡异现象。排查时先把流控全部关掉或者强制设置为无流控再看问题是否消失。我特别提醒一点别一看到串口丢包就怀疑设备侧代码。Linux下从串口接收数据丢失有可能是系统串口驱动缓冲区和应用层读取不及时导致的Windows下COM0COM虚拟串口本身也可能在转发环节丢数据。先把这些环境变量排除干净再去碰固件逻辑。2.4 几个串口场景的排查笔记串口问题在不同平台上表现差异很大我顺手记几个常见场景的排查要点。第一个是Linux环境。Linux下串口驱动默认有低延迟和调度策略但如果应用层用阻塞读而内核缓冲区又没设置大一点偶发大数据量时很容易丢包。之前调过一个项目从串口接收数据每秒钟大概几十KB用默认配置跑一会儿就丢几个字节后来把termios的VTIME和VMIN重新配置又调整了内核缓冲区大小问题就消失了。第二个是Android板子。很多Android板子做串口通讯很麻烦主要原因是系统层对串口节点权限、SELinux策略和HAL层封装各有各的要求。如果app直接读写/dev/ttyS*经常出现权限不足或者节点被占用的情况。排查这类问题不是简单改代码而是要检查init.rc的权限配置和SELinux的te规则。偶发连接不上时先看dmesg里有没有权限拒绝的记录。第三个是串口DMA场景。AT32、GD32、STM32这类芯片用串口DMA发送时如果缓冲区没有做对齐或者发送完成中断处理不及时会在数据量大时出现偶发漏发。我自己调试AT32串口DMA发送时发现把DMA缓冲区放到内部SRAM并开启内存屏障之后稳定了不少但这个细节在数据手册上通常不会特意强调。还有一类是虚拟串口场景。用com0com这类软件做串口模拟器联调时如果遇到报错先看驱动签名和位数是否匹配。虚拟串口转发环节的延迟和丢包有时候比真实串口还容易出现它适合做功能验证不适合做性能或者时序相关的测试。3. 蓝牙断开问题的证据链录屏取证是关键3.1 蓝牙偶发性断开的特点蓝牙和串口不一样它天生就有重连、切换、休眠等机制偶发断开的原因比串口更复杂。从经典蓝牙协议到低功耗蓝牙从HID人机接口设备到A2DP音频流每一类应用都有不同的断连触发条件。我见过比较多的两类场景一类是HC-05这类蓝牙模块连接不上、或者连接后一段时间自动断开另一类是手机/电脑系统里的蓝牙设备比如蓝牙键盘、蓝牙耳机用着用着就断过一会儿又能自动连回来。这两类问题看着都叫“蓝牙断开”但排查思路完全不同。HC-05这类模块断开更多是主从配置、配对信息、AT指令参数和供电问题。HC05蓝牙模块连接不上先查模块是不是还在AT模式再查模块的配对码/绑定地址是否被清掉还要查模块供电电压是否在3.3V到5V区间且电流足够。之前有个项目里HC-05在模块进入低功耗后需要拉高STATE引脚让主控知道连接状态主控没做这个处理导致显示“已连接”但实际数据不通的假象。手机/电脑系统侧断开则要优先怀疑系统的电源管理、协议栈切换和射频环境。比如Surface Pro 10这类设备蓝牙连不上很多时候是系统更新后蓝牙驱动和Wi-Fi共存冲突Ubuntu下蓝牙打不开可能是蓝牙服务没启动或固件加载失败RK3568加AP6275S的板子在鸿蒙5.1通话时蓝牙有噪声则更多和A2DP切SCO模式的时序有关。这类问题靠看代码是看不出来的必须先有现场证据。3.2 录屏取证的正确姿势与日志配合录屏取证的核心目的是把“偶发”变成“可追溯”。我总结了一个五要素记录法时间、设备状态、系统日志、操作动作、失败现象。具体操作流程打开录屏软件或直接用手机对着屏幕录像先把系统时间放大显示出来。如果条件允许尽量录制系统自带的蓝牙状态栏图标或者一个能够实时显示连接状态的测试页面。复现一次完整的“连接-断开”过程从打开蓝牙、发起配对、连接成功后开始传输数据到断开发生的时刻为止全程不要剪辑。同步抓取系统日志。Android端可以开启开发者选项里的蓝牙日志比如Realme 7这类机型打开“蓝牙HCI日志”把日志导出成文件Windows端通过事件查看器或者蓝牙抓包工具记录HCI事件Linux端用btmon或者bluetoothctl监控。记录现场环境是否有金属遮挡、是否靠近微波炉或大功率无线电设备、是否有多个蓝牙设备同时工作、蓝牙天线周围是否有USB 3.0设备。USB 3.0接口对2.4GHz频段的干扰非常明显这个细节经常被忽略。有了录屏和日志再做原因判断就有底气了。录屏至少告诉你故障确实是存在的不是测试方法的问题日志告诉你断开是设备主动断的、还是系统主动断的、还是信号丢失导致的超时。3.3 从证据推断原因HID、经典协议、A2DP/SCO拿到录屏和日志之后怎么从证据推断原因我常用的几个判断分支第一个分支看断开之后是“设备主动断”还是“主机主动断”。设备主动断常见原因是设备端休眠、看门狗复位、协议栈异常主机主动断常见原因是系统电源管理、蓝牙栈超时、配对信息失效。在HID场景里蓝牙键盘突然不能用了往往和设备的休眠策略有关比如键盘进入深度睡眠后需要重新配对而不是真正的“故障”。第二个分支看断开发生了多少次重连。如果断开后马上自动重连成功多半是信号短暂丢失或系统挂起恢复如果断开后长时间不重连多半是配对信息或者系统服务出了问题。第三个分支看业务状态。音频设备在通话过程中断音优先检查A2DP和SCO模式的切换时序。A2DP切SCO模式时如果协议栈没有在正确的时间点完成编解码器切换轻则噪声重则直接断连。之前遇到一个RK3568平台的通话噪声问题就是蓝牙驱动在SCO建立后没有及时关闭A2DP流导致的最后通过抓HCI日志里的“Set Configuration”和“Open Audio Connection”时序确认了根因。很多人以为蓝牙断开就一定是距离太远或者干扰但在实际项目里更多是协议栈、电源管理和驱动时序的问题。尤其是C#这类上层应用去和蓝牙仪表通讯时如果底层服务在连接保持策略上没处理好应用层偶发收不到数据看起来像蓝牙断开其实只是系统服务的节能策略把连接挂起了。录屏取证的价值就在于你不再需要用“我觉得”“好像”“可能”来沟通而是直接对着画面和时间线说事。3.4 模块类蓝牙与系统类蓝牙的分开对待在项目里我习惯把蓝牙问题分成“模块类”和“系统类”两桌处理。模块类主要指HC-05、ESP32蓝牙、杰理蓝牙这类嵌入式模块。排查顺序是先确认模块是否进入AT模式或透传模式再确认串口侧波特率是否和模块配置一致再检查供电电流是否满足瞬时需求。杰理蓝牙连接问题里有很大一部分是模块的默认波特率和主控不一致导致“模块已经连上了但数据没传到主控”表面看像是连接失败。系统类主要指手机、电脑、开发板系统自带的蓝牙协议栈。排查顺序是先看系统日志再看HCI日志最后才看应用层。Ubuntu蓝牙打不开先跑systemctl status bluetooth和dmesg | grep BluetoothSurface这类商用设备蓝牙连不上先看设备管理器里蓝牙和Wi-Fi是否有资源冲突必要时更新无线网卡驱动。系统类问题往往不是某一个硬件的锅而是系统服务、驱动、射频共存策略共同作用的结果。4. 烧录排查里的“新旧批次对照”4.1 新旧批次对照到底是什么“新旧批次对照”是我在烧录异常排查里最常用的一招。含义很简单拿一批已知正常的硬件和一批出问题的硬件用完全相同的烧录工具、烧录软件配置、烧录文件去烧录再对比两边的表现。这个动作看似基础但真的能分辨出很多隐蔽问题。有时候问题根本不在你的代码逻辑里而是出在烧录链路的某个环节。比如Keil5烧录失败、J-Link连续报错、IAR(一jet)烧录外部bin文件时速度异常、ESP32烧录方式选错、AT89S52用错烧录软件、GD32F470VET6被烧录软件识别成其他型号等每个环节都可能造成“烧录后表现不一致”的表象。新旧批次对照的主要目标是回答三个问题第一新批次的硬件和老批次的硬件在电气特性上是否有差异第二烧录工具、软件、配置文件是否在不知不觉中发生了变化第三固件本身是否真的拿到了正确的版本和配置。4.2 烧录环节的排查清单我整理过一份烧录排查清单每次遇到烧录异常就过一遍检查项具体内容常见坑烧录工具使用同一型号的烧录器/调试器J-Link有兼容版和正版之分速度差异很大烧录软件版本对比新旧版本配置Keil5不同小版本对芯片支持有差异烧录文件校验hex/bin的哈希值编译机器不同或编译配置变化会导致固件不同芯片型号识别确认自动识别结果GD32、STM32、CH32等型号容易被识别错误烧录速度/接口记录SWD/JTAG/SPI/I2C速度J-Link烧录SPI速度过快时容易失败供电环境测量烧录时芯片供电电压电压跌落会导致烧录失败或校验不一致烧录后复位方式确认是硬件复位还是软件复位复位方式不同启动后的行为可能不同举个例子之前有个项目用PWLink2烧录STM32固件第一批板子一切正常第二批板子烧录完成后运行不稳定。我一开始怀疑是代码改动引入的问题后来做了新旧批次对照把第一批板子用烧录第二批的固件烧一次发现第一批板子也出现了同样的不稳定。这就说明问题不是固件逻辑而是烧录烧进去的内容或者烧录后的初始化条件变了。再对比发现是第二批板子主晶振换了厂家导致启动后时钟参数不匹配属于硬件批次差异不是软件问题。还有一个很隐蔽的问题EEPROM烧录。有些项目会把校准参数烧录到EEPROM里同一份固件对不同板子读取到的EEPROM数据不同表现自然也不同。新旧批次对照时一定要把EEPROM的初始内容也纳入对比范围否则你会误以为是固件代码有bug。4.3 一个可复现的对照排查流程新旧批次对照按下面的流程走基本能覆盖大部分场景准备两台同型号硬件一台是旧批次正常板一台是新批次异常板。准备一个通用的、你知道肯定能工作的烧录工具和烧录软件。先分别用当前使用的烧录工具给两块板各烧录一次完整记录烧录界面输出的型号、容量、校验结果和耗时。把两块板都接入同一个串口或调试终端比较上电日志重点看芯片启动时的时钟初始化、寄存器默认值和外设状态。交叉烧录把正常板的固件烧到异常板上把异常板的固件烧到正常板上。如果异常板烧正常固件后表现正常说明问题在固件如果正常板烧异常固件后也出现异常说明问题在硬件或烧录配置。对每块板执行至少三次完整的烧录-运行-复位循环因为偶发问题需要反复验证。全程记录每块板的芯片丝印批次、PCB板号、固件版本、烧录软件版本、烧录速度。这些信息看着琐碎但在排查差异时非常关键。这里面最容易被忽略的是“烧录文件本身是否一致”。很多团队用CI自动编译今天和昨天的固件除了代码变化之外还可能因为编译环境、宏定义、链接脚本变化而生成不同结果。新旧批次对照前先对固件二进制做一次哈希校验确保你拿到的“旧固件”真的是旧固件。4.4 烧录场景里容易踩的隐蔽坑我多分享几个平时不太会注意的细节都是实际踩过或者帮别人排过的。第一个是烧录软件对芯片型号的自动识别。J-Link烧录SPI Flash、全志V3S串口下载、CH32X035烧录这类操作如果选错目标型号或者接口烧录器会报各种奇怪的错。有些烧录软件会从芯片里读ID但也有些只靠用户手动选一旦选错烧录过程可能“成功”但芯片启动后完全不跑。遇到这种情况新旧批次对照里第一步就应该确认型号识别结果。第二个是烧录速度对时序的影响。J-Link烧录SPI速度过快时容易失败而且失败模式还不稳定有时候连续烧三次、前两次失败第三次成功。排查时把SWD/JTAG/SPI速度调低一个档位比如从4MHz降到1MHz连续烧十次如果一次都不失败说明之前就是速度边界问题。ESP32烧录时用的是UART下载模式如果波特率设得太高加上接线稍长也会出现校验不过或者烧录完成但启动不正常的现象。第三个是分区/模式的问题。SDKManager烧录super模式、U盘烧录ISO分区这类系统级烧录需要有清晰的镜像和分区概念。如果只烧了super分区而没更新其他分区系统起来后表现不完整U盘烧录ISO分区时如果引导方式和目标平台不匹配也会出现“烧录成功但无法启动”。这类场景不太涉及“芯片固件”的调试但排查思路完全一样先把已知正常的镜像和目标硬件配对再对比异常现场。第四个是Arduino的引导程序问题。Arduino UNO给UNO板烧录引导属于AVR芯片的bootloader操作。如果熔丝位配置不对芯片可能无法再通过USB下载程序显得像变砖了。这时不要急着怀疑USB口或者驱动先检查board设置和AVRISP的熔丝位配置再用外部编程器把熔丝位改回来。这类问题在新旧批次对照时也很容易暴露因为不同批次的板子可能用了不同版本的bootloader。5. 少踩坑的排查习惯与工具建议5.1 记录问题现场的五要素排查偶发问题最怕的就是“当时没记下来事后想起来已经晚了”。我现在的习惯是任何偶发问题一旦出现立刻按五要素记录时间出现问题的准确时间精确到秒。环境硬件型号、批次、固件版本、上位机软件版本、操作系统版本。操作问题发生前最后一步操作包括供电、拔插、复位、数据收发。现象具体的失败表现比如“串口收到0xAA后停止响应”“蓝牙断开后图标消失”。日志串口日志、蓝牙HCI日志、系统事件日志能存多原始就存多原始。五要素记录好之后问题即使暂时查不出来也不会因为记忆模糊而丢失关键线索。等新旧批次对照或者换机排除时这些记录就是你分配变量的依据。我自己现在会专门维护一个“偶发问题记录表”按时间线把每次出现的问题、当时的操作、当时的日志文件路径都填进去查问题的时候快速回看。5.2 值得常备的调试工具工具不在多在于顺手。我日常排查串口、蓝牙、烧录问题常备这几样USB转串口模块选带隔离的更好避免地环路影响。手边至少备两个不同芯片方案的比如CH340和CP2102避免两个设备共用同一种驱动而互相干扰。逻辑分析仪24MHz以上采样率就能解大部分串口波形还能看时序。示波器最好双通道以上看电压跌落和波形毛刺。串口调试助手能支持定时发送、文件发送、数据统计、编码显示的。串口模拟器虽然对调试有帮助但别用它替代真实链路测试。蓝牙抓包工具USB Dongle加Wireshark或者系统HCI日志工具。蓝牙数据传输、蓝牙仪表通讯这类场景有没有HCI日志完全是两种排查体验。一个好的烧录记录模板文件名、日期、哈希值、烧录工具、烧录用例、烧录后启动日志。另外如果要调试ESP32、ESP8266-01这类模块烧录方式要特别注意。ESP32烧录方式有UART下载模式和JTAG模式选错模式会导致烧录成功但启动异常。ESP8266-01串口转WiFi模块还涉及GPIO0的上拉状态烧录时引脚状态不对会直接导致无法进入下载模式。这类“烧录过程正常但工作不正常”的问题用新旧批次对照往往最快定位。5.3 关于偶发 bug 的个人体会写了这么多我最想表达的一点是偶发 bug 并不可怕可怕的是用猜的方式去排查。换机排除、录屏取证、新旧批次对照本质都是在把“未知变量”转变成“已知变量”。我之前也犯过“改代码试运气”的错后来慢慢才理解偶发问题背后的变量往往藏在工具链和链路细节里。比如串口DMA发送时缓存区没对齐导致偶发异常这种问题不换机、不对比批次光看代码真的很难定位。ROS2 Humble下用串口桥接ESP32小车时串口读写的非阻塞模式如果没处理好也会出现“跑着跑着丢数据”的偶发情况这种时候换机排除加录屏取证往往比读十遍源码更有效。如果你现在正被某个偶发 bug 折磨我建议你从这一步开始把问题现场完整录下来把设备、线材、上位机环境都列出来然后做一次严格的换机排除或新旧批次对照。大概率你会发现真正的问题并不藏在最显眼的那行代码里而是在你从没注意过的链路上。这个思路一旦建立起来后面的调试就会顺畅很多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑