STM32 Bootloader 跳转 APP 后 HAL_Delay 失效问题排查
一、问题现象
STM32F429ZGTx 采用 IAP 双区方案:Bootloader 烧录在 0x08000000,APP 烧录在 0x08020000。跳转到 APP 后,串口只打印一次 "进入APP程序",之后再无输出。将 HAL_Delay(500) 注释掉后,printf 能持续打印。
[15:22:00.782]收←◆进入APP程序, uwTick:134353892(之后再无输出)
二、问题原因
HAL_Delay 基于 SysTick 中断实现,依赖全局变量 uwTick 递增。若 SysTick 中断无法进入,uwTick 永不递增,HAL_Delay 必然死循环。
根因在 Bootloader 跳转函数中:
void bootloader_jump_to_app(void)
{
...
__disable_irq(); // 致命:PRIMASK = 1
...
__set_MSP(app_stack_top);
app_reset(); // 跳转,但 PRIMASK 仍为 1
}
跳转前用 __disable_irq() 关闭了全局中断(PRIMASK=1),跳转到 APP 后没有重新开启,导致 SysTick 中断永远无法进入,HAL_Delay 死循环。
另外,APP 的 main 函数中还有一个变量遮蔽 Bug:
uint32_t uwTick; // 未初始化的局部变量,遮蔽了 HAL 库的全局 uwTick
打印出的 134353892 是栈上的随机垃圾值,并非真实 tick 数。
三、解决方案
1. 在 APP 端 HAL_Init() 之后调用 __enable_irq() 清除 PRIMASK:
HAL_Init();
__enable_irq(); // 关键!清除 Bootloader 遗留的 PRIMASK=1
2. 删除局部变量 uwTick,改用 HAL_GetTick() 获取真实 tick:
while (1) {
printf("进入APP程序, uwTick:%lu\r\n", HAL_GetTick());
HAL_Delay(500);
}
修改后的 main.c 关键片段:
int main(void)
{
SCB->VTOR = 0x08020000U; // 重定位向量表到 APP 区
HAL_Init();
__enable_irq(); // 修复点 1:重新使能全局中断
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
while (1) {
printf("进入APP程序, uwTick:%lu\r\n", HAL_GetTick()); // 修复点 2
HAL_Delay(500);
}
}
四、验证结果
重新编译烧录后,串口正常持续输出,tick 值合理递增:
进入APP程序, uwTick:0
进入APP程序, uwTick:501
进入APP程序, uwTick:1002
进入APP程序, uwTick:1503
...
五、经验总结
5.1 Bootloader → APP 跳转的标准动作
- 关闭所有外设、清除时钟使能(HAL_RCC_DeInit)
- 关闭 SysTick(SysTick->CTRL = 0 等)
- 清除所有 NVIC 中断使能位和挂起位
- 设置 MSP(__set_MSP)
- 重定位 VTOR(也可放到 APP 端做)
- 明确全局中断的开关职责:要么 Bootloader 跳转前重新 __enable_irq(),要么 APP 入口处 __enable_irq(),二者必有其一
5.2 PRIMASK 在跨镜像跳转时的行为
__disable_irq() 本质是设置 PRIMASK = 1。CPU 复位会清零 PRIMASK,但函数跳转不会。跳转到 APP 是普通的函数调用,PRIMASK 保持原值,这就是很多老教程中"先 __disable_irq() 再跳转"会踩坑的原因。
5.3 变量遮蔽陷阱
uwTick、huart1 这类 HAL 库全局变量,在 main 里不要再起同名局部变量,否则会引发非常隐蔽的 Bug(编译器不报错,运行时数据是垃圾值)。建议用 HAL_GetTick()、HAL_GetTickFreq() 这类 API 而非直接访问内部变量。
更多推荐


所有评论(0)