STM32H7双Bank模式下的固件升级失败自动回滚:硬件级保障机制深度解析
👁 2 阅读 · 2026-08-27 · 嵌入式
在工业与物联网场景中,固件升级失败可能导致设备变砖,造成高昂的维护成本。STM32H7系列凭借其双Bank闪存架构,提供了硬件级的原子性切换能力,使得升级失败后能自动回滚至旧固件。本文深入剖析双Bank模式的原理、配置步骤及完整代码实现,帮助开发者构建高可靠性的OTA升级方案。
# STM32H7双Bank模式下的固件升级失败自动回滚:硬件级保障机制深度解析
## 1. 为什么需要硬件级回滚?
传统单Bank升级流程中,新固件直接覆盖旧固件,一旦写入过程中发生断电、校验错误或新固件本身存在缺陷,设备将无法启动。软件层面的回滚方案(如Bootloader备份)需要额外存储空间和复杂的逻辑,而STM32H7的双Bank闪存从硬件层面解决了这一问题:两个独立的Bank(Bank1和Bank2)可以分别存储新旧固件,通过硬件标志位实现原子性切换,确保系统始终有一个可用的固件。
## 2. 双Bank模式原理
### 2.1 闪存架构
STM32H7系列(如H743、H750)内置2MB闪存,分为两个1MB的Bank。每个Bank拥有独立的地址空间(0x08000000-0x080FFFFF和0x08100000-0x081FFFFF),支持独立擦写。
### 2.2 切换机制
- **软件切换**:通过修改选项字节(Option Bytes)中的`nBOOT1`和`nBOOT0`位,或使用`FLASH_OB_GetBanks()`函数动态切换启动Bank。
- **硬件自动回滚**:当在Bank1中写入新固件时,若发生错误(如CRC校验失败),硬件会保持Bank2的旧固件有效,并在复位后自动从Bank2启动。
### 2.3 关键寄存器
- `FLASH_CR`:控制擦写操作,其中`BANK`位选择目标Bank。
- `FLASH_OBR`:反映当前启动Bank状态。
- `FLASH_SR`:错误标志位,如`PGSERR`(编程错误)、`WRPERR`(写保护错误)。
## 3. 配置步骤
### 3.1 启用双Bank模式
在STM32CubeMX中,选择芯片型号后,在`Memory`配置中启用`Dual Bank Mode`。若使用HAL库,需在初始化时调用:
```c
HAL_FLASH_Unlock();
FLASH_OBProgramType ob;
ob.OptionType = OPTIONBYTE_BANK;
ob.BANK = FLASH_BANK_1; // 或FLASH_BANK_2
ob.BANKMode = FLASH_BANK_MODE_DUAL;
HAL_FLASH_OBProgram(&ob);
HAL_FLASH_Lock();
```
### 3.2 链接脚本调整
需要将固件分为两个区域:
- Bank1固件(当前运行)链接到0x08000000
- Bank2固件(待升级)链接到0x08100000
在IAR或Keil中,通过分散加载文件或链接脚本定义两个执行区域。
### 3.3 固件升级流程
1. 接收新固件数据包,写入非活动Bank(例如当前运行Bank1,则写入Bank2)。
2. 写入完成后,读取回校验CRC或SHA256。
3. 校验通过后,设置`FLASH_OBR`中的`BANK`位切换启动Bank,并复位。
4. 若校验失败,直接复位,硬件自动从原Bank启动。
## 4. 完整代码示例
以下代码演示了在Bank2中写入新固件并切换启动Bank的过程(基于HAL库):
```c
#include "stm32h7xx_hal.h"
#define APP_BANK1_ADDR 0x08000000
#define APP_BANK2_ADDR 0x08100000
#define FLASH_PAGE_SIZE 0x2000 // 8KB per page
// 擦除Bank2指定区域
void Flash_EraseBank2(uint32_t startAddr, uint32_t endAddr) {
FLASH_EraseInitTypeDef eraseInit;
uint32_t pageError = 0;
eraseInit.TypeErase = FLASH_TYPEERASE_PAGES;
eraseInit.Banks = FLASH_BANK_2;
eraseInit.Page = (startAddr - APP_BANK2_ADDR) / FLASH_PAGE_SIZE;
eraseInit.NbPages = (endAddr - startAddr) / FLASH_PAGE_SIZE;
HAL_FLASH_Unlock();
if (HAL_FLASHEx_Erase(&eraseInit, &pageError) != HAL_OK) {
// 处理擦除错误
Error_Handler();
}
HAL_FLASH_Lock();
}
// 写入数据到Bank2
void Flash_WriteBank2(uint32_t destAddr, uint8_t *data, uint32_t size) {
HAL_FLASH_Unlock();
for (uint32_t i = 0; i < size; i += 8) {
uint64_t data64 = 0;
for (int j = 0; j < 8; j++) {
if (i + j < size) {
data64 |= ((uint64_t)data[i+j]) << (8*j);
}
}
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_QUADWORD, destAddr + i, data64) != HAL_OK) {
// 处理编程错误
Error_Handler();
}
}
HAL_FLASH_Lock();
}
// 校验Bank2数据(简单CRC示例)
uint32_t CalculateCRC(uint8_t *data, uint32_t size) {
uint32_t crc = 0xFFFFFFFF;
// 使用硬件CRC或软件实现
// 此处省略具体实现
return crc;
}
// 执行升级
void PerformFirmwareUpdate(uint8_t *newFirmware, uint32_t size) {
// 1. 擦除Bank2
Flash_EraseBank2(APP_BANK2_ADDR, APP_BANK2_ADDR + size);
// 2. 写入新固件
Flash_WriteBank2(APP_BANK2_ADDR, newFirmware, size);
// 3. 读取回校验
uint32_t expectedCRC = CalculateCRC(newFirmware, size);
uint32_t actualCRC = CalculateCRC((uint8_t*)APP_BANK2_ADDR, size);
if (expectedCRC != actualCRC) {
// 校验失败,直接复位,硬件自动回滚
NVIC_SystemReset();
}
// 4. 切换启动Bank到Bank2
FLASH_OBProgramType ob;
ob.OptionType = OPTIONBYTE_BANK;
ob.BANK = FLASH_BANK_2;
ob.BANKMode = FLASH_BANK_MODE_SINGLE; // 或保持双Bank模式
HAL_FLASH_Unlock();
HAL_FLASH_OBProgram(&ob);
HAL_FLASH_Lock();
// 5. 复位,从Bank2启动
NVIC_SystemReset();
}
```
## 5. 注意事项
- **地址对齐**:写入地址必须按8字节对齐(QUADWORD编程),否则HAL库会返回错误。
- **中断处理**:擦写闪存期间,应关闭可能触发中断的定时器或外设,避免中断服务函数访问闪存导致死锁。
- **电源稳定性**:升级过程中若掉电,硬件机制仍能保证旧固件可启动,但建议在升级前检测电源电压。
- **双Bank模式限制**:启用双Bank后,每个Bank容量减半,需确保固件大小不超过1MB。
- **选项字节编程**:切换启动Bank的操作会修改选项字节,需注意选项字节的写保护设置。
## 6. 总结
STM32H7的双Bank模式为OTA升级提供了硬件级的原子性保障,极大降低了升级失败的风险。通过合理配置和代码实现,开发者可以轻松构建具备自动回滚能力的固件升级系统。在实际项目中,建议结合Bootloader进行更复杂的升级流程管理,如多版本回退、加密校验等。