STM32MP157-M4:OpenOCD 一键烧录踩坑实录——五个坑,从 Thumb 位到换行符(LiteOS-M 移植⑥)

本系列完全不依赖 Keil MDK,Windows 环境纯 GCC+Makefile 从零搭建 STM32MP157 M4 裸机 + LiteOS-M RTOS 工程。
面向 Windows 原生 arm-none-eabi-gcc + Makefile,不做 Keil。


0 前言:一个「看起来 4 行命令」的脚本,踩了 5 个坑

前一篇(⑤·续篇)恢复了 SVC + PendSV 标准启动,多任务调度跑起来了。剩下的最后一件事,就是把「下载固件 + 运行」做成双击就能跑的一键脚本,而不是每次开 GDB 手动敲命令。

第⑤篇结尾用的是 STM32CubeProgrammer(GUI 点点点),不方便自动化。我换成 OpenOCD 命令行,写了个 flash-m4.bat。理想状态就 4 步:

连 ST-Link → 下载 elf → 设 SP/PC → resume

结果这 4 步,我踩穿了 5 个坑,而且一个比一个隐蔽——最后一个「bash 能跑、双击 bat 不能跑」的反直觉坑,差点让我以为硬件坏了。

读完本篇你会理解:

  • OpenOCD 的 reg pc 和 GDB 的 set $pc 为什么行为不一样(Thumb 位)
  • STM32MP157 工程模式下,M4 复位后从哪读向量表(RETRAM 跳板的由来)
  • 为什么「手动命令能跑、双击脚本不能跑」要查换行符而不是查硬件

1 背景:从 CubeProgrammer 到 OpenOCD 脚本

第⑤篇用 CubeProgrammer 烧录:选文件、选核、点 Download。能用,但每次要点一堆,不适合反复调试。

换成 OpenOCD 命令行,核心就是一条命令链:

openocd -f board_stm32mp157_m4.cfg ^
  -c "init" -c "targets stm32mp15x.cm4" -c "halt" ^
  -c "load_image build/m4_liteos.elf" ^
  -c "reg sp 0x10060000" -c "reg pc 0x1000ddf5" -c "resume"

看起来人畜无害。但这里埋着前两个坑。


2 五个坑逐个拆解

坑 1(最核心):reg pc 写奇数地址,不会自动置 Thumb 位

现象resume 之后灯不闪,但同样的固件用 GDB 调试却能闪。

排查:对比两种启动方式,唯一实质差异是设 PC 的方式——

方式 命令 结果
GDB set $pc = Reset_Handler ✅ 灯闪
OpenOCD reg pc 0x1000ddf5 ❌ 灯不闪

Reset_Handler = 0x1000ddf5,注意这个地址是奇数(bit0=1)。在 Cortex-M 上,函数入口地址的 bit0 是 Thumb 状态位:bit0=1 表示这条代码是 Thumb 指令(Cortex-M 只支持 Thumb)。

  • GDB 的 set $pc = Reset_Handler,会智能识别符号是 Thumb 函数,自动把 xPSR 的 T 位置 1。
  • OpenOCD 的 reg pc 0x1000ddf5,只是把 PC 寄存器写成这个值,不会去动 xPSR 的 T 位。

结果:T 位没置 1,M4 以为自己在跑 ARM 状态(32 位指令),实际代码是 Thumb 指令 → 直接取指异常跑飞。没有 HardFault 现场(因为根本没正常执行过一条指令),所以特别难查。

修复:设完 PC,手动补一句把 T 位置 1:

-c "reg pc 0x1000ddf5" ^
-c "reg xpsr 0x01000000" ^

0x01000000 的 bit24 就是 xPSR 的 T 位。

这个坑是「OpenOCD 脚本」专属。用 GDB 的人永远不会遇到(GDB 帮你处理了),所以网上很少有人提。


坑 2(最乌龙):寄存器名是 xpsr 全小写,不是 xPSR

照着写了 reg xPSR 0x01000000(大写 PSR),结果:

Error: register xPSR not found in current target

查 OpenOCD 源码(armv7m.c)才确认,寄存器名定义是全小写

{ ARMV7M_XPSR, "xpsr", 32, REG_TYPE_INT, "general", ... }

改回 reg xpsr 就好了。

教训:OpenOCD 的寄存器名、命令名都对大小写敏感,报 not found 先怀疑拼写,别急着怀疑硬件。


坑 3(平台特有):shutdown 复位 M4,而工程模式固定从 RETRAM 读向量

现象:修完坑 1、坑 2 后,手动跑命令(去掉 shutdown)灯能闪了;但双击 bat(带 shutdown)灯闪一下就灭。

根因:STM32MP157 的 M4 和普通 STM32 不一样——它复位后固定从 RETRAM(0x00000000)读向量表,而不是从你固件所在的 SRAM(0x10000000)读。

shutdown 断开 OpenOCD 与 ST-Link 的调试连接时,会把 M4 复位。复位后:

在这里插入图片描述

这就是「GDB 调试能闪(连接一直保持,不复位)、一键脚本闪一下就灭(shutdown 复位)」的根本原因。

修复(RETRAM 跳板):M4 复位只从 RETRAM 读前 8 字节(初始 SP + Reset 入口),之后 SystemInit 会把 VTOR 重定位回 SRAM。所以只要在 RETRAM 开头写上这两条,复位后就能跳回 SRAM 固件:

-c "load_image build/m4_liteos.elf" ^
-c "mww 0x00000000 0x10060000" ^    ← RETRAM[0] = 初始 SP(_estack)
-c "mww 0x00000004 0x1000ddf5" ^    ← RETRAM[4] = Reset_Handler 入口

复位后:M4 从 RETRAM 读 SP+Reset → 跳到 SRAM 的 Reset_Handler → 正常调度 → 灯持续闪。

关键认知:STM32MP157 工程模式(拨码 001)下,M4 复位后固定从 RETRAM 启动,没有「从 SRAM 启动」的寄存器开关(RCC_MP_BOOTCR 里的 MCU_BEN 只是 STANDBY 唤醒用,跟启动地址无关)。


坑 4(陷阱):RCC_MP_GCR 是 A7 域受保护寄存器,别写它

排查坑 3 时,我一度以为要设 BOOT_MCU 位让 M4 复位后「脱离 hold 状态」,于是写:

-c "mww 0x5000010C 0x00000001"    ← 想设 RCC_MP_GCR.BOOT_MCU

结果报错:

Error: Failed to write memory at 0x50000110

RCC_MP_GCR(地址 0x5000010CBOOT_MCU 在 bit0)是 A7(MPU)域的受保护寄存器,M4 侧的调试接口根本写不进去。

关键:这个寄存器根本不需要写。坑 3 已经证明了——M4 复位后会自动去 RETRAM 读向量,不需要 BOOT_MCU 那一脚。直接删掉这行,RETRAM 跳板就够了。

教训:STM32MP157 是双核,RCC 里带 MP_ 前缀的寄存器基本都归 A7 管,M4 侧别碰。


坑 5(最隐蔽):bat 换行符被改成了 LF,cmd 解析 for /f 失败

现象:所有命令都修对了,手动在 bash 里敲能闪,但双击 flash-m4.bat 就是不闪

这是最反直觉的一个坑。当时的 OpenOCD 输出:

pc (/32): 0x1000ddf5        ← 地址居然是对的?
xpsr (/32): 0x01000000      ← T 位也对了
shutdown command invoked

所有检查点都过了,灯还是不闪。直到我发现一个细节:bat 里用 for /f 从 elf 动态解析入口地址:

for /f "tokens=2" %%A in ('arm-none-eabi-readelf -s build\m4_liteos.elf ^| findstr /R /C:" Reset_Handler$"') do set ENTRY=0x%%A

根因:我在编辑 bat 时,编辑工具把文件从 Windows 的 CRLF 换行改成了 Unix 的 LF 换行。Windows 的 cmdfor /f 命令和 ^ 续行符的解析对 LF 敏感——LF 换行导致 for /f 解析失败,%ENTRY% 变成空值

于是 reg pc %ENTRY% 实际执行的是 reg pc(空地址),M4 跳到错误位置跑飞。

而手动命令是直接敲在 bash 里的(硬编码地址),完全不经过 bat 的换行符解析,所以能闪。

这就是「bash 能跑、双击 bat 不能跑」的真相——不是硬件,是换行符。

修复:把 bat 归一化为 CRLF:

python -c "d=open('flash-m4.bat','rb').read(); d=d.replace(b'\r\n',b'\n').replace(b'\n',b'\r\n'); open('flash-m4.bat','wb').write(d)"

验证方法(以后改完 bat 都该跑一下):

python -c "d=open('flash-m4.bat','rb').read(); print('CRLF:', d.count(b'\r\n'), '孤立LF:', d.count(b'\n')-d.count(b'\r\n'))"

「孤立 LF」大于 0 就是问题。

教训:在 Windows 工程里改 .bat 后,务必确认是 CRLF 换行。这个坑的迷惑性在于——OpenOCD 输出看起来全对(地址对、T 位对),问题却出在更上游的「地址到底有没有被正确解析」上。


3 最终的 flash-m4.bat 完整命令链

踩完五个坑,最终脚本的核心命令链长这样:

for /f "tokens=2" %%A in ('arm-none-eabi-readelf -s build\m4_liteos.elf ^| findstr /R /C:" Reset_Handler$"') do set ENTRY=0x%%A

"%OCD_BIN%" ^
  -s "%OCD_SCRIPTS%" ^
  -f openocd/board_stm32mp157_m4.cfg ^
  -c "init" ^
  -c "targets stm32mp15x.cm4" ^
  -c "halt" ^
  -c "load_image build/m4_liteos.elf" ^
  -c "mww 0x00000000 0x10060000" ^        @ RETRAM[0]=SP(跳板,坑3)
  -c "mww 0x00000004 %ENTRY%" ^           @ RETRAM[4]=Reset(跳板,坑3)
  -c "reg sp 0x10060000" ^
  -c "reg pc %ENTRY%" ^
  -c "reg xpsr 0x01000000" ^              @ T 位(坑1,注意全小写坑2)
  -c "resume" ^
  -c "shutdown"

五个坑对应的修复,全在这 11 行里了。


4 验证结果

验证项 结果
%ENTRY% 解析 0x1000ddf5(正确,坑 5 修复后)
reg pc pc (/32): 0x1000ddf5
reg xpsr xpsr (/32): 0x01000000(T 位 = 1)
RETRAM 跳板 mww 0x00000000 / 0x00000004 无报错
shutdown M4 从 RETRAM 跳回 SRAM,灯持续闪
最终效果 LED0(红)500ms、LED1(绿)200ms 独立闪烁

双击 flash-m4.bat,一个黑窗口跑完自动关闭,板子上的 LED 红绿交替闪——这就是「一键烧录」该有的样子。


5 总结:排查方法论比坑本身更重要

五个坑,按「隐蔽程度」排个序:

# 迷惑性 一句话识别
1 Thumb 位 ★★★★ GDB 能闪、OpenOCD 脚本不能
2 xpsr 大小写 register not found
3 shutdown 复位 + RETRAM ★★★ 手动能闪、带 shutdown 闪一下就灭
4 RCC_MP_GCR 受保护 ★★ 写内存报 Failed to write
5 bat 换行符 ★★★★★ bash 能跑、双击 bat 不能跑

几条通用方法论,以后调试嵌入式都适用:

  1. 「环境 A 能跑、环境 B 不能跑」先怀疑环境差异,不是固件。这次「bash 能跑 / cmd 不能跑」就是换行符;「GDB 能跑 / OpenOCD 不能跑」就是 Thumb 位。固件本身往往没问题。

  2. OpenOCD 脚本和 GDB 不是等价的reg pc 不会帮你置 Thumb 位、monitor 命令和直接 -c 命令也有差异,别想当然。

  3. 双核芯片(STM32MP157)的寄存器要分域。带 MP_ 前缀的 RCC 寄存器归 A7,M4 侧别碰;M4 复位后从哪读向量,也要查芯片手册而不是想当然。

  4. 改完 bat/.ps1 一定要看换行符。这是 Windows 开发里最隐蔽、最反直觉的坑,for /f^ 续行都对 LF 敏感。


系列目录
环境准备:Windows GCC+Make 搭建
源码下载、裁剪与工程搭建
os_config 三板斧详解:target_config.h / los_builddef.h / los_printf.h
Makefile 逐段拆解:GCC+Makefile 编译系统
链接脚本 .lds 解析 + 编译烧录 + LED 任务跑起来
⑤·续篇 SVC + PendSV 标准启动流程,恢复完整多任务调度
本篇:OpenOCD 一键烧录踩坑实录——五个坑,从 Thumb 位到换行符

欢迎评论区交流移植经验,把复杂的讲简单,持续更新中。


标签#STM32MP157 #LiteOS-M #OpenOCD #GCC #Makefile #一键烧录 #Thumb位 #Cortex-M #嵌入式调试 #踩坑实录

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐