STM32H7双Bank Flash在线升级失败后自动回滚的启动流程设计
👁 1 阅读 · 2026-08-27 · 嵌入式
在工业级嵌入式设备中,在线升级(OTA)的可靠性至关重要。STM32H7系列凭借其双Bank Flash架构,为固件升级提供了硬件级的安全保障。本文将深入剖析基于双Bank Flash的启动流程,重点讲解如何利用硬件交换机制实现升级失败后的自动回滚,确保设备永不变砖。文章将涵盖原理分析、配置步骤、完整代码示例及关键注意事项,助力开发者构建高可靠的OTA系统。
# STM32H7双Bank Flash在线升级失败后自动回滚的启动流程设计
## 一、为什么需要双Bank Flash?
在传统的单Bank Flash方案中,升级过程中一旦发生断电、写入错误或校验失败,设备将因固件损坏而无法启动,即“变砖”。STM32H7系列(如H743、H750)提供双Bank Flash,将Flash划分为两个独立的Bank(Bank1和Bank2),每个Bank可独立擦写。通过硬件支持的Bank切换机制,系统可以在两个固件镜像之间无缝切换,从而在升级失败时自动回滚到上一个稳定版本。
## 二、双Bank启动原理
### 2.1 Flash布局
STM32H7的Flash通常为1MB或2MB,分为两个Bank,每个Bank大小相等。例如,H743的1MB Flash分为Bank1(0x08000000-0x0807FFFF)和Bank2(0x08080000-0x080FFFFF)。每个Bank可独立存放一个完整的固件镜像。
### 2.2 硬件交换机制
STM32H7的Flash控制器(FLASH)提供`FLASH_CR`寄存器中的`BKSWAP`位。当该位被置1时,硬件会将Bank1和Bank2的地址映射互换。这意味着:
- 物理Bank1的地址变为0x08080000,物理Bank2的地址变为0x08000000。
- CPU从0x08000000启动时,实际执行的是物理Bank2中的代码。
这一机制使得启动时无需复制代码,只需切换映射即可选择运行哪个Bank的固件。
### 2.3 启动流程设计
设计目标:
- 正常启动:从当前活动Bank启动。
- 升级过程:将新固件写入非活动Bank,校验成功后切换活动Bank。
- 升级失败:启动时检测到固件无效,自动切换到另一个Bank(回滚)。
具体流程如下:
1. **上电启动**:CPU从0x08000000读取向量表,执行启动代码(Bootloader)。
2. **检查标志**:Bootloader读取备份寄存器(如RTC备份寄存器)或Flash特定区域,获取当前活动Bank信息及升级状态。
3. **验证固件**:对活动Bank中的固件进行CRC或SHA校验。若校验通过,跳转到该Bank的应用程序;若失败,则切换Bank并再次校验。
4. **升级流程**:应用程序收到新固件后,将其写入非活动Bank,写入完成后设置“待切换”标志,并触发软复位。
5. **切换确认**:Bootloader检测到“待切换”标志,执行Bank切换(置位BKSWAP),并清除标志。若新固件启动后自检失败,可再次切换回旧Bank。
## 三、配置步骤
### 3.1 内存布局配置
在STM32CubeIDE中,为Bootloader和两个应用程序分别设置链接脚本。Bootloader占用Flash起始区域(如0x08000000,大小32KB),应用程序A位于Bank1(0x08008000),应用程序B位于Bank2(0x08088000)。注意:当BKSWAP置位后,Bank2的物理地址映射到0x08000000,因此应用程序B的链接地址应设为0x08000000(但实际物理地址是0x08080000)。
### 3.2 使能双Bank模式
STM32H7的Flash默认是单Bank模式,需通过选项字节或运行时配置切换为双Bank。在系统初始化时,调用以下代码:
```c
void Flash_EnableDualBank(void) {
FLASH_OBProgramInitTypeDef ob;
HAL_FLASHEx_OBGetConfig(&ob);
if (ob.Banks != FLASH_BANK_BOTH) {
ob.Banks = FLASH_BANK_BOTH;
HAL_FLASH_Unlock();
HAL_FLASHEx_OBProgram(&ob);
HAL_FLASH_Lock();
// 需要复位才能生效
NVIC_SystemReset();
}
}
```
### 3.3 Bootloader启动代码
Bootloader的核心逻辑如下:
```c
#define APP_A_ADDR 0x08008000 // Bank1 应用程序A
#define APP_B_ADDR 0x08088000 // Bank2 应用程序B(物理地址)
#define BACKUP_REG RTC->BKP0R // 使用备份寄存器存储状态
void JumpToApp(uint32_t app_addr) {
uint32_t msp_val = *(volatile uint32_t*)app_addr;
if ((msp_val & 0xFFF00000) != 0x20000000) return; // 栈顶地址无效
void (*app_reset)(void) = (void (*)(void))(*(volatile uint32_t*)(app_addr + 4));
__set_MSP(msp_val);
app_reset();
}
int main(void) {
HAL_Init();
// 读取状态标志
uint32_t state = BACKUP_REG;
uint32_t active_bank = (state & 0x1) ? 2 : 1; // 1表示Bank1,2表示Bank2
uint32_t app_addr = (active_bank == 1) ? APP_A_ADDR : APP_B_ADDR;
// 校验当前Bank固件
if (CRC_Check(app_addr) == OK) {
JumpToApp(app_addr);
} else {
// 校验失败,切换Bank并尝试
if (active_bank == 1) {
// 切换到Bank2
FLASH->CR |= FLASH_CR_BKSWAP;
app_addr = APP_B_ADDR;
} else {
FLASH->CR &= ~FLASH_CR_BKSWAP;
app_addr = APP_A_ADDR;
}
if (CRC_Check(app_addr) == OK) {
BACKUP_REG = (active_bank == 1) ? 0 : 1; // 更新状态
JumpToApp(app_addr);
} else {
// 双Bank均失败,进入错误处理
Error_Handler();
}
}
}
```
### 3.4 应用程序升级代码
在应用程序中,接收新固件并写入非活动Bank:
```c
void OTA_Update(uint8_t *data, uint32_t len) {
uint32_t target_bank = (GetActiveBank() == 1) ? 2 : 1;
uint32_t target_addr = (target_bank == 1) ? APP_A_ADDR : APP_B_ADDR;
// 擦除目标Bank
FLASH_EraseInitTypeDef erase;
erase.TypeErase = FLASH_TYPEERASE_SECTORS;
erase.Banks = (target_bank == 1) ? FLASH_BANK_1 : FLASH_BANK_2;
erase.Sectors = 0;
erase.NbSectors = 8;
erase.VoltageRange = FLASH_VOLTAGE_RANGE_3;
HAL_FLASHEx_Erase(&erase, &error);
// 写入数据
HAL_FLASH_Unlock();
for (uint32_t i = 0; i < len; i += 8) {
uint64_t val = *(uint64_t*)(data + i);
HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, target_addr + i, val);
}
HAL_FLASH_Lock();
// 设置切换标志并复位
BACKUP_REG = (target_bank == 1) ? 0 : 1;
NVIC_SystemReset();
}
```
## 四、注意事项
- **向量表重定位**:应用程序编译时需将向量表偏移到对应Bank的起始地址(通过`VECT_TAB_OFFSET`宏),并在启动时调用`SCB->VTOR = APP_ADDR`。
- **CRC校验**:建议使用硬件CRC单元或软件CRC32,在固件末尾附加校验值,Bootloader在跳转前校验。
- **备份寄存器初始化**:确保RTC备份域已使能,且备份寄存器在复位后不丢失(需VBAT供电)。
- **Bank切换时序**:BKSWAP操作必须在无Flash访问时进行,建议在复位后立即执行,避免中断干扰。
- **调试注意**:使用ST-Link调试时,若切换了Bank,调试器可能无法识别,需手动复位或使用`ST-LINK Utility`设置。
- **双Bank容量**:确保每个Bank足够容纳固件,否则需压缩或分块传输。
## 五、总结
通过STM32H7的双Bank Flash和硬件交换机制,我们可以实现高效的OTA升级回滚方案。Bootloader负责校验和切换,应用程序负责接收和写入新固件,整个过程无需外部存储,且回滚速度极快。该设计显著提升了设备的可靠性和用户体验,是工业级产品的理想选择。