前言

  • 通常情况下,我们使用 STM32CubeProgrammer 配合 STLink 兼容调试器进行单片机 Flash 的读写,包括修改标志位等等操作。但在使用其他调试器例如 CMSIS-DAP 的情况下,我们只能使用 OpenOCD 等工具进行烧录和调试,而这些工具本身并不提供一键修改标志位的命令或是接口。

  • 目标:用 OpenOCD 将一块全新的 STM32G4 的 nSWBOOT0 标志位写为 0 来关闭 BOOT0 引脚选取启动模式的功能,从而实现引脚复用。附:STM32G4 的启动模式选择表STM32G4 的启动模式选择表

  • 事实上,可以通过 STM32 HAL 库在用户程序中对 FLASH 标志位进行修改,这个方法仅适用于 BOOT0 引脚复用为默认为低电平的外部输入或是外设输出,也就是程序可以烧录进去运行的情况。我这块板子的情况是,BOOT0 复用为 I2C 引脚,并且接入了 4.7kΩ 的硬件上拉,导致全新的 MCU 一上电运行就进入了 BOOT0=1 的模式,即 Bootloader 模式,用户程序无法运行。

  • 此处还有一个判断程序有没有跑到用户程序段的方法,在 Bootloader 模式下,调试端口仍然打开,可以通过 GDB 等工具查看当前的 PC(Program Counter)寄存器地址,再与该系列 MCU 规定的用户 Flash 起始地址比较,若当前 PC 指向起始地址前,就代表程序还没有进入用户段。

  • 解决方案1:暂时先卸掉上拉电阻,烧入修改标志位的程序,确定标志位已修改之后再换上电阻。

  • 解决方案2:直接使用调试器,通过 SW 调试口对标志位进行更改。


理论

对于不同型号的 STM32,其 Flash Interface 寄存器的位置以及偏移量、解锁 KEY 可能不尽相同,这里以 STM32G431 为例。

  1. 查阅 STM32G4 参考手册(RM0440) 84 页 "STM32G4 Series memory map and peripheral register boundary addresses"(其他型号的表名可能有所不同,技巧:在 PDF 中搜索 "register boundary"),找到 "Flash Interface" 外设,其起始地址为 0x40022000
    STM32G4 外设地址边界表

  2. 点击右侧的 Flash register map,跳转到 Flash 寄存器的偏移量记录表,得到 FLASH_KEYR 寄存器的基地址偏移量为 0x08,FLASH_OPTKEYR 为 0x0C,FLASH_SR 为 0x10,FLASH_CR 为 0x14,以及标志位所在的 FLASH_OPTR = 0x20。各寄存器的地址为外设起始地址+偏移量,例如 FLASH_KEYR 寄存器的地址为 0x40022000 + 0x08 = 0x40022008
    Flash 寄存器偏移量表

  3. 来到第 164 页 4.4.2 节: Option bytes programming。这一节告诉我们,在单片机复位之后,FLASH_CR 寄存器中关于标志位更改的控制位处于锁定状态。在对标志位区域进行操作时,必须先按照步骤对标志位区域进行解锁。解锁标志位区域

    1. 按照解锁 Flash Memory 的步骤解锁 FLASH_CR 寄存器

      • 跳转到手册 5.3.5 节: Flash program and erase operations,得到解锁 FLASH_CR 的方式:按顺序向 FLASH_KEYR 写入两个 KEY:0x456701230xCDEF89AB,错误的顺序或者不正确的 KEY 会导致 FLASH_CR 上锁并触发 HardFault 中断,复位可清除。(不同的 STM32 型号,可能会有不同的 KEY)解锁 Flash

      • 根据 FLASH_CR 寄存器的定义,Bit 31 为寄存器锁 LOCK 位,Bit 30 为标志位锁 OPTLOCK 位。也就是说我们需要先解锁 FLASH_CR,再解锁 FLASH_OPTR,直到 FLASH_CR 的 Bit 31 和 Bit 30 都为 0,即代表解锁完成,可以进行标志位写入操作。整个过程中,可以采用调试器的读取命令观察 FLASH_CR 的变化。FLASH_CR 寄存器定义

      • 解锁 FLASH_CR 后,按顺序向 FLASH_OPTKEYR 写入两个 KEY:0x08192A3B0x4C5D6E7F ,来解锁 FLASH_OPTR。(不同的 STM32 型号,可能会有不同的 KEY)

  4. 解锁完成后,我们准备对标志位进行修改:

  5. 在进行 Flash 操作前,先检查 FLASH_SR 的 BSY 位是否为 0,确定当前没有正在进行的读写操作。

  6. 对标志位寄存器(涵盖了 FLASH_OPTR、FLASH_PCROP1SR、FLASH_PCROP1ER、FLASH_WRP1AR、FLASH_WRP1BR)进行修改。

  7. 修改完后,设置 FLASH_CR 的 OPTSTRT 位(Bit 17)为 1,并等待 FLASH_SR 的 BSY 位复位到 0,即完成标志位的修改。

  8. 修改之后,有两种使得设置的标志位生效的方式:

  9. 设置 FLASH_CR 的 OBL_LAUNCH 位(Bit 27)为 1

  10. 断开目标板电源,再上电,复位系统


实践

  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
  1. Telnet 操作

    • 这里我们不使用 GDB,而是使用更加底层的 Telnet,连接工具可以用 PuTTY,也可以用 Windows 自带的 telnet,OpenOCD 的 Telnet 默认地址为 localhost,端口为 4444。这里我使用 PuTTY 进行演示,如图。PuTTY 配置 OpenOCD 连接成功

    • 连接好之后,需要用到以下几个 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,返回值如图
    mdw 命令示例

  1. 操作步骤
    1. 复位目标板,并连接 OpenOCD 和 Telnet。
    2. 执行一次 reset init
    3. (可选) 读取 FLASH_CR 寄存器的值,mdw 0x40022014,如图,证明 LOCK 和 OPTLOCK 位均上锁,需要进行解锁。
      FLASH_CR_1
    4. mww 0x40022008 0x45670123,写入解锁 LOCK 的 KEY1
    5. mww 0x40022008 0xCDEF89AB,写入解锁 LOCK 的 KEY2
    6. (可选) 读取 FLASH_CR 寄存器的值,mdw 0x40022014,如图,证明 LOCK 位已解锁。
      FLASH_CR_2
    7. mww 0x4002200C 0x08192A3B,写入解锁 OPTLOCK 的 KEY1
    8. mww 0x4002200C 0x4C5D6E7F,写入解锁 OPTLOCK 的 KEY2
    9. (可选) 读取 FLASH_CR 寄存器的值,mdw 0x40022014,如图,证明所有锁定位均解锁。
      FLASH_CR_3
    10. 我们需要更改的 nSWBOOT0 位于 FLASH_OPTR(offset = 0x20) 的 Bit 26,为确保其他位不被更改,先用 mdw 0x40022020查看寄存器原值为 0xFFEFF8AAFLASH_OPTR 原值
      如图,可以看到 Bit 26 位为 1,我们需要将它更改为 0,得到修改后的寄存器值:0xFBEFF8AA
      FLASH_OPTR 修改值
    11. mww 0x40022020 0xFBEFF8AA,设置标志位
    12. 等待 mdw 0x40022010 为 0 后,设置 FLASH_CR 的 OPTSTRT 位(Bit 17)为 1:mww 0x40022014 0x00020000
    13. 等待 mdw 0x40022010 为 0 后,操作结束,通过断电来复位目标板。输入 exit 退出 telnet session。

至此,我们已经通过 OpenOCD 完成了对目标板标志位的更改。

最后修改:2024 年 02 月 10 日