STM32 IAP跳过Bootloader的改进

之前写过两篇关于 STM32 IAP bootloader 的博客(STM32 BootLoader升级固件STM32 F0实现IAP升级固件),实际项目里又遇到了两个问题,记录一下最终的改进方案。

一、问题背景

APP 中原本有一个 App_reset 的功能,用来跳过 bootloader,核心做法是从 APP 里直接跳转到应用入口。实际运行中偶发卡住,怀疑与串口输入的中断状态有关。

同时,为了方便用户在 IAP 中输入 1 来确认升级,我在 bootloader 中加入了 4 秒等待。这样在正常启动时会浪费时间,尤其是当主程序被看门狗复位时,体验很差。

二、原有实现(有隐患)

从 APP 里直接跳转到应用入口的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static void Application_Reset(void)
{
/* Test if user code is programmed starting from address "APPLICATION_ADDRESS" */
if (((*(__IO uint32_t*)APPLICATION_ADDRESS) & 0x2FFE0000) == 0x20000000)
{
printf("Jump to the user App.\n");
/* Jump to user application */
uint32_t JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4);
pFunction JumpToApplication = (pFunction) JumpAddress;
/* Initialize user application's Stack Pointer */
__set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS);
JumpToApplication();
}
else
{
printf("Jump to the user App failed.\n");
}
}

问题:

  1. APP 直接跳转到入口,有机会受外设/中断状态影响,导致偶发卡住。
  2. bootloader 的等待窗口固定,正常启动也会浪费时间。

三、改进方案

让 APP 主动设置“跳过 bootloader”的标志位,然后触发一次完整的硬件复位。bootloader 在上电后优先检测该标志,若存在则直接进入 APP。这样:

  • APP reset 统一从 bootloader 开始,避免中断状态不一致。
  • 只有 APP 明确要求跳过时才跳过,减少等待时间的浪费。

1. APP 设置标志并复位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* Safe backup-domain unlock (non-blocking) */
static void EnableBackupDomain(void)
{
PWR->CR |= PWR_CR_DBP;
for (volatile uint32_t i = 0; i < 1000; i++) __NOP();
}

/* Reset app and skip bootloader */
void ResetAndSkipBootloader(void)
{
EnableBackupDomain();
RTC->BKP0R = 0xDEADBEEF; // set skip flag
NVIC_SystemReset(); // full hardware reset
}

2. bootloader 读取标志并清理

bootloader 启动后首先检查 BKP0R 的标志位,如果命中则清理标志并直接跳转到 APP。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* Safe backup-domain unlock (non-blocking) */
static void EnableBackupDomain(void)
{
PWR->CR |= PWR_CR_DBP;
for (volatile uint32_t i = 0; i < 1000; i++) __NOP(); // small delay
}

/* Bootloader entry */
int main(void)
{
/* Enable backup domain safely */
EnableBackupDomain();

/* Check skip flag */
if (RTC->BKP0R == 0xDEADBEEF)
{
RTC->BKP0R = 0; /* Clear flag */
JumpToApplication(APPLICATION_ADDRESS);
while(1);
}

/* Normal bootloader code here (UART update, etc.) */
while (1)
{
// Bootloader main loop
}
}

四、效果

  1. APP 触发复位时始终走完整 reset 流程,稳定性提升。
  2. 正常启动不再固定等待 4 秒,体验明显改善。
  3. 逻辑更加清晰:APP 决定是否跳过,bootloader 只负责执行。

五、注意事项

  • BKP0R 属于备份寄存器,需确保已开启备份域访问。
  • 标志位需在 bootloader 中及时清除,避免一直跳过升级窗口。
  • 如果系统中还使用了备份寄存器存其他配置,需要统一分配。