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 可能不一样,得自己查一下。
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 事件,这样每次复位后自动执行,省得手动敲命令。
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 文件里的默认值。
我常用的做法是:
- 先加载官方 interface 文件
- 再加载自己写的 board 文件(里面 source 官方 target)
- 在 board 文件里用
set覆盖需要的参数
这样既保留了官方配置的完整性,又能灵活调整。
4.6 知识结构图:配置文件体系
下面这张图展示了三类配置文件的关系和加载流程:
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 各是一块积木,拼对了就能跑。别怕改,多试几次就熟了。