STM32F4双Bank Flash在线升级失败后自恢复的启动流程设计
👁 1 阅读 · 2026-08-27 · 嵌入式
在嵌入式产品中,OTA升级失败可能导致设备变砖。STM32F4系列的双Bank Flash特性为安全升级提供了硬件基础。本文深入讲解如何利用双Bank设计一个具备自恢复能力的启动流程,包括原理分析、Bootloader与App的协作机制、关键配置步骤及完整代码示例,帮助开发者构建高可靠的升级方案。
# 引言
在工业、医疗等对可靠性要求极高的嵌入式场景中,OTA(Over-The-Air)升级是必备功能,但升级过程中断电、通信错误等意外常导致固件损坏。STM32F4系列(如STM32F407/427)支持双Bank Flash,每个Bank可独立擦写,配合硬件交换机制,可实现“升级失败自动回滚”的稳健启动流程。本文将深入剖析这一设计。
# 原理:双Bank Flash与启动流程
STM32F4的Flash分为两个Bank(Bank1和Bank2),每个Bank容量相等(如1MB器件各512KB)。关键特性是**Flash地址映射可交换**:通过设置选项字节`BOOT_SW`或运行时操作`FLASH_CR`的`SWAP_BANK`位,可将Bank1和Bank2的物理地址对调。这意味着CPU可以从Bank1启动,也可以从Bank2启动,而无需改变向量表地址。
启动流程设计如下:
- **Bootloader**(常驻Bank1,不可被覆盖)负责检查App有效性,并决定从哪个Bank启动。
- **App**(运行在Bank2或Bank1)接收新固件,写入非活动Bank,完成后置位标志并复位。
- **Bootloader**在启动时检查标志:若升级完成,则交换Bank并启动新App;若升级失败(标志未置位或校验失败),则继续从旧App启动,实现自恢复。
# 硬件与软件准备
- 硬件:STM32F4系列开发板(如STM32F407-Discovery),外部Flash可选(用于存储固件包)。
- 软件:STM32CubeIDE或Keil,HAL库。
- 关键配置:
- 分区Flash:Bank1(0x08000000-0x0807FFFF)存放Bootloader和旧App;Bank2(0x08080000-0x080FFFFF)存放新App。
- 设置选项字节:`BOOT_SW`位(在`FLASH_OBProgramInitTypeDef`中配置)允许软件交换Bank。
# 启动流程设计
## 1. 状态标志存储
使用Flash的最后一个扇区(如Bank1的最后一个扇区)存储升级状态标志。定义如下:
```c
#define APP_FLAG_ADDR 0x0807F000 // 位于Bank1末尾
#define FLAG_UPDATE_PENDING 0xA5A5A5A5
#define FLAG_UPDATE_OK 0x5A5A5A5A
#define FLAG_APP_VALID 0x12345678
```
## 2. Bootloader启动逻辑
Bootloader上电后执行以下步骤:
- 读取标志区,判断是否有升级请求。
- 若有`FLAG_UPDATE_PENDING`,则检查新App(Bank2)的校验和(如CRC32)。
- 校验通过:设置`FLAG_UPDATE_OK`,交换Bank,跳转至新App。
- 校验失败:清除标志,直接跳转至旧App(Bank1)。
- 若无升级请求,则检查当前Bank的App有效性,无效则尝试另一Bank。
关键代码(HAL库):
```c
void Bootloader_Start(void) {
uint32_t flag = *(volatile uint32_t*)APP_FLAG_ADDR;
if (flag == FLAG_UPDATE_PENDING) {
// 校验Bank2中的新App
if (CheckAppCRC(BANK2_APP_ADDR, APP_MAX_SIZE) == 0) {
// 设置升级完成标志
WriteFlag(FLAG_UPDATE_OK);
// 交换Bank,使Bank2映射到0x08000000
FLASH_OBProgramInitTypeDef ob;
HAL_FLASHEx_OBGetConfig(&ob);
ob.BOOT_SW = FLASH_BOOT_SW_BANK2; // 根据实际定义
HAL_FLASHEx_OBProgram(&ob);
// 复位以重新映射
NVIC_SystemReset();
} else {
// 校验失败,清除标志,回滚到旧App
WriteFlag(FLAG_APP_VALID);
JumpToApp(BANK1_APP_ADDR);
}
} else {
// 正常启动,检查当前Bank的App
if (CheckAppCRC(BANK1_APP_ADDR, APP_MAX_SIZE) == 0) {
JumpToApp(BANK1_APP_ADDR);
} else {
// 尝试Bank2
JumpToApp(BANK2_APP_ADDR);
}
}
}
```
## 3. App升级流程
App在运行中收到新固件,将其写入非活动Bank(若当前从Bank1运行,则写入Bank2)。写入完成后,设置`FLAG_UPDATE_PENDING`并复位。
```c
void OTA_Update(uint8_t *data, uint32_t len) {
// 擦除Bank2,写入数据(省略具体Flash操作)
Flash_EraseBank(BANK2);
Flash_WriteData(BANK2_APP_ADDR, data, len);
// 设置升级待定标志
WriteFlag(FLAG_UPDATE_PENDING);
NVIC_SystemReset();
}
```
## 4. 跳转函数实现
跳转至App时,需设置MSP和复位向量:
```c
void JumpToApp(uint32_t app_addr) {
uint32_t msp = *(volatile uint32_t*)app_addr;
void (*app_reset)(void) = (void (*)(void))(*(volatile uint32_t*)(app_addr + 4));
__set_MSP(msp);
app_reset();
}
```
# 注意事项
- **Bank交换的时序**:交换Bank后,必须复位才能生效,且需确保App的链接地址与Bank基地址匹配。
- **中断向量表重定位**:App中需在`SystemInit`后设置`SCB->VTOR`为当前Bank的基地址。
- **Flash擦写保护**:Bootloader区域应设置读保护或写保护,防止意外覆盖。
- **校验算法**:建议使用CRC32或SHA256,确保固件完整性。
- **测试**:模拟断电场景,验证升级失败后能回滚。
# 总结
利用STM32F4的双Bank特性,结合精心设计的启动流程,可以显著提升OTA升级的可靠性。本文提供的方案在升级失败时能自动回滚至旧版本,避免了设备变砖的风险。开发者可根据实际需求调整标志存储和校验策略,构建更健壮的升级系统。