STM32双Bank Flash在OTA升级中的防掉电变砖策略与实现
👁 1 阅读 · 2026-08-27 · 嵌入式
在嵌入式OTA升级中,掉电或通信异常可能导致固件损坏,设备变砖。STM32的双Bank Flash架构通过A/B分区切换机制,结合标志位管理和启动跳转逻辑,可有效实现原子性升级。本文深入解析双Bank原理,给出基于HAL库的完整实现代码,涵盖配置、写入、切换及回滚策略,帮助开发者构建高可靠的OTA系统。
# STM32双Bank Flash在OTA升级中的防掉电变砖策略与实现
## 一、为什么需要双Bank Flash?
传统单Bank Flash升级时,新固件直接覆盖旧固件,一旦写入中途掉电,Flash中既无完整新固件,也无可用旧固件,设备只能变砖。双Bank Flash将Flash划分为两个独立区域(Bank1和Bank2),分别存放当前运行固件和新固件。升级时,新固件写入非活动Bank,完成后通过切换启动地址使系统运行新固件。若写入失败或校验错误,仍可回退到旧Bank,确保设备永不掉线。
## 二、双Bank Flash原理与硬件支持
STM32系列(如F4、L4、H7等)部分型号支持双Bank模式。以STM32F4为例,Flash容量≥1MB时,可配置为双Bank,每个Bank独立擦写。关键特性:
- **Bank切换**:通过设置选项字节或操作FLASH_CR寄存器,系统可从Bank1或Bank2启动。
- **原子切换**:切换操作在重启后生效,利用复位向量表重映射实现。
- **独立擦写**:一个Bank擦写时,另一个Bank可正常执行代码(需注意总线访问)。
硬件连接上,无需额外电路,但需确保电源稳定(建议加掉电检测电路,如通过ADC监测VDD)。
## 三、总体策略设计
1. **分区规划**:
- Bank1(0x08000000):当前运行区(App1)
- Bank2(0x08040000):待升级区(App2)
- 末尾预留标志区(如0x0807F000)存储状态标志。
2. **状态标志**:
- `0xA5A5A5A5`:Bank1有效,Bank2无效
- `0x5A5A5A5A`:Bank2有效,Bank1无效
- `0x00000000`:升级中(无效状态)
3. **升级流程**:
- 接收新固件,写入非活动Bank(如当前运行Bank1,则写Bank2)。
- 每写一页校验CRC或哈希。
- 全部写入后,设置标志为“Bank2有效”,然后软复位。
- Bootloader检查标志,跳转到对应Bank。
4. **掉电恢复**:
- 若写入中途掉电,标志仍为“Bank1有效”,重启后继续运行旧固件。
- 若标志已更新但新固件校验失败,Bootloader可回滚标志并启动旧Bank。
## 四、代码实现(基于STM32F4 HAL库)
### 1. Flash配置(启用双Bank)
```c
// 在系统初始化时调用,设置双Bank模式
void Flash_EnableDualBank(void) {
FLASH_OBProgramInitTypeDef obInit;
HAL_FLASH_Unlock();
HAL_FLASH_OB_Unlock();
obInit.OptionType = OPTIONBYTE_BANK;
obInit.BANK = FLASH_BANK_1; // 或FLASH_BANK_2,取决于当前配置
obInit.BankConfig = FLASH_BANK_DUAL; // 启用双Bank
HAL_FLASH_OBProgram(&obInit);
HAL_FLASH_OB_Lock();
HAL_FLASH_Lock();
// 需要重新上电或复位生效
NVIC_SystemReset();
}
```
### 2. 写入固件到目标Bank
```c
#define APP1_ADDR 0x08000000
#define APP2_ADDR 0x08040000
#define FLASH_PAGE_SIZE 2048 // 根据型号调整
uint32_t Write_Firmware(uint8_t *data, uint32_t len, uint8_t bank) {
uint32_t addr = (bank == 1) ? APP1_ADDR : APP2_ADDR;
uint32_t page_num = (len + FLASH_PAGE_SIZE - 1) / FLASH_PAGE_SIZE;
HAL_FLASH_Unlock();
for (uint32_t i = 0; i < page_num; i++) {
// 擦除页
FLASH_EraseInitTypeDef erase;
erase.TypeErase = FLASH_TYPEERASE_PAGES;
erase.Banks = (bank == 1) ? FLASH_BANK_1 : FLASH_BANK_2;
erase.Page = i;
erase.NbPages = 1;
uint32_t pageError = 0;
if (HAL_FLASHEx_Erase(&erase, &pageError) != HAL_OK) {
HAL_FLASH_Lock();
return 1;
}
// 写入数据(按双字对齐)
for (uint32_t offset = 0; offset < FLASH_PAGE_SIZE; offset += 8) {
uint64_t data64 = 0;
memcpy(&data64, data + i*FLASH_PAGE_SIZE + offset, 8);
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, addr + i*FLASH_PAGE_SIZE + offset, data64) != HAL_OK) {
HAL_FLASH_Lock();
return 2;
}
}
}
HAL_FLASH_Lock();
return 0;
}
```
### 3. 设置启动标志并切换Bank
```c
#define FLAG_ADDR 0x0807F000
#define FLAG_BANK1_VALID 0xA5A5A5A5
#define FLAG_BANK2_VALID 0x5A5A5A5A
void Set_BootFlag(uint32_t flag) {
HAL_FLASH_Unlock();
// 擦除标志页
FLASH_EraseInitTypeDef erase;
erase.TypeErase = FLASH_TYPEERASE_PAGES;
erase.Banks = FLASH_BANK_2; // 标志区位于Bank2末尾
erase.Page = (FLAG_ADDR - 0x08000000) / FLASH_PAGE_SIZE;
erase.NbPages = 1;
uint32_t pageError;
HAL_FLASHEx_Erase(&erase, &pageError);
// 写入标志
uint64_t data = flag;
HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, FLAG_ADDR, data);
HAL_FLASH_Lock();
}
void Jump_To_App(uint8_t bank) {
uint32_t app_addr = (bank == 1) ? APP1_ADDR : APP2_ADDR;
// 检查栈顶地址是否合法
if (*(uint32_t*)app_addr > 0x20000000 && *(uint32_t*)app_addr < 0x20020000) {
// 设置主栈指针
__set_MSP(*(uint32_t*)app_addr);
// 跳转到复位向量
void (*app_reset)(void) = (void (*)(void))(*(uint32_t*)(app_addr + 4));
app_reset();
}
}
```
### 4. Bootloader主逻辑
```c
void Bootloader_Main(void) {
uint32_t flag = *(uint32_t*)FLAG_ADDR;
if (flag == FLAG_BANK2_VALID) {
// 验证Bank2固件(如CRC)
if (Verify_Firmware(APP2_ADDR) == 0) {
Jump_To_App(2);
} else {
// 校验失败,回滚
Set_BootFlag(FLAG_BANK1_VALID);
Jump_To_App(1);
}
} else {
// 默认运行Bank1
Jump_To_App(1);
}
}
```
## 五、注意事项
- **中断向量表重映射**:App固件需在启动时设置SCB->VTOR为自身Bank起始地址,否则中断异常。
- **Flash擦写时间**:双Bank擦写时,若代码从另一Bank执行,需确保总线不冲突(F4支持并行访问)。
- **掉电检测**:建议在升级前检测电压,低于阈值时暂停写入。
- **标志区可靠性**:标志写入前先擦除,且使用双字写入,防止半字写入掉电。
- **固件校验**:写入时逐页计算CRC,并在跳转前整体校验,避免损坏固件运行。
- **测试回滚**:实际测试中,模拟写入中途断电,验证设备能正常回退。
## 六、总结
双Bank Flash方案通过空间换可靠性,结合标志位和Bootloader跳转,实现了OTA升级的原子性。即使升级过程中掉电,设备也能自动回退到旧版本,彻底解决变砖问题。该策略在工业、物联网等领域具有极高实用价值,是嵌入式OTA设计的核心进阶技术。