OpenOCD基础篇(三)——擦除和读取

前言:
  本篇文章介绍使用OpenOCD烧录和调试。

1 全片擦除

  通过OpenOCD可以对MCU的Flash进行全片擦除,本文以STM32F103ZET6单片机为例,创建一个OpenOCD的脚本文件 jlink_swd_stm32f103_erase.cfg ,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# ============================================
# STM32F103ZET6 全片擦除配置文件(J-Link + SWD)
# 支持无 nSRST 连接情况
# ============================================

# 使用 J-Link 调试器
source [find interface/jlink.cfg]

# 选择 SWD 传输模式
transport select swd

# 设置 SWD 时钟频率(单位:kHz),STM32F1 支持最高约 4000 kHz
adapter speed 4000

# 目标芯片配置
source [find target/stm32f1x.cfg]

# ==================================================================================
# 复位配置说明:
# - 如果你没有将 J-Link 的 nSRST 接到 STM32 的 NRST 引脚,请使用 `reset_config none`
# - 否则可使用 `reset_config srst_only srst_open_drain` 等
# ==================================================================================
reset_config none

# 可选:启用软复位支持(通过 AIRCR 触发 Cortex-M 复位)
adapter srst delay 100
adapter srst pulse_width 100

# ==================================================================================
# 擦除主程序函数
# ==================================================================================
proc erase_chip {} {
echo "开始擦除 STM32F103ZET6 ..."

# 初始化调试会话
init

# 尝试复位并进入 halt 状态
echo "尝试复位并暂停 CPU..."
catch {reset init} err
if {[string first "timed out" $err] != -1 || [string first "Error" $err] != -1} {
echo "reset init 失败,尝试手动 halt..."

# 如果 reset init 失败,尝试强制 halt
catch {halt}
if {[target cur_state] != "halted"} {
echo "错误:无法暂停 CPU,请检查供电、BOOT 模式或保护位!"
shutdown
return -1
}
}

# 等待稳定
sleep 100

# 解锁 Flash(自动处理读保护)
echo "正在解锁 Flash(自动解除读保护)..."
catch {stm32f1x unlock 0} unlock_result
if {[string first "Error" $unlock_result] != -1} {
echo "警告: Flash 解锁失败或已处于解锁状态: $unlock_result"
} else {
echo "Flash 已成功解锁。"
# 解锁后需要重新初始化目标
reset init
sleep 100
}

# 执行全片擦除(mass erase)
echo "执行全片擦除..."
set res [catch {stm32f1x mass_erase 0} result]
if {$res == 0} {
echo "全片擦除成功!"
} else {
echo "全片擦除失败: $result"
echo "尝试逐扇区擦除..."
for {set i 0} {$i < 64} {incr i} {
echo "擦除扇区 $i..."
flash erase_sector 0 $i $i
}
}

# 锁定 Flash(可选,通常不需要立即锁定)
echo "重新锁定 Flash..."
catch {stm32f1x lock 0}

# 最终复位并运行
echo "复位并启动芯片..."
reset run

echo "擦除完成!"
}

# 执行擦除操作
erase_chip

# 关闭连接
shutdown

  通过JLink连接单片机,并使用命令行运行该脚本:

1
openocd -f jlink_swd_stm32f103_erase.cfg

  擦除成功后可看到如下打印:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
╰─ openocd -f jlink_swd_stm32f103_erase.cfg
Open On-Chip Debugger 0.11.0
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
开始擦除 STM32F103ZET6 ...
Info : J-Link V9 compiled May 7 2021 16:26:12
Info : Hardware version: 9.70
Info : VTarget = 3.300 V
Info : clock speed 1000 kHz
Info : SWD DPIDR 0x1ba01477
Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints
Info : starting gdb server for stm32f1x.cpu on 3333
Info : Listening on port 3333 for gdb connections
尝试复位并暂停 CPU...
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x1ffff020 msp: 0x200001fc
正在解锁 Flash(自动解除读保护)...
Info : device id = 0x10036414
Info : flash size = 512kbytes
Flash 已成功解锁。
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x1ffff020 msp: 0x200001fc
执行全片擦除...
全片擦除成功!
重新锁定 Flash...
复位并启动芯片...
Error: stm32f1x.cpu -- clearing lockup after double fault
target halted due to debug-request, current mode: Handler HardFault
xPSR: 0x01000003 pc: 0x1ffff038 msp: 0x200001dc
Polling target stm32f1x.cpu failed, trying to reexamine
Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints
擦除完成!
shutdown command invoked

  注意,这里报了一个Error,出现了 clearing lockup after double faultHardFault,这在Flash被完全擦除后的复位过程中是非常常见的行为,因为芯片启动时找不到有效的向量表(中断向量为空或非法),导致进入 HardFault。
  通常为了安全,产线烧录后会对芯片进行加锁,但开发调试阶段为了方便,一般不加锁,将代码 catch {stm32f1x lock 0} 注释掉即可。

2 读取全片

  通过OpenOCD读取芯片全片Flash,同样以STM32F103ZET6单片机为例,创建文件 jlink_swd_stm32f103_read.cfg ,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# ============================================
# STM32F103 Flash读取
# ============================================

source [find interface/jlink.cfg]
transport select swd
adapter speed 4000
source [find target/stm32f1x.cfg]
reset_config none

# 设置输出文件名
set output_file "read_flash.bin"

# 主程序
proc read_flash_clean {} {
global output_file

echo "开始读取STM32F103 Flash..."

# 初始化
init
reset init
halt
sleep 100

# 检查并解锁Flash
echo "检查Flash状态..."
catch {stm32f1x unlock 0} unlock_result

if {[string first "unlocked" $unlock_result] != -1} {
echo "Flash已解锁,需要复位..."
# 解锁后需要复位
reset init
halt
sleep 200
}

# 直接读取整个Flash(512KB)
echo "正在读取Flash到: $output_file"
echo "大小: 512KB (0x08000000 - 0x0807FFFF)"

# 删除旧文件(如果存在)
catch {file delete $output_file}

# 读取整个Flash
dump_image $output_file 0x08000000 0x80000

# 验证读取
if {[file exists $output_file]} {
set file_size [file size $output_file]
echo "读取完成!"
echo "文件大小: $file_size 字节 ([expr $file_size/1024] KB)"

if {$file_size == 0x80000} {
echo "读取成功: $output_file"
echo ""

# 使用外部命令查看文件头(兼容性更好)
echo "使用外部命令检查文件头:"
catch {
# 使用hexdump命令(如果可用)
exec sh -c "hexdump -C -n 64 $output_file 2>/dev/null || xxd -g 1 -l 64 $output_file 2>/dev/null || od -x -N 64 $output_file 2>/dev/null"
}

} else {
echo "警告: 文件大小不正确"
}
} else {
echo "错误: 文件创建失败"
}

# 恢复芯片运行
reset run
echo "芯片已复位运行"
}

# 执行读取
read_flash_clean

# 关闭连接
shutdown

  执行以下命令读取全片,保存到bin文件。

1
openocd -f jlink_swd_stm32f103_read.cfg

  可以看到打印如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
╰─ openocd -f jlink_swd_stm32f103_read.cfg
Open On-Chip Debugger 0.11.0
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
开始读取STM32F103 Flash...
Info : J-Link V9 compiled May 7 2021 16:26:12
Info : Hardware version: 9.70
Info : VTarget = 3.300 V
Info : clock speed 1000 kHz
Info : SWD DPIDR 0x1ba01477
Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints
Info : starting gdb server for stm32f1x.cpu on 3333
Info : Listening on port 3333 for gdb connections
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x1ffff020 msp: 0x200001fc
检查Flash状态...
Info : device id = 0x10036414
Info : flash size = 512kbytes
Flash已解锁,需要复位...
target halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x1ffff020 msp: 0x200001fc
正在读取Flash到: read_flash.bin
大小: 512KB (0x08000000 - 0x0807FFFF)
读取完成!
文件大小: 524288 字节 (512 KB)
读取成功: read_flash.bin

使用外部命令检查文件头:
芯片已复位运行
shutdown command invoked

  脚本会先解锁Flash的读保护,然后将读取的内容写入bin文件,比如STM32F103ZET6的Flash大小为512KB,即为 $512 * 1024 = 524288$ 字节。如果芯片为加锁,不需要解锁步骤,则可将 catch {stm32f1x unlock 0} unlock_result 行注释。