4. 目标芯片调试架构

好,咱们进入正题。OpenOCD的配置,说白了就是告诉它三件事:你用什么调试器连上去?你连的是什么芯片?这块板子上还有什么特殊的东西? 这三件事分别对应三类配置文件:接口配置文件、目标配置文件、板级配置文件。我刚开始接触OpenOCD时,总觉得这些文件又多又乱,后来摸清了套路,发现其实就那么回事。

核心思路:OpenOCD的配置是分层叠加的。接口层 → 目标层 → 板级层,层层递进。你写配置时,也按这个顺序来,基本不会乱。

4.1 接口配置文件:告诉OpenOCD你的调试器长什么样

接口配置文件,就是定义调试器本身。比如你用的是J-Link、ST-Link、FT2232还是CMSIS-DAP。每种调试器都有自己的通信协议和初始化方式。

我个人习惯把接口配置单独写一个文件,比如 interface_jlink.cfg。这样换调试器时,只改这一行引用就行。

来看一个典型的J-Link配置:

# interface_jlink.cfg
interface jlink
transport select swd
adapter speed 4000
adapter srst delay 100
jlink newdap swo 0

这里每行什么意思?

  • interface jlink:告诉OpenOCD,我用的是J-Link调试器。
  • transport select swd:选择SWD模式。JTAG模式就写 jtag。我建议除非你非要调试4线JTAG,否则SWD更省引脚、更稳定。
  • adapter speed 4000:设置SWD时钟频率,单位kHz。4000就是4MHz。别一上来就设太高,我遇到过好几次因为线太长、频率太高导致连接不上的情况。
  • adapter srst delay 100:复位后延迟100ms,给芯片一点喘气的时间。

如果是FT2232这类USB转JTAG的芯片,配置会稍微复杂一点:

# interface_ft2232.cfg
interface ftdi
ftdi vid_pid 0x0403 0x6010
ftdi channel 0
ftdi layout_init 0x0018 0x001b
ftdi layout_signal nTRST -data 0x0010
ftdi layout_signal nSRST -data 0x0020
adapter speed 1000

小技巧:如果你不确定调试器的VID/PID,在Linux下用 lsusb 查看,Windows下用设备管理器。我当年第一次配FT2232时,就是靠这个命令找到的。

4.2 目标配置文件:定义你的芯片内核

目标配置文件,核心就是告诉OpenOCD:你要调试的芯片是什么架构、有几个核、内存映射是怎样的。

以最常见的Cortex-M3为例:

# target_stm32f1.cfg
set CHIPNAME stm32f1
set ENDIAN little
set CPUTAPID 0x1ba01477

swj_newdap $CHIPNAME cpu -irlen 4 -expected-id $CPUTAPID
target create $CHIPNAME.cpu cortex_m -endian $ENDIAN -chain-position $CHIPNAME.cpu

$CHIPNAME.cpu configure -work-area-phys 0x20000000 -work-area-size 0x10000
$CHIPNAME.cpu configure -work-area-backup 0

这里有几个关键点:

  • swj_newdap:创建JTAG/SWD访问端口。 -irlen 4 是IR长度,Cortex-M一般是4位。
  • -expected-id:芯片的IDCODE。如果ID不匹配,OpenOCD会报错。我建议你第一次连接时,先用 scan_chain 命令读一下实际ID,再写进配置文件。
  • work-area-phys:指定一块RAM作为工作区,用于下载算法、断点管理等。一般选SRAM的起始地址。

注意:work-area-size别设太大,留点空间给程序本身跑。我曾经设了64KB,结果程序一跑就崩,查了半天才发现是工作区把程序栈给挤了。

4.3 板级配置文件:把接口和目标串起来

板级配置文件,就是胶水层。它把接口配置和目标配置组合在一起,再加上板子特有的东西,比如复位方式、时钟配置、额外的GPIO控制等。

一个典型的板级配置:

# board_stm32f103c8t6.cfg
source [find interface_jlink.cfg]
source [find target_stm32f1.cfg]

reset_config srst_only srst_nogate
adapter srst pulse_width 100

$_TARGETNAME configure -event reset-init {
    # 初始化时钟
    mww 0x40021000 0x00000102  ;# RCC_CR: HSI on
    sleep 10
    mww 0x40021004 0x00000764  ;# RCC_CFGR: PLL config
    sleep 10
    mww 0x40021000 0x01000102  ;# RCC_CR: PLL on
    sleep 10
}

这里 source 命令把前面两个配置文件引进来。reset_config 定义复位信号的行为。-event reset-init 是个事件钩子,在复位后自动执行,用来初始化时钟。这个功能非常实用,我经常用它来在调试前把芯片时钟配好,省得每次手动敲命令。

配置层级 文件名示例 核心内容
接口层 interface_jlink.cfg 调试器类型、传输协议、速率、VID/PID
目标层 target_stm32f1.cfg 芯片架构、IDCODE、工作区、内存映射
板级层 board_stm32f103c8t6.cfg 组合接口+目标、复位策略、初始化脚本

4.4 多核调试配置:同时看住多个核

多核调试,说白了就是在一个JTAG链上挂多个目标。比如一个芯片里有两个Cortex-M4核,或者一个M4加一个M0。

配置的关键在于 JTAG链的扫描顺序。每个核在链上有一个位置,OpenOCD通过 -chain-position 来区分。

来看一个双核Cortex-M4的配置:

# target_dual_m4.cfg
set CHIPNAME dualm4

# 第一个核
swj_newdap $CHIPNAME cpu0 -irlen 4 -expected-id 0x2ba01477
target create $CHIPNAME.cpu0 cortex_m -endian little -chain-position $CHIPNAME.cpu0

# 第二个核
swj_newdap $CHIPNAME cpu1 -irlen 4 -expected-id 0x2ba01477
target create $CHIPNAME.cpu1 cortex_m -endian little -chain-position $CHIPNAME.cpu1

# 分别配置工作区
$CHIPNAME.cpu0 configure -work-area-phys 0x20000000 -work-area-size 0x10000
$CHIPNAME.cpu1 configure -work-area-phys 0x20010000 -work-area-size 0x10000

# 设置复位事件
foreach cpu { cpu0 cpu1 } {
    $CHIPNAME.$cpu configure -event reset-init {
        # 每个核的初始化代码
    }
}

启动OpenOCD时,用 targets 命令可以看到两个核:

openocd -f board_dual_m4.cfg
> targets
    TargetName         Type       Endian TapName            State
--  ------------------ ---------- ------ ------------------ ------------
 0* dualm4.cpu0        cortex_m   little dualm4.cpu0        running
 1  dualm4.cpu1        cortex_m   little dualm4.cpu1        running

调试时,用 targets 0 或 targets 1 切换当前调试的核。我做过一个项目,两个核共享内存,一个核写数据、一个核读数据。用OpenOCD同时挂两个核,设断点观察同步逻辑,问题一下子就暴露了。

避坑指南:多核调试时,复位策略要特别小心。我曾经遇到过复位一个核,结果把另一个核也带崩了。后来改用 reset_config 分别控制每个核的复位信号,才解决问题。

4.5 配置文件的组织与调试流程

下面这张图,是我自己总结的OpenOCD配置与调试的完整流程。你写配置时,就按这个思路来:

OpenOCD配置与调试流程 接口配置文件 interface_jlink.cfg 目标配置文件 target_stm32f1.cfg 板级配置文件 board_stm32f103.cfg 启动OpenOCD openocd -f board_stm32f103.cfg 连接GDB客户端 arm-none-eabi-gdb -x gdb_init.gdb 调试操作 load / reset / break / step / continue reg / mdw / mww / flash write_image 多核时 targets N 切换核

嗯,流程其实不复杂。你写好板级配置文件,一条命令启动OpenOCD,然后GDB连上去,剩下的就是调试操作了。多核时,记得用 targets 命令切换当前核。

我的经验:刚开始写配置时,别追求一步到位。先写一个最简单的接口配置,连上调试器,确认通信正常。再逐步加上目标配置、板级配置。每加一层,就测试一次。这样出了问题,你马上知道是哪一层的事。

好了,这一章的内容就是这些。配置文件这东西,写多了就熟了。你手边如果有开发板,现在就可以打开OpenOCD,照着上面的例子写一个试试。实践出真知。