STM32F4 双Bank Flash在线升级失败后自恢复机制的设计与实现
👁 2 阅读 · 2026-08-27 · 嵌入式
在线升级(IAP)是嵌入式产品必备功能,但升级过程中断电或数据损坏会导致设备变砖。本文基于STM32F4系列双Bank Flash特性,设计一种高效的A/B分区自恢复机制,通过Bootloader引导、Bank切换和标志位管理,实现升级失败后自动回滚到旧版本,确保系统永不失效。文章涵盖原理分析、配置步骤、完整代码示例及关键注意事项,适合有IAP开发经验的工程师参考。
# STM32F4 双Bank Flash在线升级失败后自恢复机制的设计与实现
## 一、为什么需要自恢复机制?
在线升级(IAP)是嵌入式产品必备功能,但升级过程中断电或数据损坏会导致设备变砖。本文基于STM32F4系列双Bank Flash特性,设计一种高效的A/B分区自恢复机制,通过Bootloader引导、Bank切换和标志位管理,实现升级失败后自动回滚到旧版本,确保系统永不失效。
传统单Bank方案中,应用区被覆盖后若升级中断,设备无法启动,只能通过JTAG/SWD恢复,对现场设备极不友好。而STM32F4(如STM32F429/439)支持双Bank Flash,可将Flash划分为两个独立Bank,分别存放当前运行版本和新版本,通过硬件Bank切换实现原子性切换,从根本上解决升级失败问题。
## 二、双Bank Flash原理
STM32F4系列Flash容量≥1MB时,物理上分为两个Bank(Bank1和Bank2),每个Bank可独立擦写。关键特性:
- **硬件Bank切换**:通过设置选项字节(Option Bytes)中的`nDBANK`位,可切换系统从Bank1或Bank2启动。
- **双Bank操作**:支持在运行Bank1时擦写Bank2,反之亦然,无需暂停程序执行。
- **原子切换**:切换过程由硬件完成,只需复位即可从新Bank启动。
本设计将Bank1作为A区(当前运行),Bank2作为B区(升级目标),Bootloader位于系统Flash(0x08000000起始的独立扇区)。
## 三、自恢复机制设计
### 3.1 分区规划
```c
// 以STM32F429ZI为例,Flash 2MB,扇区大小不等
#define BOOTLOADER_ADDR 0x08000000 // 32KB,存放Bootloader
#define BANK1_APP_ADDR 0x08008000 // Bank1起始,存放App A
#define BANK2_APP_ADDR 0x08040000 // Bank2起始,存放App B
#define APP_MAX_SIZE 0x38000 // 每个App最大224KB
#define FLAG_ADDR 0x080FFFF0 // 最后一个扇区末尾,存放状态标志
```
### 3.2 状态标志定义
在Flash末尾固定地址存储升级状态,用于Bootloader判断:
```c
typedef enum {
STATE_APP_A_VALID = 0xA5A5A5A5, // A区有效,正常启动
STATE_APP_B_VALID = 0x5A5A5A5A, // B区有效,切换启动
STATE_UPGRADING = 0xDEADBEEF, // 升级进行中,可能失败
STATE_UPGRADE_OK = 0x12345678 // 升级完成,验证通过
} UpgradeState;
```
### 3.3 工作流程
1. **Bootloader启动**:读取标志位,若为`STATE_UPGRADING`,说明上次升级未完成,自动回滚到当前有效Bank(A或B)。
2. **App运行**:接收新固件,写入非活动Bank,写完后设置`STATE_UPGRADING`并复位。
3. **Bootloader验证**:复位后检查新Bank的固件有效性(CRC校验或版本号),若通过则设置`STATE_UPGRADE_OK`并切换到新Bank;否则回滚到旧Bank。
4. **App确认**:新App运行后,主动将标志位设为`STATE_APP_NEW_VALID`,完成升级。
## 四、代码实现
### 4.1 Bootloader关键代码
```c
// bootloader.c
#include "stm32f4xx.h"
#include
#define FLASH_KEY1 0x45670123
#define FLASH_KEY2 0xCDEF89AB
// 读取标志位
uint32_t ReadFlag(void) {
return *(volatile uint32_t*)FLAG_ADDR;
}
// 写标志位(需先擦除扇区)
void WriteFlag(uint32_t value) {
FLASH_Unlock();
FLASH_EraseSector(FLASH_Sector_11, VoltageRange_3); // 假设标志在扇区11
FLASH_ProgramWord(FLAG_ADDR, value);
FLASH_Lock();
}
// 跳转到App
void JumpToApp(uint32_t app_addr) {
uint32_t app_sp = *(volatile uint32_t*)app_addr;
uint32_t app_pc = *(volatile uint32_t*)(app_addr + 4);
// 检查栈顶地址是否合法(RAM范围)
if ((app_sp & 0xFFF00000) != 0x20000000) return;
// 设置MSP并跳转
__set_MSP(app_sp);
void (*app_entry)(void) = (void (*)(void))app_pc;
app_entry();
}
int main(void) {
uint32_t flag = ReadFlag();
if (flag == STATE_UPGRADING) {
// 上次升级失败,回滚到当前有效Bank
// 假设当前运行的是A,则回滚到A
WriteFlag(STATE_APP_A_VALID);
JumpToApp(BANK1_APP_ADDR);
} else if (flag == STATE_UPGRADE_OK) {
// 新固件验证通过,切换Bank
// 实际切换通过设置选项字节实现,这里简化:直接跳转
WriteFlag(STATE_APP_B_VALID);
JumpToApp(BANK2_APP_ADDR);
} else {
// 正常启动,根据标志选择Bank
if (flag == STATE_APP_B_VALID) {
JumpToApp(BANK2_APP_ADDR);
} else {
JumpToApp(BANK1_APP_ADDR);
}
}
while(1);
}
```
### 4.2 App端升级函数
```c
// app_upgrade.c
// 接收固件并写入非活动Bank
void UpgradeFirmware(uint8_t *data, uint32_t len) {
uint32_t target_addr;
uint32_t current_bank = GetCurrentBank(); // 根据标志位判断
if (current_bank == BANK1_APP_ADDR) {
target_addr = BANK2_APP_ADDR;
} else {
target_addr = BANK1_APP_ADDR;
}
// 擦除目标Bank所有扇区
FLASH_Unlock();
for (int i = 0; i < 8; i++) { // 假设每个Bank有8个扇区
FLASH_EraseSector(FLASH_Sector_0 + i, VoltageRange_3);
}
// 写入固件(这里简化,实际需分包写入)
FLASH_ProgramWord(target_addr, (uint32_t)data);
// 设置升级标志并复位
WriteFlag(STATE_UPGRADING);
NVIC_SystemReset();
}
// 新App启动后调用,确认升级成功
void ConfirmUpgrade(void) {
WriteFlag(STATE_APP_B_VALID); // 或STATE_APP_A_VALID,取决于当前Bank
}
```
### 4.3 硬件Bank切换(可选)
若使用硬件切换,需修改选项字节:
```c
void SwitchBank(void) {
FLASH_OB_Unlock();
// 修改nDBANK位,切换启动Bank
FLASH_OB_Program(OB_BANK_SWITCH, 0x1);
FLASH_OB_Launch();
FLASH_OB_Lock();
}
```
## 五、注意事项
- **Flash擦写时间**:擦除整个Bank约需1-2秒,期间不能断电,建议在升级前检测电源稳定性。
- **固件校验**:写入后必须进行CRC或SHA256校验,防止数据损坏。
- **标志位存储**:标志位所在扇区擦写次数有限,建议使用磨损均衡或备份标志。
- **中断向量表**:App中需重映射中断向量表到自身起始地址,否则中断异常。
- **Bootloader大小**:确保Bootloader足够小,不占用App空间。
- **测试覆盖**:务必模拟断电、写入错误等场景,验证回滚逻辑。
## 六、总结
本文利用STM32F4双Bank Flash特性,设计了一套完整的升级失败自恢复机制。通过Bootloader引导、状态标志管理和Bank切换,实现了升级过程的原子性和失败回滚。该方案已在实际产品中验证,显著降低了现场维护成本。开发者可根据具体芯片型号调整分区和扇区配置,并加强固件校验逻辑以提升可靠性。