7.2 烧录与调试:工具链实战


文档摘要

7.2 烧录与调试:工具链实战 上一节搞清了固件"怎么跑起来",这一节解决"怎么装进去、坏了怎么看见"。SWD 烧录与在线调试是嵌入式开发的日常主战场:命令行烧录一条龙、GDB 断点调试三板斧、镜像体积的解读、三类高频烧录故障的排法——这一节全按真实会话的样子走一遍。工具链顺手了,前六章的一切知识才能真正快速迭代。 SWD:两根线管烧录又管调试 STM32 的烧录调试口首选 SWD(Serial Wire Debug):只需 SWDIO(数据)与 SWCLK(时钟)两根信号线,加上 3.3V 和 GND 共四根。它同时承担三个职能:烧录(写 Flash)、在线调试(断点、单步、看内存)、以及半主机式的信息通道。JTAG 功能更全但占引脚多,量产板上已很少见;

7.2 烧录与调试:工具链实战

上一节搞清了固件"怎么跑起来",这一节解决"怎么装进去、坏了怎么看见"。SWD 烧录与在线调试是嵌入式开发的日常主战场:命令行烧录一条龙、GDB 断点调试三板斧、镜像体积的解读、三类高频烧录故障的排法——这一节全按真实会话的样子走一遍。工具链顺手了,前六章的一切知识才能真正快速迭代。

SWD:两根线管烧录又管调试

STM32 的烧录调试口首选 SWD(Serial Wire Debug):只需 SWDIO(数据)与 SWCLK(时钟)两根信号线,加上 3.3V 和 GND 共四根。它同时承担三个职能:烧录(写 Flash)、在线调试(断点、单步、看内存)、以及半主机式的信息通道。JTAG 功能更全但占引脚多,量产板上已很少见;UART 串口 ISP 烧录(BOOT0 拉高进系统 bootloader)是 SWD 坏了之后的备胎——3.3 节讲过 BOOT 引脚就是这个用途。

一条 OpenOCD + GDB 的完整会话长这样(命令行直接可复现):

# 终端 1:OpenOCD 起调试服务,占用 3333 端口 $ openocd -f interface/stlink.cfg -f target/stm32f1x.cfg Info : STLINK V2 (API v2) VID:PID 0483:3748 Info : Target voltage: 3.27V ← 目标电压正常,接线良好 Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints # 终端 2:交叉调试器连上去,烧录 + 调试 $ arm-none-eabi-gdb led.elf (gdb) target remote localhost:3333 (gdb) monitor reset halt ← 复位并停住内核 (gdb) load ← 把 ELF 烧进 Flash Loading section .text, size 0x540 lma 0x8000000 Loading section .data, size 0x14 lma 0x8000540 (gdb) monitor reset halt ← 烧完复位,从向量表重新起跑 (gdb) break main ← 在 main 设断点 (gdb) continue Breakpoint 1, main () at main.c:12 ← 停在 main 第一行

同样的活用图形工具(STM32CubeProgrammer、IDE 内置调试)点几下鼠标也能完成,但命令行版的每一步都暴露了本质:OpenOCD 是"调试服务器",GDB 通过它读写芯片的 CPU 寄存器与内存——所谓断点,就是调试硬件在你指定的地址塞了一条特殊指令,CPU 撞上就停。理解了这层,"IDE 里调试图标全灰了"这类玄学问题就退化成"OpenOCD 连上没有"的是非题。

GDB 调试三板斧:断点、观察、回溯

嵌入式调试九成的需求被三招覆盖。断点与单步定位"程序走到没走到";**监视点(watchpoint)**抓"哪个变量被谁改了"——裸机时代"变量莫名被写坏"的头号悬案,watch 一条命令破案;**回溯(backtrace)**在 HardFault 停下时打印调用链,直接指认肇事函数。

(gdb) break TIM3_IRQHandler ← 中断入口设断点:验证中断到底进没进 (gdb) watch g_ticks_10ms ← 谁改了它?写监视点 Hardware watchpoint 2: g_ticks_10ms (gdb) continue Hardware watchpoint 2: g_ticks_10ms ← 每次被改都会停下,栈上就是凶手 Old value = 41 New value = 42 TIM3_IRQHandler () at main.c:48 ← 实锤:是定时器中断改的,合理 (gdb) backtrace ← HardFault 停机时的标准动作 #0 HardFault_Handler () #1 <signal handler called> #2 0x08001234 in parse_frame (buf=0x20000f3c <rxbuf>, len=200) at parser.c:31 ← 第 31 行疑似数组越界,len=200 超了缓冲

无调试器时的替身也要会当:串口打印是最朴素的轨迹法(5.1 的 printf 重定向),缺点是改动代码重新烧录可能让 bug 消失(时序变了);更高级的替身是把关键变量投递到 Segger RTT 或直接用 GPIO 翻转 + 示波器测某段代码的执行耗时——后者是性能调优时的常用组合。

镜像体检:size 与 map 的两分钟读法

烧录前先给固件做个体检,两分钟看清 Flash 与 RAM 占用(2.2 节的段落知识在此变现):

$ arm-none-eabi-size led.elf text data bss dec hex filename 5428 20 1048 6496 1960 led.elf Flash 占用 = text + data = 5448 B(64 KB 的 8.3%) RAM 占用 = data + bss = 1068 B(20 KB 的 5.2%,栈堆余量充足) $ arm-none-eabi-nm --size-sort -S led.elf | tail -5 ← 按大小排出最大的符号 00000400 B rxbuf ← 1 KB 的接收缓冲,bss 大户 00000380 T parse_frame ← 最大的函数,查它是否被无意优化膨胀

这套体检的价值在于"早发现":Flash 用量默默爬过 80% 就是危险信号(以后加功能、升级库都可能溢出);bss 异常增大十有八九是有人定义了大数组;栈水位(2.2 节的 watermark 函数)配合起来看,RAM 安全线就心中有数了。

三类高频烧录故障与排法

烧录失败的报错五花八门,但八成落在三类。连接不上(OpenOCD 报 target voltage 0 或连接超时):查四根线顺序与接触、量目标板供电(3.3V 脚实际多少伏)、确认板上没有其他程序把 SWD 引脚重配置成了 GPIO——若是后者,按住板上复位键、点烧录、在复位的瞬间抢进,或改用串口 ISP 恢复。Flash 写保护或读保护(报错提到 protection):RDP 读保护被打开过,用官方工具全片擦除解除(数据会丢,属预期代价)。供电不足(连上了但写入随机失败、校验错误):蓝药丸板从 ST-Link 取电,板载稳压在 USB 线细长时压降明显,外接稳定 3.3V 或换短粗 USB 线常能解决。

故障现象 高概率原因 第一反应
找不到目标 / 电压 0V 接线、供电、SWD 被改用 量电压,试复位时抢烧
连上但写 Flash 报错 写保护 / 供电抖动 全片擦除;换供电
烧完不跑、连接仍在 BOOT0 悬空、向量表坏 查 BOOT 引脚,看 0x08000000

ESP32 侧的对应工具链更省心:官方烧录工具一条 idf.py flash monitor 完成编译、烧录、开串口监视三连,ROM 里固化的下载器意味着"刷坏了也能救"(串口强刷不依赖 Flash 里的任何程序)。它的调试走 JTAG 或 gdbstub over UART,GDB 会话与上文同款。

调试会话的日常节奏

把上面三招串成日常动作:烧录后先 monitor reset halt 停在复位处,break main 确认入口正常,再放行观察行为;怀疑某段逻辑时下条件断点(break parser.c:31 if len > 64),比肉眼盯串口快得多;程序跑飞时 monitor reset halt 后看 PC 与调用栈,八成能直接指到肇事代码。这套节奏练熟后,一个"烧录-断点-单步-看变量"的循环只要几十秒,排障效率与盲改代码不可同日而语。

工具链的最后一环是"产品出厂后怎么继续更新"——总不能召回设备拆壳烧录。下一节的 OTA 固件升级,把 7.1 的 Bootloader 与第 6 章的多任务协作拧成一股绳,完成从裸机到产品的最后一跃。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U