STM32MP157 M4开发实战:解决OpenOCD二次烧写失败,聊聊 unknown‑state 警告
STM32MP157 M4开发实战:解决OpenOCD二次烧写失败,聊聊 unknown‑state 警告
适用场景:板子拨码切到M4单独启动,程序跑在M4内部256KB内存里,用的LiteOS‑M系统,不使用Flash,ST‑Link + OpenOCD一键下载。
STM32MP157是个双核芯片,一个A7大核,一个M4小核。
很多人拿OpenOCD给M4下载程序的时候,会碰到两个头疼事:
- 第一次下载一切正常;不拔电不断电,执行第二次下载之后,LED要么常亮、要么彻底熄灭,就是不会按照预期闪烁,必须断电重启板子才恢复正常。
- 控制台总会蹦出来一条黄色警告:
Warn : target was in unknown state when halt was requested。
这篇就讲真实踩坑过程:把二次烧写异常这个问题彻底修好;至于那条警告,没法彻底删掉,我会讲明白为什么,以及怎么做到警告不影响干活,附带可以直接复制使用的Windows一键下载脚本。
1. 遇到了什么问题
1.1 最开始写的一键下载脚本
一开始想法很简单:让调试器把M4停下来,把程序拷进内存,手动给栈、程序入口地址,再命令M4跑起来。
"%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 "reg sp 0x10040000" ^
-c "reg pc 0x100006b1" ^
-c "resume" ^
-c "shutdown"
这是最开始的朴素写法,没有用软复位,所以第二次下载会错乱(详见 1.2)。下面最终方案会换成 RETRAM 伪向量表 + VECTRESET。
1.2 两个麻烦现象
- 二次下载运行异常
板子上电,第一次下载,LED正常闪烁,程序跑的好好的。
不关机、不断电,直接跑第二次下载,工具显示“下载完成”,但M4行为错乱:LED要么一直亮,要么完全灭掉,就是不会周期性闪烁。只有拔掉电源重新上电,程序才恢复正常。
大白话原因:M4正在全速跑程序的时候,OpenOCD的“halt停机命令”不一定能把它拽停。一旦没停下来,后面设置栈、设置程序入口、让它运行的指令全部等于白说。内存里新旧代码混杂,GPIO状态保留上一次的残留,就出现LED常亮或者熄灭这种错乱现象。
- 总是出现 unknown state 警告
Warn : target was in unknown state when halt was requested
当板子设置成M4独立启动模式,芯片有安全保护。调试器读不到M4内部状态,OpenOCD心里没底,就打出这条警告。
划重点:警告 ≠ 报错。很多新手看见黄色警告,就以为下载失败。本文方案不能消除这条警告,但可以保证:就算有警告,程序照样正常下载、正常运行。
2. 试过好几种办法,全都不好用
尝试1:直接用 OpenOCD 的 reset halt
想着直接让工具复位再停机,万事大吉。
结果:会报调试寄存器读取错误,调试接口反复重连,虽然程序偶尔能跑,但链路不稳定,有概率下载直接失败。
M4单独启动模式下,OpenOCD完整硬件复位会和芯片安全机制冲突。
尝试2:写内核寄存器强制把M4按住停下来
绕开工具自带命令,直接往芯片内核寄存器写值,暴力强制M4暂停。
-c "mww 0xE000EDF0 0xA05F0001"
结果:时灵时不灵。
如果M4正在处理中断、或者程序跑飞卡死,这个强制停机指令有可能直接被无视,依旧停不下来。内存旧数据还在,LED依旧错乱,治标不治本。
尝试3:软件互相配合握手
约定一块内存,固件运行的时候检测这个标记;标记生效就自己死循环停下来,方便调试器接管。
结果:太麻烦,不适合一键脚本。
必须依赖上一版旧程序乖乖配合,时序稍微不对就翻车,自动化调试用着很难受。
3. 最终靠谱方案:AIRCR软复位 + RETRAM伪向量表
核心思路(人话版):
不要让调试器去控制M4启动、不要命令M4“你现在给我跑起来”。调试器只负责写内存,剩下全部交给芯片自己的硬件规则来干活。
硬件原理通俗解释
1. RETRAM内存地址:0x00000000
M4每次复位重启,硬件第一件事,固定去 0x00000000 这个地址读两个东西:栈顶地址、程序启动入口地址。
注意:这不是只读ROM,是一块可以改写的内存(它是 M4 内部 SRAM 的一段别名,
0x00000000与0x10000000指向同一块物理内存,写哪个都行)。M4独立启动模式下,ST‑Link调试器可以往这里写数据。我们就手动在这里“伪造一份启动参数”,告诉芯片:重启之后去哪个地址跑新程序。
顺带讲清:上面这个“写内存伪造启动参数”的动作,靠的是 mww 命令。
脚本里反复出现 mww,新手常看不懂:
mww = Memory Write Word(OpenOCD 专属命令)
- 功能:通过调试器直接对芯片内存 / 寄存器写入 32 位数据,不需要运行芯片内部任何代码;
- 语法:
mww 地址 数值; - 等价于 C 语言:
*(volatile uint32_t *)地址 = 数值;(直接指针操作寄存器)。
所以我们能用 mww 0x00000000 ... 伪造 RETRAM 向量表、用 mww 0xE000ED0C ... 触发软复位,全靠这个命令“绕过 CPU 直接写硬件”。
2. AIRCR寄存器,给M4发软复位信号
往内核寄存器 0xE000ED0C 写入 0x05FA0001(VECTRESET),就相当于给 M4 发一个只复位内核的软重启指令。
这里有个 90% 教程都会写错的致命细节:
0xE000ED0C是 M4 内核的 AIRCR 系统控制寄存器,写不同值含义天差地别:
| 写入值 | 名称 | 复位范围 | 对本板 M4 的影响 |
|---|---|---|---|
0x05FA0001 |
VECTRESET(bit0) | 只复位 Cortex-M4 内核(PC、寄存器、NVIC pending、SysTick、Fault) | ✅ 不动 RCC、保留 BOOT_MCU → M4 复位后仍被硬件释放,自动从 SRAM 向量表干净启动 |
0x05FA0004 |
SYSRESETREQ(bit2) | 全局系统复位(含 RCC / 外设复位) | ❌ 清掉 BOOT_MCU → M4 复位后不被释放 → 灯不闪 |
⚠️ 一句话判断:写完
0x05FA0004灯不闪,就说明你用的是 SYSRESETREQ 而不是 VECTRESET。 把值改成0x05FA0001,两者只差一个十六进制位,效果天差地别。
⚠️ 关键陷阱(第二个坑):VECTRESET 之后内核并不会自动跑起来。
OpenOCD 连接 M4 后,默认开启DEMCR寄存器的VC_CORERESET(复位即停机)。你发 VECTRESET,M4 刚复位完,立刻被调试硬件拉住停机,停在pc=0x00000008、msp=0x00000100,还没来得及去0x00000000读向量表。对应日志:[stm32mp15x.cm4] halted due to debug-request, current mode: Thread xPSR: 0x01000000 pc: 0x00000008 msp: 0x00000100此时程序不跑、灯不亮,不是脚本错了,是调试器霸占着核。所以必须紧接着显式发
resume让它真正跑起来,否则两灯全灭。shutdown只是“礼貌断开调试会话”,不是“释放内核让它跑”——真正的启动靠resume。
黄金顺序(记牢这个,二次下载永不翻车):
VECTRESET(0x05FA0001) 软复位
→ 核停在 pc=0x00000008(halt-on-reset,调试器拉住)
→ reg sp / pc / xpsr (双保险,也可靠 RETRAM 自动取)
→ resume ★ 没这句核永远停着,两灯不亮
→ sleep 200
→ shutdown (最后干净断开调试器)
⚠️非常容易踩的隐形大坑!
这个软复位,只重启CPU内核,不会清空M4片内SRAM内存!
内存里还残留上一次运行程序的旧垃圾数据,包括GPIO引脚电平、全局变量。如果程序没有及时把全局变量清零,就会出现LED状态错乱、时好时坏、莫名其妙死机。
解决办法:在程序最开头Reset_Handler里面,手动把BSS全局变量区域全部清零。
extern unsigned int __bss_start__, __bss_end__;
void Reset_Handler(void)
{
unsigned int *p = &__bss_start__;
for (; p < &__bss_end__; p++){
*p = 0U;
}
// 再往下:硬件初始化、LiteOS‑M系统初始化、进入main函数
}
极端情况:如果M4已经严重跑飞锁死,软复位也救不了,只能手动断电重启板子。
完整可直接使用 flash‑m4.bat
使用前提:
- Windows系统把arm‑none‑eabi工具链加到系统环境变量PATH;
- 开发板拨码设置为M4独立启动;
- 脚本里删掉所有
mww 0x50000xxx这类指令,安全模式下写这些地址会直接报错;- 本脚本放在工程
tools/目录下运行(cd /d %~dp0\..会自动切到工程根,这样build\与openocd/相对路径才能正确解析)。
@echo off
cd /d %~dp0\..
SET "OCD_DIR=D:\tools\xpack-openocd-0.12.0-5-win32-x64\xpack-openocd-0.12.0-5"
SET "OCD_BIN=%OCD_DIR%\bin\openocd.exe"
SET "OCD_SCRIPTS=%OCD_DIR%\openocd\scripts"
:: 自动读取 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
"%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 0x10040000" ^
-c "mww 0x00000004 %ENTRY%" ^
-c "mww 0xE000ED0C 0x05FA0001" ^
-c "sleep 100" ^
-c "halt" ^
-c "reg sp 0x10040000" ^
-c "reg pc %ENTRY%" ^
-c "reg xpsr 0x01000000" ^
-c "resume" ^
-c "sleep 200" ^
-c "shutdown"
echo [DONE] M4 Running Success !
pause
跑完一条标准日志怎么看(确认真的成功了)
正常跑完上面脚本,控制台会打出类似这样的日志:
Info : Listening on port 3333 for gdb connections
Info : [stm32mp15x.cm4] starting gdb server on 3334
Info : Listening on port 3334 for gdb connections
[stm32mp15x.cm4] halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x00000008 msp: 0x00000100
25792 bytes written at address 0x10000000
downloaded 25792 bytes in 0.167046s (150.782 KiB/s)
Info : [stm32mp15x.cm4] external reset detected
[stm32mp15x.cm4] halted due to debug-request, current mode: Thread
xPSR: 0x01000000 pc: 0x00000008 msp: 0x00000100
sp (/32): 0x10040000
pc (/32): 0x100006b1
xpsr (/32): 0x01000000
shutdown command invoked
逐行看重点:
pc: 0x00000008:VECTRESET + halt-on-reset 的正常中间态(核被调试器拉住,还没开始跑),不是错误;25792 bytes written ...:程序完整写入 M4 SRAM;external reset detected:软复位成功触发;sp / pc变成合法用户地址(pc指向Reset_Handler):向量表 / 入口加载成功;shutdown:调试器断开,业务程序正式运行。
✅ 只要没有任何 Error、链路稳定、二次下载不翻车,就说明成了。
这套方案好在哪
- 不管M4之前在正常跑、死循环、普通死机,软复位都可以接管;
- 调试器只负责拷贝数据,启动作业交给芯片硬件,比调试器发命令启动稳定得多;
- 支持反复下载,不用断电,LED可以正常闪烁,适合平时开发频繁调试。
4. 正确看待那条 unknown state 警告
用上上面这套脚本,运行的时候依旧大概率会看到这条黄色警告:
Warn : target was in unknown state when halt was requested
- 为什么消不掉:M4独立启动+芯片安全开启,调试接口拿不到M4内部状态,这是硬件条件带来的客观限制,改脚本消除不了。
- 实际影响:不影响下载、不影响软复位、不影响程序运行。该跑的照样跑。
- 不要瞎折腾:不要写指令强行去读内核寄存器试图消除警告,那样会直接报错误,打断整个下载流程。
一个能帮你判断固件到底跑没跑的实测细节:这条警告第一次下载不出现,第二次(不断电连跑)才出现。原因是我们用的 LiteOS 空闲任务会执行
WFI(Wait For Interrupt)让核进低功耗睡眠——第一次 M4 刚上电是正常 RUNNING 态,halt见到的状态符合预期不报警;第二次 M4 已跑进 idle 睡眠态,halt去停一个睡眠中的核,OpenOCD 发现不在标准运行/已停态就报unknown state。
这条警告恰恰证明固件确实在 M4 上跑(且跑到 idle 睡眠)。 它发生在这条命令链最前面的第一次halt,报完 OpenOCD 会强制停下;真正干净的启动由后半段保证——VECTRESET把整个核(含 WFI 睡眠态、NVIC/SysTick/Fault 残留)清零,第二次halt(核已是干净复位态)无警告,再reg sp/pc/xpsr+resume从Reset_Handler干净启动。第一次 halt 的警告对最终启动毫无影响。
补充实测对比现象:
很多同学会想:既然有警告,那我加上‑c reset halt,强制硬件复位再停机,是不是就能消除警告?
实际测试结果很有意思:
- 加上
‑c reset halt:警告确实不见了,但是会爆出一类Error:
Error: Fail reading CTRL/STAT register. Force reconnect
Info : [stm32mp15x.cm4] AP write error, reset will not halt
调试接口会反复断开、重新连接。虽然最终LED也能闪烁,程序能跑,但是调试链路不稳定,有概率直接下载失败。
- 不使用
reset halt,仅保留普通‑c halt:出现黄色警告Warn : target was in unknown state when halt was requested,没有报错,下载链路稳定,LED正常闪烁。
根本原因:M4独立启动,TZEN安全机制生效。OpenOCD没有权限在硬件复位的一瞬间强制把M4拉住停机,reset halt这套动作在这里本身就不受硬件支持。
👉工程取舍:宁可保留无害黄色警告,也不要为消除警告去强行使用reset halt,避免引入调试链路报错重连的风险。
判断下载成功看板子LED实际行为,不要盯着控制台日志文字。
截图中出现的
Deferring arp_examine也是伴随现象,M4安全模式下OpenOCD无法自动探查AP,同样可以直接忽略。
分清两种输出,别搞混:
✅Warn : target was in unknown state when halt was requested:警告,可以无视;
❌Error: Fail reading CTRL/STAT register. Force reconnect:真正风险,尽量不要触发。
5. 总结干货
- 双核芯片调试,优先看懂芯片硬件手册。很多问题工具层面解决不了,要学会利用芯片本身的硬件机制绕坑。
- OpenOCD不是万能神器。遇到安全机制限制,尽量少让调试器控制CPU运行启停;优先用「写内存 + 硬件自动重启启动」这套思路。
- M4软复位不会清空内存!程序开头务必手动清零BSS段,否则会出现引脚状态错乱、LED常亮/不亮等奇怪现象。
- 分清警告和错误。无害警告可以保留,硬件实际运行效果才是检验标准;不要为消除警告去触发新的Error报错。
- 软复位必须用 VECTRESET(
0x05FA0001),且复位后必须resume,否则核停在 halt 态不跑、两灯全灭。切勿写成0x05FA0004(那是 SYSRESETREQ 全局复位,会清BOOT_MCU导致灯不闪)。
本脚本针对STM32MP157 M4跑片内SRAM的LiteOS‑M工程,反复多次下载不用断电,LED都可以稳定正常闪烁。
更多推荐

所有评论(0)