STM32F4双Bank Flash在线升级失败后自动回滚的启动流程设计
👁 2 阅读 · 2026-08-27 · 嵌入式
在嵌入式OTA升级中,固件损坏或升级中断可能导致设备变砖。STM32F4系列凭借双Bank Flash硬件支持,可实现A/B分区无缝切换与失败回滚。本文深入解析双Bank架构原理,设计一套基于Bootloader+App的启动流程,涵盖跳转逻辑、标志位管理、CRC校验及回滚策略,并给出完整C代码示例与关键注意事项,助你构建高可靠的在线升级方案。
# 一、双Bank Flash架构与回滚原理
STM32F4系列(如STM32F407/427)的Flash被划分为两个独立的Bank(Bank1和Bank2),每个Bank容量相等(通常为1MB时各512KB)。硬件支持从任一Bank启动,且可通过`SYSCFG_MEMRMP`寄存器动态切换启动Bank。这一特性为OTA提供了天然的双区备份能力:
- **Bank1(App A)**:当前运行的应用固件。
- **Bank2(App B)**:新固件下载区或备份区。
**回滚核心思想**:升级时先写入非活动Bank,校验通过后切换启动地址;若新固件启动失败或运行异常,Bootloader检测到标志位后自动回退到旧Bank。整个过程无需外部存储,且Flash擦写操作不影响当前运行固件。
# 二、启动流程总体设计
系统包含三个角色:Bootloader(固定于Bank0起始区)、App A(Bank1)、App B(Bank2)。启动流程如下:
1. **上电复位**:Bootloader首先执行。
2. **检查升级标志**:读取备份寄存器或Flash特定地址的标志位。
3. **校验App有效性**:对目标Bank的固件进行CRC或Magic Number校验。
4. **选择启动Bank**:
- 若升级标志有效且新固件校验通过 → 跳转至新Bank。
- 若新固件校验失败或运行超时 → 清除标志,回滚至旧Bank。
5. **运行App**:App启动后,若需升级,通过Bootloader提供的接口写入另一Bank。
**关键设计点**:
- 使用备份寄存器(如`RTC->BKPxR`)存储状态,掉电不丢失。
- 每个App固件头部包含版本号、CRC、长度等元数据。
- 设置看门狗(IWDG)防止App死循环导致无法回滚。
# 三、配置步骤
## 1. 内存分区规划
以STM32F407ZG(1MB Flash)为例:
- **Bootloader**:0x08000000 ~ 0x0800FFFF(64KB)
- **App A(Bank1)**:0x08010000 ~ 0x0807FFFF(448KB)
- **App B(Bank2)**:0x08080000 ~ 0x080FFFFF(512KB)
注意:Bootloader必须位于Bank1起始地址,且App A和B需分别链接到对应地址。
## 2. 使能双Bank模式
在系统初始化时,通过选项字节或代码启用双Bank操作(部分型号默认开启):
```c
// 启用双Bank模式(以STM32F4为例)
FLASH_OBProgramInitTypeDef ob;
HAL_FLASHEx_OBGetConfig(&ob);
if ((ob.USERConfig & FLASH_OPTION_BANK_SWAP) == 0) {
ob.USERConfig |= FLASH_OPTION_BANK_SWAP;
HAL_FLASHEx_OBProgram(&ob);
HAL_FLASH_OB_Launch(); // 重启生效
}
```
## 3. Bootloader跳转逻辑
```c
// 跳转至指定Bank的App
void jump_to_app(uint32_t app_addr) {
uint32_t msp = *(volatile uint32_t*)app_addr;
void (*app_reset)(void) = (void (*)(void))(*(volatile uint32_t*)(app_addr + 4));
// 关闭全局中断,设置MSP,跳转
__disable_irq();
HAL_RCC_DeInit();
SysTick->CTRL = 0;
SCB->VTOR = app_addr;
__set_MSP(msp);
app_reset();
}
```
## 4. 升级与回滚标志管理
使用备份寄存器(如BKP1)存储状态:
```c
#define APP_A_VALID 0xA5A5
#define APP_B_VALID 0x5A5A
#define ROLLBACK_FLAG 0xDEAD
void set_boot_flag(uint32_t flag) {
HAL_PWR_EnableBkUpAccess();
HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR1, flag);
}
uint32_t get_boot_flag(void) {
HAL_PWR_EnableBkUpAccess();
return HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR1);
}
```
## 5. 完整Bootloader主流程示例
```c
int main(void) {
HAL_Init();
SystemClock_Config();
uint32_t flag = get_boot_flag();
uint32_t app_addr;
// 默认启动App A
if (flag == APP_B_VALID) {
// 检查App B的CRC(伪代码,需实现CRC校验函数)
if (check_crc(APP_B_ADDR) == PASS) {
app_addr = APP_B_ADDR;
} else {
set_boot_flag(ROLLBACK_FLAG);
app_addr = APP_A_ADDR;
}
} else if (flag == ROLLBACK_FLAG) {
// 回滚:清除标志,启动App A
set_boot_flag(APP_A_VALID);
app_addr = APP_A_ADDR;
} else {
app_addr = APP_A_ADDR;
}
// 启动看门狗,超时自动复位
MX_IWDG_Init();
jump_to_app(app_addr);
while(1); // 若跳转失败,停留此处
}
```
## 6. App端升级与回滚配合
App运行时,若收到新固件,写入非活动Bank,然后设置标志并复位:
```c
// 假设当前运行在Bank1,升级Bank2
void ota_update(uint8_t* data, uint32_t len) {
// 擦写Bank2,写入固件
flash_erase_bank(APP_B_ADDR);
flash_write(APP_B_ADDR, data, len);
// 设置启动标志为App B
set_boot_flag(APP_B_VALID);
NVIC_SystemReset();
}
```
# 四、注意事项
- **CRC校验**:必须使用硬件CRC或软件CRC,确保固件完整性。建议在固件头部存放CRC值,Bootloader计算整个固件区域。
- **看门狗**:Bootloader和App都应开启独立看门狗(IWDG),防止App卡死导致无法回滚。
- **中断向量表**:App中必须重定位中断向量表(`SCB->VTOR = APP_ADDR`),否则中断异常。
- **Flash擦写保护**:确保Bootloader区域不被App误擦除,可通过选项字节设置读保护(RDP)或写保护(WRP)。
- **升级失败处理**:若升级过程中断电,标志位可能未更新,Bootloader应能识别无效固件并回滚。建议在写入前先备份旧固件(若空间允许)。
- **双Bank切换**:某些型号需通过`SYSCFG_MEMRMP`切换启动地址,但跳转函数中直接设置VTOR即可,无需硬件切换。
- **测试**:务必在真机上模拟断电、擦写中断等场景,验证回滚可靠性。
# 五、总结
利用STM32F4双Bank Flash,配合Bootloader的智能判断,可以低成本实现高可靠的OTA升级。本文提供的启动流程和代码框架可直接应用于实际项目,只需根据具体型号调整Flash地址和CRC算法。记住:回滚机制的核心是“状态明确、校验严格、超时兜底”,三者缺一不可。