4. 配置文件基础:OpenOCD 配置文件结构

OpenOCD 的配置文件,说白了就是告诉调试器三件事:你连的是什么调试器?你连的是什么板子?板上是什么芯片?

我刚接触 OpenOCD 那会儿,总想一个脚本搞定所有事。结果呢?换个板子就得从头改,改完还经常报错。后来我才明白——OpenOCD 的配置文件是分层设计的。你想想看,调试器、板子、芯片,这三者本来就是独立的,硬塞在一起反而麻烦。

4.1 配置文件的三大类型

OpenOCD 的配置文件分三类,各管各的:

类型 文件名惯例 职责
interface interface/xxx.cfg 定义调试器硬件(如 J-Link、ST-Link、FTDI)
board board/xxx.cfg 描述板级特性(时钟、复位、内存映射)
target target/xxx.cfg 定义芯片内核(ARM Cortex-M、RISC-V 等)

启动时,OpenOCD 按 interface → board → target 的顺序加载。board 文件里通常会 source 对应的 target 文件。我个人习惯是:尽量不改官方提供的 board 文件,只改 interface 和 target 的局部参数,这样升级 OpenOCD 版本时不会出问题。

4.2 interface 配置文件

interface 文件负责初始化调试器硬件。它告诉 OpenOCD:你用的是什么调试器,走什么协议,速度多快。

举个例子,J-Link 的配置:

# interface/jlink.cfg
interface jlink
transport select swd
adapter speed 4000
adapter srst delay 100

这里 transport select swd 选择 SWD 协议(而不是 JTAG)。adapter speed 4000 设成 4 MHz。嗯,这里要注意——速度不是越高越好。我曾经在一块长线缆的板子上设了 10 MHz,结果死活连不上,降回 4 MHz 就稳了。线长了,信号会衰减,跑太快反而容易出错。

ST-Link 的配置类似:

# interface/stlink.cfg
interface hla
hla_layout stlink
hla_device_desc "ST-Link"
hla_vid_pid 0x0483 0x3748

这里用了 hla(High-Level Adapter)模式,ST-Link 的 VID/PID 是 0x0483/0x3748。如果你手上有山寨版 ST-Link,PID 可能不一样,得自己查一下。

小技巧: 不确定调试器的 VID/PID?在 Linux 下用 lsusb,Windows 下用设备管理器查看。找到后记下来,以后改配置就方便了。

4.3 board 配置文件

board 文件是中间层,它把 interface 和 target 串起来,再加上板子特有的东西——比如复位逻辑、时钟配置、额外的内存区域。

以 STM32F4-Discovery 为例:

# board/stm32f4discovery.cfg
source [find interface/stlink.cfg]
source [find target/stm32f4x.cfg]

reset_config srst_only srst_nogate

$_TARGETNAME configure -event reset-init {
    # 初始化板载 LED 等外设
    mww 0x40023830 0x00000101  ; 使能 GPIO 时钟
}

你看,board 文件先 source 了 interface 和 target,然后定义了复位配置。srst_only 表示只使用系统复位(SRST),srst_nogate 表示复位信号不经过门控——这个参数我踩过坑,后面细说。

board 文件里还可以加一些板级初始化脚本,比如上电后配置 GPIO、初始化 SDRAM 等。我习惯把这类初始化写成 reset-init 事件,这样每次复位后自动执行,省得手动敲命令。

注意: 不要把所有初始化都塞进 board 文件。如果初始化逻辑很复杂(比如 DDR 训练),建议单独写一个脚本文件,在 board 里用 source 引入。这样维护起来清爽很多。

4.4 target 配置文件

target 文件是核心,它定义芯片的内核类型、调试接口、内存映射、工作模式等。

看一个 STM32F4 的例子:

# target/stm32f4x.cfg
if { [info exists CHIPNAME] } {
    set _CHIPNAME $CHIPNAME
} else {
    set _CHIPNAME stm32f4x
}

if { [info exists ENDIAN] } {
    set _ENDIAN $ENDIAN
} else {
    set _ENDIAN little
}

if { [info exists CPUTAPID] } {
    set _CPUTAPID $CPUTAPID
} else {
    set _CPUTAPID 0x2ba01477
}

# 创建 TAP(Test Access Port)
jtag newtap $_CHIPNAME cpu -irlen 4 -ircapture 0x1 -irmask 0xf -expected-id $_CPUTAPID

# 创建目标
set _TARGETNAME $_CHIPNAME.cpu
target create $_TARGETNAME cortex_m -endian $_ENDIAN -chain-position $_TARGETNAME

# 工作区 RAM 配置
$_TARGETNAME configure -work-area-phys 0x20000000 -work-area-size 0x20000 -work-area-backup 0

# Flash 配置
flash bank stm32f4x.flash stm32f4x 0x08000000 0x100000 2 4 $_TARGETNAME

这里有几个关键点:

  • CPUTAPID:芯片的 IDCODE,用于验证连接的芯片是否正确。我遇到过一块声称是 STM32F407 的板子,结果 IDCODE 对不上——后来发现是打磨片。所以,拿到新板子第一件事就是读 IDCODE。
  • work-area:调试时用的 RAM 区域,OpenOCD 用它来暂存下载的代码。大小要合理,太小了下载大文件会失败。
  • flash bank:定义 Flash 的起始地址、大小、扇区数等。这里 0x08000000 是 STM32F4 的内部 Flash 起始地址,0x100000(1 MB)是大小。

核心原则: target 文件只描述芯片本身,不要掺入板级信息。比如板子上外挂的 SRAM、LED 引脚等,应该放在 board 文件里。这样同一个 target 文件可以复用到不同板子上。

4.5 配置文件的加载顺序与覆盖机制

OpenOCD 启动时,你可以通过命令行指定配置文件:

openocd -f interface/jlink.cfg -f board/my_board.cfg

加载顺序就是 -f 参数的顺序。后加载的配置可以覆盖先加载的变量。比如你在 board 文件里重新定义了 CPUTAPID,它会覆盖 target 文件里的默认值。

我常用的做法是:

  1. 先加载官方 interface 文件
  2. 再加载自己写的 board 文件(里面 source 官方 target)
  3. 在 board 文件里用 set 覆盖需要的参数

这样既保留了官方配置的完整性,又能灵活调整。

4.6 知识结构图:配置文件体系

下面这张图展示了三类配置文件的关系和加载流程:

OpenOCD 配置文件体系 openocd -f 启动 interface 配置 board 配置 target 配置 interface 内容 • 调试器类型 (J-Link/ST-Link) • 传输协议 (SWD/JTAG) • 适配器速度 board 内容 • 复位配置 • 板级初始化脚本 • 外设内存映射 target 内容 • 内核类型 (Cortex-M) • TAP 配置 & IDCODE • Flash & 工作区配置 加载顺序:interface → board → target(board 可 source 两者)

4.7 常见问题与避坑

配置文件看着简单,但实际用起来坑不少。我挑几个常见的说说:

  • IDCODE 不匹配:target 文件里写的 IDCODE 和实际芯片对不上。解决办法:先用 openocd -f interface/xxx.cfg -c "adapter speed 1000; init; halt; rp 0" 读一下真实 IDCODE,然后改 target 文件。
  • 复位配置错误:有些板子用 srst_only,有些用 trst_only,还有些两个都要。我曾经在一块 i.MX6 板子上配错了复位类型,导致调试器一连接就死机。后来查手册才发现,那板子的复位信号是低电平有效,需要加 srst_nogate。
  • 工作区太小:如果 work-area-size 设得太小,下载大文件时会报 out of memory。我一般设成芯片 SRAM 的一半,留一半给程序运行。
我的习惯: 每拿到一块新板子,我会先建一个 board/my_custom_board.cfg,里面只写板级特有的东西。interface 和 target 都用官方的。这样换调试器时,只需要改 -f 参数里的 interface 文件,其他不动。

配置文件这东西,说白了就是搭积木。interface、board、target 各是一块积木,拼对了就能跑。别怕改,多试几次就熟了。