STM32双Bank Flash在线升级失败后自动回滚的Bootloader设计要点
👁 2 阅读 · 2026-08-27 · 嵌入式
在嵌入式OTA升级中,升级失败可能导致设备变砖。利用STM32双Bank Flash特性,设计一个具备自动回滚机制的Bootloader,可显著提升升级可靠性。本文从原理出发,详细讲解双Bank映射、跳转逻辑、回滚触发条件及完整代码实现,并给出关键注意事项,帮助开发者构建安全稳健的在线升级方案。
# STM32双Bank Flash在线升级失败后自动回滚的Bootloader设计要点
## 一、为什么需要双Bank Flash?
传统单Bank升级方案中,新固件直接覆盖旧固件,一旦写入过程中断电或校验失败,设备将无法启动。双Bank Flash(如STM32F7/H7系列)将Flash分为两个独立Bank(Bank0和Bank1),可分别存储当前运行固件和新固件。升级时,Bootloader将新固件写入非活动Bank,完成后通过标志位切换启动Bank。若新固件启动失败,Bootloader可自动回滚到旧Bank,确保设备始终可用。
## 二、双Bank升级与回滚原理
### 2.1 双Bank内存映射
以STM32H743为例,Flash共2MB,分为Bank0(0x08000000-0x080FFFFF)和Bank1(0x08100000-0x081FFFFF)。默认从Bank0启动,通过设置选项字节或操作Flash控制寄存器(FLASH_CR)的BANK_SWAP位,可切换启动Bank。
### 2.2 升级流程
1. **Bootloader启动**:检查升级标志(如备份寄存器或Flash特定地址)。
2. **接收固件**:通过UART/CAN/以太网接收新固件,写入非活动Bank。
3. **校验固件**:计算CRC或哈希,与固件头比对。
4. **切换Bank**:设置BANK_SWAP位,并置位升级完成标志。
5. **跳转App**:复位并启动新Bank中的App。
### 2.3 回滚触发条件
- **App启动失败**:App在启动后需在限定时间内(如5秒)向备份寄存器写入“运行正常”标志,否则Bootloader判定为启动失败。
- **硬件看门狗**:若App未及时喂狗,看门狗复位,Bootloader检测到异常标志后回滚。
- **显式回滚命令**:App可主动请求回滚(如检测到严重错误)。
回滚操作:清除BANK_SWAP位,恢复原Bank启动,并清除升级标志。
## 三、Bootloader代码实现
以下基于STM32H743,使用HAL库,展示核心逻辑。
### 3.1 内存映射与标志定义
```c
#define APP_BANK0_ADDR 0x08020000 // Bank0 App起始(跳过Bootloader)
#define APP_BANK1_ADDR 0x08120000 // Bank1 App起始
#define UPGRADE_FLAG_ADDR 0x0801FF00 // 升级标志存储地址(Bootloader尾部)
#define RUN_OK_FLAG_ADDR 0x0801FF04 // App运行正常标志地址
// 升级标志值
#define UPGRADE_PENDING 0xA5A5A5A5 // 等待升级完成
#define UPGRADE_DONE 0x5A5A5A5A // 升级完成,需验证App
#define UPGRADE_FAILED 0xDEADBEEF // 升级失败
```
### 3.2 检查并执行回滚
```c
void CheckAndRollback(void) {
uint32_t upgrade_flag = *(volatile uint32_t*)UPGRADE_FLAG_ADDR;
uint32_t run_ok_flag = *(volatile uint32_t*)RUN_OK_FLAG_ADDR;
if (upgrade_flag == UPGRADE_DONE) {
// 上次升级完成,但本次启动未收到App运行正常标志
if (run_ok_flag != 0x12345678) {
// 回滚:切换回Bank0
FLASH_OBProgramInitTypeDef ob;
HAL_FLASHEx_OBGetConfig(&ob);
ob.Banks = FLASH_BANK_1; // 当前从Bank1启动,需切回Bank0
ob.OptionType = OPTIONBYTE_BANK;
ob.Bank = FLASH_BANK_1;
ob.BankSwap = FLASH_BANK_SWAP_DISABLE; // 禁用Bank交换
HAL_FLASH_Unlock();
HAL_FLASH_OB_Unlock();
HAL_FLASHEx_OBProgram(&ob);
HAL_FLASH_OB_Launch(); // 触发选项字节重载,复位
// 复位后将从Bank0启动
} else {
// App运行正常,清除升级标志
WriteFlash(UPGRADE_FLAG_ADDR, 0);
}
}
}
```
### 3.3 跳转到App
```c
typedef void (*pFunction)(void);
void JumpToApp(uint32_t app_addr) {
uint32_t app_stack = *(volatile uint32_t*)app_addr;
pFunction app_entry = (pFunction)*(volatile uint32_t*)(app_addr + 4);
if ((app_stack & 0x2FFE0000) != 0x20000000) return; // 检查栈顶地址合法性
HAL_RCC_DeInit();
SysTick->CTRL = 0; SysTick->LOAD = 0; SysTick->VAL = 0;
__disable_irq();
// 设置MSP为App的栈顶
__set_MSP(app_stack);
app_entry();
}
```
### 3.4 主流程
```c
int main(void) {
HAL_Init();
SystemClock_Config();
// 检查是否需要回滚
CheckAndRollback();
// 判断当前启动Bank(通过选项字节)
FLASH_OBProgramInitTypeDef ob;
HAL_FLASHEx_OBGetConfig(&ob);
uint32_t app_addr = (ob.BankSwap == FLASH_BANK_SWAP_ENABLE) ? APP_BANK1_ADDR : APP_BANK0_ADDR;
// 检查App有效性(如校验头部)
if (IsAppValid(app_addr)) {
JumpToApp(app_addr);
} else {
// 无效则进入Bootloader升级模式
EnterBootloader();
}
while(1);
}
```
### 3.5 App端配合(关键)
App在main函数开头必须写入运行正常标志,并启动看门狗。
```c
// App main.c
#define RUN_OK_FLAG_ADDR 0x0801FF04
int main(void) {
// 写入运行正常标志
*(volatile uint32_t*)RUN_OK_FLAG_ADDR = 0x12345678;
// 初始化看门狗,若主循环卡死则复位
MX_IWDG_Init();
while(1) {
// 业务逻辑
IWDG_Refresh();
}
}
```
## 四、注意事项
- **Flash写入保护**:Bootloader和App区域需设置读写保护,防止意外擦写。
- **固件头设计**:建议在固件起始处包含魔数、版本号、长度、CRC等,便于Bootloader校验。
- **升级标志存储**:使用备份寄存器(如RTC备份域)更安全,避免Flash磨损,但需注意备份域复位条件。
- **看门狗超时**:App启动后必须在看门狗超时前喂狗,否则回滚机制会误触发。
- **双Bank切换时序**:切换Bank需操作选项字节,注意解锁和重载流程,避免死锁。
- **中断向量表**:App中需设置VTOR寄存器指向对应Bank的起始地址,否则中断异常。
- **测试回滚**:在开发阶段故意制造升级失败(如写入错误固件),验证回滚逻辑。
## 五、总结
利用STM32双Bank Flash,结合标志位和看门狗,可设计出高可靠的OTA升级方案。本文提供的代码框架覆盖了核心回滚逻辑,开发者可根据具体芯片型号和需求调整。记住:回滚机制的关键在于“快速检测、可靠切换”,务必在真机上反复测试。