嵌入式调试的焚决
- 引言
- 硬件排查
- 硬件排查表
- 软件调试
- 总结
引言
- 大部分人刚上手嵌入式时难免会遇到各种各样莫名其妙的bug,而怎么解决bug又成为一个问题,大部分人都是想到哪里就去调整到哪里,没有任何的明确方法,这样就会导致一个bug就会浪费大量的时间来解决,反而得不偿失了,接下就让我带你真正的走进嵌入式的调试
硬件排查
- 为什么要把硬件问题放在第一位,而不是先去排查bug的常发生地—软件呢?
- 因为在嵌入式开发里,很多“像软件 Bug 的问题”,其实最后都是硬件问题,如果硬件不通,任你怎么去修改软件都是徒劳,因此必须先确定硬件可用,再去排查软件
- 那怎么排查硬件问题最为高效,实用呢?答案是确定一个最小Demo,“最小 Demo”就是把系统缩减到只验证一个最基本功能的最简单程序。先看看硬件能不能跑通再把问题转移到软件上
硬件排查表
| GPIO / LED | 初始化一个 GPIO 输出,让 LED 以 500ms 周期翻转 | 验证主控是否正常运行、时钟是否基本正常、GPIO 配置是否有效 | 供电异常、时钟未起振、引脚接错、GPIO 复用错误、LED 极性理解错误 |
| 串口 UART 发送 | 初始化 UART,循环发送 "hello\\r\\n" | 验证串口基础发送链路是否正常 | TX/RX 接反、波特率错误、串口时钟未开、引脚复用错误、串口工具配置错误 |
| 串口 UART 接收 | 上位机发送固定字符串,单片机接收后原样回发 | 验证接收中断/DMA/轮询链路是否正常 | RX 引脚接错、中断未开、DMA 配置错、缓存区配置错误、协议层误判 |
| I2C 扫描 | 编写 I2C 扫描程序,扫描总线上的设备地址 | 验证 I2C 总线是否通、目标器件是否应答 | SDA/SCL 接反、上拉电阻缺失、时序不对、电平不匹配、器件未上电 |
| SPI 回环/设备通信 | 发送固定命令或读芯片 ID | 验证 SPI 时钟、片选、数据线是否正常 | CS 没拉对、CLK/MOSI/MISO 接错、模式 CPOL/CPHA 不匹配、器件未初始化 |
| 屏幕显示 | 初始化屏幕并全屏填充红/绿/蓝三色 | 验证 SPI/并口屏幕通信链路和初始化流程是否基本可用 | 供电异常、复位脚异常、背光未开、初始化命令错误、屏幕接口接错 |
| ADC 采样 | 周期性采样一个固定电压并通过串口打印数值 | 验证 ADC 通道配置和采样链路是否正常 | 通道选错、参考电压异常、输入悬空、采样时间不够、引脚复用错误 |
| PWM 输出 | 输出固定占空比 PWM,用示波器测量波形 | 验证定时器和 PWM 输出链路是否正常 | 定时器时钟未开、通道配置错误、引脚复用错误、频率计算错误 |
| 外部中断 EXTI | 按键触发中断,在中断中翻转 LED 或打印日志 | 验证输入引脚、中断配置、NVIC 配置是否正常 | 上拉/下拉错误、触发沿配置错误、中断线映射错误、抖动影响 |
| 定时器中断 | 配置定时器周期中断,在回调里翻转 LED | 验证定时器与中断系统是否正常 | 预分频/计数值设置错误、中断未使能、时钟源错误 |
| DMA 传输 | 使用 DMA 搬运一段固定数据,完成后置标志位 | 验证 DMA 控制器和数据搬运流程是否可用 | 通道映射错误、源/目标地址错误、长度设置错误、中断回调未开 |
| Flash 读写 | 写入固定测试数据,再读出校验 | 验证片内 Flash 擦写流程是否正常 | 地址越界、擦除粒度理解错误、写保护未解除、电源不稳 |
| 文件系统 / SD 卡 | 挂载文件系统,创建一个测试文件并写入字符串 | 验证存储设备和文件系统基础能力 | 接线问题、卡未格式化、驱动不匹配、供电不足、SPI/SDIO 配置错误 |
| 网络接口 | 仅初始化网络并 ping 通目标地址 | 验证网口/Wi-Fi 基础通信链路是否正常 | PHY 未通、IP 配置错误、网线问题、时钟/复位问题、驱动初始化失败 |
| 传感器模块 | 读取芯片 ID 或一个固定寄存器值 | 验证“主控—总线—外设”最基础的通信是否打通 | 接线错误、地址错误、芯片未上电、时序不匹配、器件损坏 |
软件调试
-
在进行软件调试之前,我们必须确立调试的原则,即先复现现象 → 再缩小范围 → 再提出假设 → 再设计实验验证 → 最后定位根因,软件调试最忌讳靠猜,一次改很多地方,看到现象就直接下结论。
-
首先我们需要控制变量,简化问题很多人调试失败,不是因为不会看代码,而是因为调试环境太复杂,Bug 根本不稳定。Bug 一会儿出现,一会儿消失,你做任何修改都没有参考意义,因此我们要尽量简化调试的环境,可按如下步骤
- 排除无关硬件。可以问自己为了复现这个 Bug,哪些硬件是必须存在的?那么就可以去掉那些没有必要的硬件来减少干扰
- 排除无关代码。尽可能的不跑整个应用,单独新建工程,只跑可能出现问题的代码
- 简单输入,观察输出。我们可以直接给人固定的数据,先看看程序能不能先跑起来,再逐渐增加复制度
-
其次嵌入式调试有时候是反向排查的,因为有时候代码太复杂,正向进行思考难度很大,而此时更有效的方法则是最终错误现象倒推回去,看看需要哪些条件才会出现这种现象,这就是反向排查
- 以串口接收回调没进为例:我们可以这样思考
#mermaid-svg-xSq0Gh9RwqjtzonB{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-xSq0Gh9RwqjtzonB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xSq0Gh9RwqjtzonB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xSq0Gh9RwqjtzonB .error-icon{fill:#552222;}#mermaid-svg-xSq0Gh9RwqjtzonB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xSq0Gh9RwqjtzonB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xSq0Gh9RwqjtzonB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xSq0Gh9RwqjtzonB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xSq0Gh9RwqjtzonB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xSq0Gh9RwqjtzonB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xSq0Gh9RwqjtzonB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xSq0Gh9RwqjtzonB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xSq0Gh9RwqjtzonB .marker.cross{stroke:#333333;}#mermaid-svg-xSq0Gh9RwqjtzonB svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xSq0Gh9RwqjtzonB p{margin:0;}#mermaid-svg-xSq0Gh9RwqjtzonB .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-xSq0Gh9RwqjtzonB .cluster-label text{fill:#333;}#mermaid-svg-xSq0Gh9RwqjtzonB .cluster-label span{color:#333;}#mermaid-svg-xSq0Gh9RwqjtzonB .cluster-label span p{background-color:transparent;}#mermaid-svg-xSq0Gh9RwqjtzonB .label text,#mermaid-svg-xSq0Gh9RwqjtzonB span{fill:#333;color:#333;}#mermaid-svg-xSq0Gh9RwqjtzonB .node rect,#mermaid-svg-xSq0Gh9RwqjtzonB .node circle,#mermaid-svg-xSq0Gh9RwqjtzonB .node ellipse,#mermaid-svg-xSq0Gh9RwqjtzonB .node polygon,#mermaid-svg-xSq0Gh9RwqjtzonB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-xSq0Gh9RwqjtzonB .rough-node .label text,#mermaid-svg-xSq0Gh9RwqjtzonB .node .label text,#mermaid-svg-xSq0Gh9RwqjtzonB .image-shape .label,#mermaid-svg-xSq0Gh9RwqjtzonB .icon-shape .label{text-anchor:middle;}#mermaid-svg-xSq0Gh9RwqjtzonB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-xSq0Gh9RwqjtzonB .rough-node .label,#mermaid-svg-xSq0Gh9RwqjtzonB .node .label,#mermaid-svg-xSq0Gh9RwqjtzonB .image-shape .label,#mermaid-svg-xSq0Gh9RwqjtzonB .icon-shape .label{text-align:center;}#mermaid-svg-xSq0Gh9RwqjtzonB .node.clickable{cursor:pointer;}#mermaid-svg-xSq0Gh9RwqjtzonB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-xSq0Gh9RwqjtzonB .arrowheadPath{fill:#333333;}#mermaid-svg-xSq0Gh9RwqjtzonB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-xSq0Gh9RwqjtzonB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-xSq0Gh9RwqjtzonB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xSq0Gh9RwqjtzonB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-xSq0Gh9RwqjtzonB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xSq0Gh9RwqjtzonB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-xSq0Gh9RwqjtzonB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-xSq0Gh9RwqjtzonB .cluster text{fill:#333;}#mermaid-svg-xSq0Gh9RwqjtzonB .cluster span{color:#333;}#mermaid-svg-xSq0Gh9RwqjtzonB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-xSq0Gh9RwqjtzonB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-xSq0Gh9RwqjtzonB rect.text{fill:none;stroke-width:0;}#mermaid-svg-xSq0Gh9RwqjtzonB .icon-shape,#mermaid-svg-xSq0Gh9RwqjtzonB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xSq0Gh9RwqjtzonB .icon-shape p,#mermaid-svg-xSq0Gh9RwqjtzonB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-xSq0Gh9RwqjtzonB .icon-shape rect,#mermaid-svg-xSq0Gh9RwqjtzonB .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xSq0Gh9RwqjtzonB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-xSq0Gh9RwqjtzonB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-xSq0Gh9RwqjtzonB :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
回调为什么没进?
中断有没有触发?
中断使能有没有开?
外设是否真的收到数据?
RX 引脚是否有波形?
上位机是否真的发了数据?
- 接着我们可以设计一个实验。很多人调试时常常是改一点,然后烧进去,看看行不行,不行再继续乱改,这是十分低效而看运气的,真正有效的是,每次修改代码之前都知道你的目的是什么。可以按照下面的格式进行思考 我怀疑是 X 导致了问题。 如果我做 Y,那么应该观察到 Z。 如果出现 Z,说明假设成立;如果没有出现,说明假设不成立。
- 调试手段,在嵌入式学习中,我们不能仅仅只了解ST_Link中这种简单的调试工具,而应该学会用**Jlink,Trace,离线断点(同步异常)**等,这些工具能把程序运行时那些本来看不见的东西,变成我们能看见的信息,从而帮助我们更好的解决问题
总结
- 在嵌入式学习中遇到问题时,请不要不区分软件硬件,一上来就大改代码,而是有属于你自己的调试思路,在排查硬件问题之后再思考软件问题,先设计实验进而逐步验证,一步步有条不紊的进行,这样一切问题都会迎刃而解

