STM32MP157-M4:OpenOCD 一键烧录踩坑实录——五个坑,从 Thumb 位到换行符(LiteOS-M 移植⑥)
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(地址 0x5000010C,BOOT_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 的 cmd 对 for /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 不能跑 |
几条通用方法论,以后调试嵌入式都适用:
-
「环境 A 能跑、环境 B 不能跑」先怀疑环境差异,不是固件。这次「bash 能跑 / cmd 不能跑」就是换行符;「GDB 能跑 / OpenOCD 不能跑」就是 Thumb 位。固件本身往往没问题。
-
OpenOCD 脚本和 GDB 不是等价的。
reg pc不会帮你置 Thumb 位、monitor命令和直接-c命令也有差异,别想当然。 -
双核芯片(STM32MP157)的寄存器要分域。带
MP_前缀的 RCC 寄存器归 A7,M4 侧别碰;M4 复位后从哪读向量,也要查芯片手册而不是想当然。 -
改完 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#嵌入式调试#踩坑实录
更多推荐



所有评论(0)