3. 调试基础

硬件调试,说白了就是让CPU停下来听你指挥。你让它单步走一步,它就执行一条指令;你让它读寄存器,它就把值吐出来。OpenOCD就是干这个活的——它把你的PC和调试器(J-Link、ST-Link、FTDI这些)连起来,再通过JTAG或SWD协议跟目标芯片对话。

我刚开始用OpenOCD的时候,光配一个.cfg文件就折腾了大半天。后来发现,只要理解了它的三层结构,其实没那么玄乎。

3.1 OpenOCD的三层架构

OpenOCD的配置逻辑,我习惯把它拆成三层:接口层、传输层、目标层。你想想看,调试器硬件是接口层,JTAG/SWD协议是传输层,芯片内核就是目标层。

核心思路:OpenOCD启动时,按顺序加载接口配置、传输配置、目标配置。顺序错了,或者文件选错了,调试器就认不出芯片。

下面这张图,是我每次给新人培训时必画的。它把OpenOCD的启动流程和三层关系讲清楚了。

OpenOCD 三层架构与启动流程 接口层 调试器硬件 J-Link / ST-Link / FTDI 传输层 JTAG / SWD 协议 时钟速率、链扫描 目标层 芯片内核 Cortex-M / RISC-V 配置加载顺序:interface → transport → target 接口配置示例 (jlink.cfg) adapter driver jlink adapter speed 4000 transport select swd # 或 transport select jtag 目标配置示例 (nrf52.cfg) set CHIPNAME nrf52 source [find target/nrf52.cfg] # 或手动指定: set _TARGETNAME $_CHIPNAME.cpu target create $_TARGETNAME cortex_m -endian little -chain-position 0

3.2 接口配置:选对调试器驱动

OpenOCD支持的调试器很多。我个人最常用的是J-Link和ST-Link。配置其实就几行,但坑不少。

调试器 驱动名 配置文件 注意事项
SEGGER J-Link jlink interface/jlink.cfg 速度建议 4000 kHz 起步
ST-Link/V2 stlink interface/stlink.cfg 注意固件版本,老版不支持SWD
FTDI 2232 ftdi interface/ftdi/xxx.cfg 需要自己配引脚映射
CMSIS-DAP cmsis-dap interface/cmsis-dap.cfg 速度一般,适合低成本方案

我的习惯:调试新板子时,先把速度降到 1000 kHz。等确认连接稳定了,再往上提。我曾经遇到过一块板子,布线有点长,4000 kHz死活连不上,降到 2000 kHz 就稳了。

3.3 传输层:JTAG vs SWD

JTAG用了4根线(TMS、TCK、TDI、TDO),SWD只要2根(SWDIO、SWCLK)。现在的Cortex-M芯片,我几乎只用SWD。为什么?省引脚,而且速度不差。

但有个细节要注意:SWD和JTAG不能同时用。你在配置里选了SWD,调试器就只走SWD协议。如果芯片当前被锁在JTAG模式,你得先切过去。

我曾经踩过的坑:有块nRF52840的板子,之前被人用J-Link Commander切到了JTAG模式。我拿OpenOCD用SWD去连,怎么都连不上。折腾了半小时,才发现是模式没对上。用 transport select jtag 一试,秒连。

3.4 目标配置:让OpenOCD认识你的芯片

目标配置是最容易出问题的地方。Zephyr的boards目录下,每个板子都有对应的OpenOCD配置文件。比如 boards/arm/nrf52840dk_nrf52840/support/openocd.cfg。

但如果你自己画了板子,或者用了非标准的芯片,就得手写目标配置了。核心参数就这几个:

  • 芯片名:set CHIPNAME mychip
  • 内核类型:cortex_m、riscv 等
  • 端序:ARM基本都是小端 -endian little
  • 链位置:-chain-position 0,多核芯片这里要注意
# 一个典型的目标配置片段
set CHIPNAME stm32f407
source [find target/stm32f4x.cfg]

# 如果找不到现成的,手动创建
set _TARGETNAME $_CHIPNAME.cpu
target create $_TARGETNAME cortex_m -endian little -chain-position 0
$_TARGETNAME configure -work-area-phys 0x20000000 -work-area-size 0x10000

关键点:work-area-phys 和 work-area-size 指定了OpenOCD在RAM里临时存放数据的位置。如果这个区域跟你的程序堆栈冲突了,调试时会出现奇怪的问题。我一般选在RAM末尾,避开程序使用的区域。

3.5 启动OpenOCD:实战命令

配置写好了,怎么启动?我最常用的命令是:

openocd -f interface/jlink.cfg -f target/nrf52.cfg

如果一切正常,你会看到类似这样的输出:

Info : Listening on port 3333 for gdb connections
Info : Listening on port 4444 for telnet connections
Info : Listening on port 6666 for tcl connections

看到这三个端口,说明OpenOCD已经跑起来了。端口3333是给GDB用的,4444是telnet控制台,6666是TCL接口。

调试小技巧:如果OpenOCD启动时报错 Error: unable to find a matching CMSIS-DAP device,八成是驱动没装好,或者USB线有问题。换根线试试,我遇到过好几次这种低级问题。

3.6 目标板控制:复位、暂停、单步

OpenOCD跑起来之后,你可以通过telnet连到4444端口,直接发命令控制目标板。

telnet localhost 4444

进去之后,常用的命令就这几个:

命令 作用 示例
reset 复位目标板 reset
halt 暂停CPU halt
resume 恢复运行 resume
step 单步执行一条指令 step
reg 查看寄存器 reg r0
mdw 读内存(32位) mdw 0x20000000 16
mww 写内存(32位) mww 0x20000000 0xdeadbeef
flash write_image 烧写固件 flash write_image erase build/zephyr/zephyr.hex

注意:用 halt 暂停CPU时,如果芯片正在跑一个关键的中断服务程序,强行暂停可能导致外设状态不一致。我一般会在调试前先确保没有关键中断在跑,或者用 reset halt 让芯片在复位后立即暂停。

3.7 与GDB配合:真正的调试体验

光用telnet敲命令,效率太低了。真正的调试,还是要靠GDB。OpenOCD启动后,你在另一个终端里跑:

arm-none-eabi-gdb build/zephyr/zephyr.elf
(gdb) target remote localhost:3333
(gdb) monitor reset halt
(gdb) load
(gdb) break main
(gdb) continue

这里有个细节:monitor reset halt 是让OpenOCD帮你复位并暂停芯片。load 是把固件烧进去。然后设断点,跑起来。

我的经验:Zephyr的构建系统已经帮你生成了 west debug 和 west debugserver 命令。前者一键启动OpenOCD+GDB,后者只启动OpenOCD,让你自己连GDB。新手我建议用 west debug,省心。

3.8 常见问题与避坑指南

  • 连接不上:先检查接线。SWD只需要SWDIO、SWCLK、GND,但有些板子还需要VCC来检测电平。我遇到过因为没接VCC导致连不上的情况。
  • 速度太慢:OpenOCD默认速度可能只有几百kHz。在配置里加 adapter speed 4000,能快不少。
  • 烧写失败:检查Flash保护位。有些芯片出厂时Flash是读保护的,得先用OpenOCD解锁。比如STM32的 stm32f1x unlock 0。
  • GDB断点不生效:硬件断点数量有限(Cortex-M一般只有6个)。如果设了太多断点,GDB会报错。用 info breakpoints 查看当前断点列表。

最后说一句:OpenOCD的日志级别可以调。启动时加 -d 或 -d3,能看到更详细的调试信息。遇到奇怪的问题,先开debug日志,往往能发现线索。