ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

MCU片上调试与硬仿真实战:SWD连接、断点与故障排查

MCU片上调试与硬仿真实战:SWD连接、断点与故障排查 做 MCU 开发真正的分水岭往往不是你会不会点灯而是能不能把片上调试器和硬仿真用顺手。片上调试器是芯片内部预留的一套观测与控制通路硬仿真则是靠这套通路把程序实实在在地压在硅片上跑同时你还能随时暂停、单步、翻内存、改寄存器。串口打印能告诉你的东西太少了一旦程序在中断里跑飞、在启动阶段就卡死、或者打印函数本身还没初始化完你就只能盯着板子发呆。这篇文章面向的是刚接触 ARM Cortex-M 或国产 MCU 的开发者以及那些能用调试器但不清楚原理的老手我会把调试通道的来龙去脉、配置方法、踩坑经验一次说透让你下次遇到连不上跑飞了被锁死这三个经典场景时手里有牌可打。1. 片上调试器和硬仿真先弄清它到底替你解决了什么1.1 从串口打日志猜问题到直接看现场我刚入行那会儿调试手段基本就是串口加 LED。程序跑飞了就加一句打印看一下走到哪一步然后二分法一段一段注释掉靠时间换答案。这种方法在逻辑简单的时候还行一旦遇到中断嵌套、DMA 搬运、启动阶段 HardFault 这类问题就会发现一个尴尬的事实故障发生的那一刻你根本来不及打印任何东西等你把日志补上现场早没了。片上调试器改变的就是这一点。它不走你写的任何代码而是走芯片内部一条独立的硬件通路。CPU 在运行调试器可以通过这条通路读内存、写寄存器、设断点CPU 停下来的时候整个现场还完整保留着堆栈、PC、LR、xPSR、外设寄存器全都在。这就是所谓的硬仿真——程序跑在真实的硅片上不是模拟器里跑模型调试动作由外部硬件发起不占用 CPU 的运算资源也不依赖你的串口是否配置正确。我个人的经验是一个 MCU 项目里调试器带来的效率提升至少占三成。尤其是启动阶段的死机、时钟树配错、Flash 等待周期不够导致取指异常这些用串口根本查不出来用调试器几分钟就能定位。你不需要一上来就懂 CoreSight 架构但至少要明白调试器不是万能的它的能力边界由芯片内核版本、厂商实现和你的接线方式共同决定。1.2 硬仿真和 IDE 软件仿真的区别别混为一谈很多人第一次听到仿真两个字会想到 IDE 里的 Simulator 模式。Keil 和 IAR 都带软件仿真功能能在 PC 上模拟一部分内核行为让你在没板子的情况下跑指令。这个模式下寄存器是软件模拟的外设寄存器大多是个空壳你写个 UART 发送它不会真的有时序波形也没有中断延迟。硬仿真完全不同。你的代码编译出来烧进芯片的 Flash 或 RAM 里CPU 真的在执行时钟真的在跑外设真的在动。调试器只是挂在这条总线上旁观干预。所以硬仿真能看到的东西是软件仿真给不了的真实的 Flash 读取时序、真实的中断响应延迟、真实的总线竞争、真实的电源噪声引起的偶发故障。反过来说硬仿真的代价是你必须有一块能正常供电、能正常起振、调试口没被占用的板子。板子硬件本身有问题的时候调试器也救不了你——调试通道依赖芯片的数字电源域和复位逻辑正常工作如果电源引脚虚焊、晶振不起振导致内核根本没法运行那连接阶段就会直接失败。这也是为什么我总建议新板子第一次上电先只焊电源和最小系统别一口气全焊完。1.3 调试器、目标板、IDE 三者之间的分工理清楚就不容易懵一个完整的硬仿真链路包含三部分PC 上的 IDE 或命令行工具、中间那块调试器硬件比如常见的 ST-Link、J-Link、DAPLink 类产品、以及目标板上的 MCU。很多人出问题时不知道该怪谁就是因为不清楚三者各自负责什么。IDE 负责把用户的意图翻译成调试协议命令比如在 0x08001234 处设一个断点读取 R0 的值往 0x40021000 写 0x00000001。调试器负责把这些命令转成电气信号在 SWCLK/SWDIO 上产生符合协议的时序同时把芯片返回的数据打包回传给 PC。目标芯片内部的调试逻辑负责解析这些信号操作内核寄存器和总线。这个分工决定了排查思路连不上先看电气层线、供电、地再看协议层时钟频率、复位方式最后才怀疑芯片被保护。程序连上就跑飞那大概率是代码里把调试引脚复用掉了或者是调试器影响了低功耗状态。把这三层分清你就不会一遇到问题就盲目换调试器、换 IDE、换板子。2. 调试通道的核心细节数据是怎么走进芯片内部的2.1 DAP 两级寻址DP 和 AP 各自干什么ARM Cortex-M 系列的调试子系统叫 CoreSight核心入口是一个叫 DAP 的组件Debug Access Port。它的结构很有意思分两级第一级是 DPDebug Port负责和外部调试器握手、管理链路状态第二级是 APAccess Port挂在 DP 后面真正去访问芯片内部的总线。你可以把 DP 理解成前台接待它管的是链路通信本身比如上电后的握手、读 IDCODE 确认对方是谁、配置传输参数。AP 才是业务窗口最常见的是 AHB-AP通过它就能读写芯片的 AHB 总线进而访问内存、外设、内核寄存器。除了 AHB-AP还有 APB-AP 用于访问调试组件本身比如断点单元。为什么这个细节值得了解因为它解释了一个常见现象有时候调试器能读到芯片的 ID能识别出内核型号但一读内存就报错。这说明 DP 这一级握手是通的链路电气上没问题问题出在 AP 访问总线上——可能是总线时钟没开、芯片在复位保持状态、或者低功耗模式把总线挂起了。知道 DP 和 AP 是分开的你排查时就能顺着层级往下走而不是一上来就怀疑线接错了。2.2 SWD 和 JTAG 怎么选线速也不是越高越好SWD 和 JTAG 是两种常见的物理接口。JTAG 是老牌标准五根线TCK、TMS、TDI、TDO加上可选的 nTRST它通过一个状态机来移位数据能串联多个器件做边界扫描。SWD 是 ARM 后来推出的两线方案SWCLK 加 SWDIO双向半双工引脚占用少速度在中等频率下和 JTAG 相当。对绝大多数 Cortex-M 应用SWD 是首选。原因很实在省引脚很多小封装芯片根本腾不出五根 JTAG 线接线简单少接三根线就少三个接触不良的可能而且 SWD 在小板子上布线更容易。JTAG 的优势主要在多器件串联调试和板级测试一般产品开发用不到。关于线速我见过太多人上来就把 SWCLK 拉到 10MHz、20MHz然后抱怨连接不稳定。SWD 的时序里每一次读写都有固定的头部、校验位、turnaround 周期频率越高信号边沿越陡对走线阻抗、线长、接地质量越敏感。杜邦线拉十几厘米10MHz 下波形就已经很糟糕了。我个人习惯是调试连接问题一律先把速度降到 1MHz 甚至 500kHz连上了再逐步往上加。日常调试用 2MHz 到 4MHz 就够烧录大容量 Flash 的时候再临时调高。还有一点SWD 的两根线是同步时钟理论上不依赖目标芯片的主时钟SWCLK 由调试器提供。但注意通过 AHB-AP 访问总线的时候目标芯片的总线时钟必须在跑。如果芯片进了深度睡眠内核时钟停了调试器能维持链路但一读内存就超时。这就是低功耗调试经常失败的根本原因后面会专门讲怎么破。2.3 断点、观察点、Trace三种观测手段的能力边界很多人把断点当成一个东西其实芯片内部有好几套不同的硬件资源在做这件事能力边界差别很大。指令断点由 FPB 单元实现Flash Patch and Breakpoint。它的原理是拿内核取到的指令地址和内部比较器比对命中就产生调试事件。比较器的数量是有限的不同内核版本不一样Cortex-M3/M4 常见是 6 个代码比较器M0 只有 2 个具体数量还要看厂商实现。这就是为什么你在某些芯片上只能设两三个断点再设就提示硬件断点资源不足。观察点由 DWT 单元实现Data Watchpoint and Trace。它监听数据总线的访问可以配置成读某个地址就停写某个地址就停数量更少通常是 4 个。这在查哪个函数偷偷改了我的全局变量时特别好用比满世界加打印高效得多。软件断点则是另一回事调试器把目标地址的指令临时替换成一条断点指令命中后由调试服务接管。这种方式不消耗硬件比较器但要求那段内存可以被改写。在 Flash 里改指令意味着要擦写整页代价太大所以软件断点通常只在 RAM 里有效。Flash 里的断点基本都得靠硬件比较器。Trace 又不一样它不打断程序运行而是把执行过的 PC 值、数据访问、或者你主动插入的 ITM 消息通过 SWO 单线或者并行 Trace 口输出到调试器。优点是能看到程序实际怎么跑的缺点是带宽有限、数据量大、需要芯片支持。Cortex-M3 及以上才有 ITMM0/M0 没有这条通路也就用不了 SWO。选芯片做复杂调试时这一点值得提前确认。3. 从接线到第一次成功进入硬仿真完整实操流程3.1 接线与供电五根线里的三个关键标准 SWD 接线是五根SWCLK、SWDIO、GND、VREF、nRST。少一根可能就连不上或者连上不稳定。GND 是最容易被忽略的。调试器和目标板之间必须共地而且最好用一根短而粗的线不要指望通过 USB 或者电源适配器的那根地线。我遇到过板子用独立电源供电、调试器通过 USB 接 PC两边地电位差了几百毫伏SWDIO 的高低电平判据就乱了连接时好时坏。加一根地线立刻稳定。VREF 这根线的作用是告诉调试器目标芯片的 IO 电平是多少。它通常接目标板的 VDD调试器据此调整输出电平。有些调试器能自动检测有些必须接。如果你发现调试器识别不到芯片但线看起来都对先量一下 VREF 引脚的电压是不是在正常范围别让这个细节坑你半天。nRST 是可选的但在两种场景下强烈建议接上。一是目标板程序可能一上电就把 SWD 引脚复用成普通 GPIO此时需要在复位保持状态下连接也就是俗称的 connect under reset。二是程序里有看门狗复位时序需要调试器来控制。这两根线加上电源和地就是完整的连接。至于目标板要不要由调试器供电取决于调试器的输出能力和板子的功耗一般调试器只能提供很小电流板上如果有电机、屏幕、无线模块务必用独立电源。3.2 IDE 侧配置器件、时钟、复位方式以常见的 Keil MDK 为例连接参数主要有几项要核对。器件型号必须和实际芯片一致。选错了型号最直接的后果是 Flash 算法不匹配烧录进去的程序跑不对甚至写坏芯片的选项字节区域。我有一次拿错工程模板调试器显示烧录成功程序却纹丝不动查了两个小时才发现选的是同系列但 Flash 容量不同的型号Flash 算法按错误的分页大小擦写程序根本没落到正确位置。调试器类型和接口要对应。选 ST-Link 就选 ST-Link Debugger选 J-Link 就选对应的驱动接口那栏选 SW 而不是 JTAG除非你确实接了五根线。Max Clock 从低频起步比如 1MHz确认稳定后再上调。复位方式这一项经常被忽视但它直接决定连接成功率。常见的几种不连接时复位、连接时复位、连接时在复位保持状态下停止、连接时复位并停止。如果目标程序会把调试引脚复用掉就必须选复位保持状态下停止这一类让调试器在芯片还处于复位的时候就抢占链路。这个选项在 ST-Link 和 J-Link 的设置面板里叫法不同但含义相通。还有一项是关于低功耗的。ST-Link 的设置里通常有Connect under reset和一个和低功耗调试相关的复选框J-Link 是在脚本里配置。如果目标程序会进入 stop 或 standby 模式必须让调试器在连接时保持调试时钟否则一连就掉。这个配置的底层对应的是芯片里某个调试控制寄存器比如 STM32 的 DBGMCU_CR里面有位用来控制睡眠、停止、待机模式下是否保持调试连接。3.3 第一次连接成功的验证动作连上不是终点还要确认连得对不对。我的习惯是分四步走。第一步读 IDCODE 或芯片识别码确认调试器认得这颗芯片。如果这一步就失败说明电气层或者链路有问题不要去动代码。第二步读内核寄存器。看看 PC 指向哪里如果指向 Flash 起始地址附近或者复位向量说明芯片处于刚复位或者卡在启动阶段如果 PC 飞舞到一个奇怪的地址可能是内存越界或者栈溢出。这一步在不用运行程序的前提下就能判断芯片状态非常高效。第三步读内存。随便挑一段 Flash 地址看看内容是不是你上次烧录的机器码再看一段 RAM 地址观察是否全 0 或者全 0xFF。全 0xFF 一般说明芯片没供电或者总线访问失败全 0 可能是还没初始化。这一步能验证 AP 访问总线是否正常。第四步全速运行再暂停。点运行等一两秒点暂停看 PC 有没有变化。如果 PC 一直不动说明程序卡在某个死循环里此时配合看调用栈和反汇编基本就能定位问题。这四步做完你对板子的状态就有了完整的把握比盲目烧录一个测试程序可靠得多。3.4 把 SWO 和 ITM 跑起来实时日志和周期时间戳串口打印最大的问题是占用一个外设、占用引脚、而且打印本身有耗时在中断里打印几乎等于自杀。SWO 这条单线输出就是为了解决这个问题的它不占用 UART不进入用户的中断处理靠的是芯片内部的 ITM 模块。条件有两个芯片是 Cortex-M3 及以上内核里有 ITM 单元调试器的 SWO 引脚接到目标板的对应引脚上。配置步骤大致是在 IDE 的 trace 设置里使能 ITM设定 SWO 时钟频率这个频率通常等于芯片的 CPU 时钟或者调试时钟然后设定 SWO 的波特率两者要匹配。举个具体的算法如果 SWO 时钟是 72MHz你想要 2MHz 的输出速率分频系数就是 36异步模式下把 prescaler 设成 36 即可。分频系数只能是整数所以选频率的时候尽量让除法结果是整数误差大了会丢数据。代码侧只需要一个写寄存器的动作。在调试时把字符塞进 ITM 的端口即可值得注意的是这个操作在芯片没连接调试器的时候会卡住因为 ITM 的 FIFO 满了没人取。所以正式发布的固件里一定要用条件编译把这段代码关掉或者只在调试构建里使能。/* 仅在调试构建下使能避免正式固件卡在 ITM 写操作上 */ #ifdef DEBUG_TRACE_ENABLE static inline void itm_send_char(char c) { /* 端口 0 是标准的 printf 重定向通道 */ while (ITM-PORT[0].u32 0u) { /* 等待 FIFO 有空间超时保护按需添加 */ } ITM-PORT[0].u8 (uint8_t)c; } #endif有了 ITM还能顺手把时间戳打开。DWT 单元里有个周期计数器 CYCCNT内核每跑一个时钟周期它就加一精度是单周期级别。使能它只需要几行代码代价是几乎没有。用来测一段函数的执行时间、中断响应延迟、两次事件之间的间隔比用定时器打断点精确得多。/* 使能 DWT 周期计数器用于微秒级以内的耗时测量 */ static void dwt_cyccnt_init(void) { CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; /* 打开跟踪总开关 */ DWT-CYCCNT 0u; /* 清计数 */ DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; /* 启动计数 */ } /* 用法示例测量一段代码的周期数 */ uint32_t t0 DWT-CYCCNT; some_function(); uint32_t cycles DWT-CYCCNT - t0; /* 假如主频 120MHzcycles / 120 就是微秒数 */这里有个细节要注意cyccnt 是 32 位的在 100MHz 主频下大约 43 秒就会溢出回绕。做长时间测量时要么关心中断要么在溢出时手动累加高位。我用它测过一个 SPI 传输加 DMA 搬运的完整耗时把两次采样点的差值换算成微秒误差在个位数比示波器打点还方便。4. 连不上、跑飞、被锁死硬仿真故障排查实录4.1 识别不到目标芯片的五种典型原因这是最高频的问题我按出现概率从高到低排一下。第一接线或供电。SWDIO 和 SWCLK 接反、GND 没接、VREF 悬空、目标板没上电这几种占了相当比例。排查办法很简单万用表量电压示波器看波形别猜。特别是那种自己焊的板子虚焊的概率远比你想象的高。第二调试引脚被代码复用。上电后程序很快把 SWD 引脚配成了 GPIO 输出调试器握手窗口就那么几十毫秒错过就再也连不上。解决办法是用 connect under reset让调试器在复位期间就把链路建立起来。长期方案是修改固件在初始化里避免动这两根引脚或者加一个延时给调试器留出握手时间。第三时钟配置错误。有些芯片在启动时先切到外部晶振如果晶振没有起振程序会卡在时钟就绪等待里面。这种情况下内核可能根本没跑起来调试器自然也连不上。表现是偶尔能连、偶尔不能或者在复位后能连一运行就断。排查手段是先用内部时钟源启动确认能连上后再切换到外部晶振逐步排查。第四调试器驱动或固件问题。调试器本身的固件版本太老不支持新芯片或者 PC 上装了两个版本的驱动系统调用了错的那个。这种情况的典型表现是设备管理器里能看到调试器但 IDE 里识别不到。换个 USB 口、重装驱动、升级调试器固件通常能解决。第五芯片处于某种保护状态。读保护等级被打开、选项字节里禁用了调试接口、或者芯片被烧了错误的选项字节导致启动就进入某种锁定。这类问题需要用专门的工具做全片擦除来恢复后面单独讲。4.2 连上之后行为异常程序跑飞与 HardFault连接正常但程序行为不对这种情况往往更费时间因为故障不是彻底不通而是时好时坏。最常见的是 HardFault。Cortex-M 内核对非法访问、除零、非对齐访问、取指错误会触发硬件异常默认进入 HardFault 处理函数。如果这个函数是个死循环程序就卡在那里了。定位方法有几条路。一条是看寄存器。调试器停下时读 CFSR、HFSR、MMFAR、BFAR 这几个寄存器它们会告诉你异常类型和出错的地址。MMFAR 通常给出导致内存管理错误的地址BFAR 给出总线错误的地址这两个非常有价值。Cortex-M 的这几个寄存器在 SCB 结构里可以在调试器的内存窗口直接输入地址查看也可以写代码在 HardFault 处理函数里读出来存到某个变量上。另一条是看压栈的现场。异常发生时内核会自动把 R0-R3、R12、LR、PC、xPSR 压入当前使用的栈在 HardFault 处理函数里通过读取 MSP 或 PSP 就能拿到这组值。其中 PC 指向的就是出错的那条指令地址把它丢进反汇编窗口或者 map 文件里查一般能定位到具体函数。还有一种情况是程序不是跑飞而是跑得不对。比如时序差了一点、中断偶尔丢一次、DMA 搬运的数据错位。这类问题往往是调试器本身带来的影响。注意一个反直觉的事实接上调试器会让芯片的功耗、时钟行为、甚至某些外设的状态发生变化。比如低功耗模式下调试器一连接就没法进入最深睡眠或者调试器占用了某条总线导致时序偏移。排查这类问题的正确姿势是先用调试器观察关键变量定位到大致范围然后脱离调试器用引脚翻转加示波器或者用 RAM 里的环形缓冲区记录日志在真实条件下复现。我还想强调一下看门狗的问题。调试时程序停在断点上CPU 停了但看门狗外设通常还在计数几十毫秒后就把芯片复位了。表现是一打断点程序就重启非常迷惑人。解决办法有几种在调试配置里让调试器冻结看门狗这需要芯片支持对应的调试控制位或者临时把看门狗的喂狗周期调长或者在调试构建里直接关掉看门狗。我一般选第三种简单直接但一定要保证发布版本里看门狗是打开的。4.3 调试口被占用或被保护后的恢复思路这一类问题最让人头疼因为表面现象都是完全连不上但原因和处理方式差别很大。情况一引脚复用导致的失联。前文说过用 connect under reset 能解决大部分。如果连复位保持状态都进不去可能需要在硬件上把 SWCLK 或者 SWDIO 在复位期间强制拉到一个确定电平或者短接某个跳线让芯片进入特殊的启动模式。有些厂商还提供了 ROM bootloader 里的恢复流程。情况二读保护导致的访问拒绝。很多芯片有读出保护机制打开之后调试器就再也读不到 Flash 内容连接时表现为能识别到芯片但一读内存就报错或者干脆握手就失败。这种情况下唯一的出路是执行全片擦除包括擦掉选项字节让芯片回到出厂状态。这一步会清空用户程序所以量产芯片上做这个操作要非常谨慎。擦除可以通过调试器的专门功能完成也可以在引脚上拉特定电平进入 ROM 模式后用官方工具擦除。情况三选项字节被写入了禁用调试的设置。这比读保护更隐蔽因为芯片表面上完全正常能跑程序只是调试接口被关了。恢复思路和情况二类似需要全片擦除。预防的办法是在正式量产前认真核对选项字节配置尤其是一些工程模板里自带的默认值可能和你预期不同。情况四调试器固件和目标固件互相干扰。某些调试器在特定芯片上需要特定的固件版本版本不匹配时表现就是连不上。这类问题的排查方式是换一台电脑、换一个调试器、或者查调试器厂商的文档看是否有已知问题。4.4 常见故障速查表为了让排查更高效我把踩过的坑整理成下面这张表。遇到问题时按现象查能省下大量试错时间。现象最可能的原因优先排查动作完全识别不到芯片接线错误、没供电、GND 未共地量电压、量 GND 通断、降低 SWCLK 频率复位后能连一运行就断调试引脚被复用、时钟切换失败用复位保持连接、检查时钟初始化代码能识别芯片但读内存报错总线时钟停止、芯片在低功耗关闭低功耗、使能调试时钟保持位打断点程序就重启看门狗未被冻结调试构建关闭看门狗或调长喂狗周期断点数量设不了几个硬件比较器资源耗尽减少同时生效的硬件断点、改用观察点或打印烧录成功但程序不跑器件型号选错、Flash 算法不匹配核对型号、检查启动地址和向量表连接时好时坏线太长、干扰大、线速太高缩短接线、共地、把 SWCLK 降到 1MHz 以下提示设备被保护无法访问读保护开启或选项字节禁用调试执行全片擦除后重新配置这张表不能覆盖所有情况但覆盖了我在实际项目里遇到的绝大多数。经验是现象越具体原因越集中现象越模糊越要回到电气层去查。5. 把调试器用出更高价值进阶玩法与个人经验5.1 用调试器做半自动化的板级验证调试器不只是手动调试工具它还能通过命令行或脚本被程序调用用来做批量烧录和板级功能验证。常见的调试器厂商都提供命令行工具和 Python 库通过它们可以实现烧录、复位、读指定内存、判断结果这样一条流水线。举个例子一个产线测试工装插上板子后自动烧录固件然后复位运行等待几秒读取 RAM 里某个测试结果变量如果值正确就点绿灯否则点红灯并记录序列号。整个过程不需要人工干预节拍稳定比人工插拔调试器快得多。# 用 pyOCD 做一次烧录 - 复位 - 读结果的最小流程示意 import time from pyocd.core.helpers import ConnectHelper from pyocd.flash.file_programmer import FileProgrammer with ConnectHelper.session_with_chosen_probe( target_overridecortex_m, options{frequency: 1_000_000}) as session: # 1. 烧录固件 FileProgrammer(session).program(firmware.hex) # 2. 复位并全速运行 session.target.reset_and_halt() session.target.resume() # 3. 等待测试完成读取结果变量所在的地址 time.sleep(3.0) session.target.halt() result session.target.read32(0x20000100) # 约定0xA5A55A5A 表示全部测试项通过 print(PASS if result 0xA5A55A5A else FAIL: 0x%08X % result)这段代码只是骨架实际产线还要加上异常处理和重试逻辑。重点在于这种自动化和调试器的连接能力复用得很好不需要额外加串口或者测试引脚。我做过一个项目用它把原本需要两分钟的测试流程压缩到二十秒而且结果判断更客观。5.2 时间戳与日志存储把观测数据留下来调试时看到的变量值转瞬即逝尤其是偶发故障等你连上调试器现象早就没了。这时候有两种互补的思路。一种是靠时间戳把事件发生的时间点记录下来。DWT 的周期计数器是最精确的选择代价极小。在关键路径上采样把差值存到 RAM 缓冲区里事后连上调试器把这段缓冲区读出来分析。这种方法对实时性影响很小适合查中断抖动、任务切换延迟这类问题。另一种是把日志存到芯片内部的存储介质里。很多 MCU 支持在 Flash 里划一块区域做日志或者用外部存储器件。这种方式的优点是掉电不丢适合现场长期运行的设备。缺点是写入 Flash 有寿命限制和耗时问题需要按块缓冲、定时落盘。设计的时候要注意几个点日志区要和程序区隔离避免擦写时把代码擦掉写 Flash 期间要关中断或者把代码搬到 RAM 里执行否则取指会被阻塞日志的头部要带一个序列号和 CRC读取的时候能判断数据是否完整。我现在做一个新项目基本会在早期就预留一个几十 KB 的日志区把关键的启动流程、状态机跳转、异常事件都记一笔。等到现场出问题把设备拿回来读一遍日志比重新复现快得多。这个习惯的成本很低收益却很高。5.3 几个我反复验证过的操作习惯做 MCU 开发这些年有些习惯是踩了坑之后固定下来的分享一下。新板子第一次上电别急着烧程序。先用调试器做一次连接读内存看 PC确认芯片活着且调试通道正常。这一步能提前排除大量硬件问题比烧进去再查要快。调试口的引脚在原理图上标注清楚在代码里加一句注释提醒不要复用。我见过太多因为接手别人代码、不知道 PA13 是 SWDIO 而把它配成输出的人。这种事情一次就够你查一天。降低调试时钟这一招永远先试。连接不稳定的时候把 SWCLK 从几兆降到 500kHz八成问题直接消失。这不是妥协是正确认识信号完整性的重要性。硬件断点数量有限别指望它能替代日志。复杂逻辑的调试还是要靠分层的打印和状态记录调试器负责在关键点上做验证。把两种手段结合效率才真正高起来。最后留一个小提醒调试构建和发布构建一定要分开。调试构建里可以关看门狗、可以打开 ITM、可以留日志发布构建里这些全部要收掉而且要用独立的方式验证发布构建能正常运行。我自己吃过亏调试时一切正常量产固件烧进去因为看门狗没喂导致反复重启查了半天才发现是两个构建配置的宏不一样。这个坑希望你别再踩一遍。
返回列表