前言
-
通常情况下,我们使用 STM32CubeProgrammer 配合 STLink 兼容调试器进行单片机 Flash 的读写,包括修改标志位等等操作。但在使用其他调试器例如 CMSIS-DAP 的情况下,我们只能使用 OpenOCD 等工具进行烧录和调试,而这些工具本身并不提供一键修改标志位的命令或是接口。
-
目标:用 OpenOCD 将一块全新的 STM32G4 的 nSWBOOT0 标志位写为 0 来关闭 BOOT0 引脚选取启动模式的功能,从而实现引脚复用。附:STM32G4 的启动模式选择表
-
事实上,可以通过 STM32 HAL 库在用户程序中对 FLASH 标志位进行修改,这个方法仅适用于 BOOT0 引脚复用为默认为低电平的外部输入或是外设输出,也就是程序可以烧录进去运行的情况。我这块板子的情况是,BOOT0 复用为 I2C 引脚,并且接入了 4.7kΩ 的硬件上拉,导致全新的 MCU 一上电运行就进入了 BOOT0=1 的模式,即 Bootloader 模式,用户程序无法运行。
-
此处还有一个判断程序有没有跑到用户程序段的方法,在 Bootloader 模式下,调试端口仍然打开,可以通过 GDB 等工具查看当前的 PC(Program Counter)寄存器地址,再与该系列 MCU 规定的用户 Flash 起始地址比较,若当前 PC 指向起始地址前,就代表程序还没有进入用户段。
-
解决方案1:暂时先卸掉上拉电阻,烧入修改标志位的程序,确定标志位已修改之后再换上电阻。
-
解决方案2:直接使用调试器,通过 SW 调试口对标志位进行更改。
理论
-
查阅 STM32G4 参考手册(RM0440) 84 页 "STM32G4 Series memory map and peripheral register boundary addresses"(其他型号的表名可能有所不同,技巧:在 PDF 中搜索 "register boundary"),找到 "Flash Interface" 外设,其起始地址为
0x40022000
-
点击右侧的 Flash register map,跳转到 Flash 寄存器的偏移量记录表,得到 FLASH_KEYR 寄存器的基地址偏移量为
0x08
,FLASH_OPTKEYR 为0x0C
,FLASH_SR 为0x10
,FLASH_CR 为0x14
,以及标志位所在的 FLASH_OPTR =0x20
。各寄存器的地址为外设起始地址+偏移量,例如 FLASH_KEYR 寄存器的地址为0x40022000 + 0x08 = 0x40022008
-
来到第 164 页 4.4.2 节: Option bytes programming。这一节告诉我们,在单片机复位之后,FLASH_CR 寄存器中关于标志位更改的控制位处于锁定状态。在对标志位区域进行操作时,必须先按照步骤对标志位区域进行解锁。
-
按照解锁 Flash Memory 的步骤解锁 FLASH_CR 寄存器
-
跳转到手册 5.3.5 节: Flash program and erase operations,得到解锁 FLASH_CR 的方式:按顺序向 FLASH_KEYR 写入两个 KEY:
0x45670123
和0xCDEF89AB
,错误的顺序或者不正确的 KEY 会导致 FLASH_CR 上锁并触发 HardFault 中断,复位可清除。(不同的 STM32 型号,可能会有不同的 KEY) -
根据 FLASH_CR 寄存器的定义,Bit 31 为寄存器锁 LOCK 位,Bit 30 为标志位锁 OPTLOCK 位。也就是说我们需要先解锁 FLASH_CR,再解锁 FLASH_OPTR,直到 FLASH_CR 的 Bit 31 和 Bit 30 都为 0,即代表解锁完成,可以进行标志位写入操作。整个过程中,可以采用调试器的读取命令观察 FLASH_CR 的变化。
-
解锁 FLASH_CR 后,按顺序向 FLASH_OPTKEYR 写入两个 KEY:
0x08192A3B
和0x4C5D6E7F
,来解锁 FLASH_OPTR。(不同的 STM32 型号,可能会有不同的 KEY)
-
-
-
解锁完成后,我们准备对标志位进行修改:
-
在进行 Flash 操作前,先检查 FLASH_SR 的 BSY 位是否为 0,确定当前没有正在进行的读写操作。
-
对标志位寄存器(涵盖了 FLASH_OPTR、FLASH_PCROP1SR、FLASH_PCROP1ER、FLASH_WRP1AR、FLASH_WRP1BR)进行修改。
-
修改完后,设置 FLASH_CR 的 OPTSTRT 位(Bit 17)为 1,并等待 FLASH_SR 的 BSY 位复位到 0,即完成标志位的修改。
-
修改之后,有两种使得设置的标志位生效的方式:
-
设置 FLASH_CR 的 OBL_LAUNCH 位(Bit 27)为 1
-
断开目标板电源,再上电,复位系统
实践
- OpenOCD 的命令操作
- 请确保你已经有对应你当前 MCU 型号以及调试器型号的 openocd.cfg,我使用的是 STM32 For VSCode,插件已经自动帮我配置好。(PS:即使启动方式不正确,OpenOCD 的烧录过程也能正常进行)
- 运行 OpenOCD:
openocd.exe -f ./openocd.cfg
,这里只是命令示例,实际情况根据你的路径或者配置文件不同而不同,有可能会是 -f 调试器.cfg -f 板子.cfg 这样。此时 OpenOCD 就会输出调试器电压、目标板电压等信息,且等待 GDB 通过 3333 端口连接它了。Info : Listening on port 3333 for gdb connections
-
Telnet 操作
-
这里我们不使用 GDB,而是使用更加底层的 Telnet,连接工具可以用 PuTTY,也可以用 Windows 自带的 telnet,OpenOCD 的 Telnet 默认地址为 localhost,端口为 4444。这里我使用 PuTTY 进行演示,如图。
-
连接好之后,需要用到以下几个 OpenOCD 命令 (https://openocd.org/doc/html/General-Commands.html):
-
reset init
用于复位目标板(Immediately halt the target, and execute the reset-init script)
-
-
mww addr word
用于向addr
地址写入 32 位的word
。 -
mdw addr
用于显示addr
地址的 32 位值。 -
例如:要读取 FLASH_SR 寄存器的值,我们通过偏移量计算得到地址为
0x40022010
,输入mdw 0x40022010
,返回值如图
- 操作步骤
- 复位目标板,并连接 OpenOCD 和 Telnet。
- 执行一次
reset init
- (可选) 读取 FLASH_CR 寄存器的值,
mdw 0x40022014
,如图,证明 LOCK 和 OPTLOCK 位均上锁,需要进行解锁。
mww 0x40022008 0x45670123
,写入解锁 LOCK 的 KEY1mww 0x40022008 0xCDEF89AB
,写入解锁 LOCK 的 KEY2- (可选) 读取 FLASH_CR 寄存器的值,
mdw 0x40022014
,如图,证明 LOCK 位已解锁。
mww 0x4002200C 0x08192A3B
,写入解锁 OPTLOCK 的 KEY1mww 0x4002200C 0x4C5D6E7F
,写入解锁 OPTLOCK 的 KEY2- (可选) 读取 FLASH_CR 寄存器的值,
mdw 0x40022014
,如图,证明所有锁定位均解锁。
- 我们需要更改的 nSWBOOT0 位于 FLASH_OPTR(offset = 0x20) 的 Bit 26,为确保其他位不被更改,先用
mdw 0x40022020
查看寄存器原值为0xFFEFF8AA
如图,可以看到 Bit 26 位为 1,我们需要将它更改为 0,得到修改后的寄存器值:0xFBEFF8AA
mww 0x40022020 0xFBEFF8AA
,设置标志位- 等待
mdw 0x40022010
为 0 后,设置 FLASH_CR 的 OPTSTRT 位(Bit 17)为 1:mww 0x40022014 0x00020000
- 等待
mdw 0x40022010
为 0 后,操作结束,通过断电来复位目标板。输入exit
退出 telnet session。
至此,我们已经通过 OpenOCD 完成了对目标板标志位的更改。