STM32H7双Bank Flash在线升级失败后自动回滚的硬件级保障机制实现
👁 3 阅读 · 2026-08-27 · 嵌入式
在工业级嵌入式产品中,在线升级(OTA)的可靠性至关重要。STM32H7系列凭借其独特的双Bank Flash架构,提供了硬件级的升级失败自动回滚能力,无需外部存储或复杂软件协议。本文将深入剖析该机制的原理,并给出基于HAL库的完整实现代码,涵盖配置步骤、关键代码与实战注意事项,助你打造永不“变砖”的固件升级方案。
# STM32H7双Bank Flash在线升级失败后自动回滚的硬件级保障机制实现
## 1. 为什么需要硬件级回滚?
传统单Bank Flash升级流程中,新固件直接覆盖旧固件。若升级过程中断电、通信中断或固件校验失败,设备将无法启动,只能通过JTAG/SWD等外部接口恢复,这在现场环境下几乎不可行。STM32H7的双Bank Flash架构(如STM32H743/H750)将Flash分为两个独立的Bank(Bank1和Bank2),每个Bank可独立擦写和启动。利用这一特性,我们可以实现:
- **固件冗余**:旧固件保留在非活动Bank,新固件写入活动Bank。
- **硬件自动回滚**:若新固件启动失败(如看门狗超时),芯片可自动切换至旧Bank启动,无需软件干预。
## 2. 原理剖析:双Bank启动与回滚机制
### 2.1 双Bank Flash布局
STM32H7的Flash容量可达2MB,分为两个1MB的Bank。每个Bank可独立配置为启动区域。通过选项字节(Option Bytes)中的`nBOOT1`和`nBOOT0`引脚状态,或软件配置`FLASH_OPTCR`寄存器,可控制芯片从哪个Bank启动。
### 2.2 硬件自动回滚流程
1. **升级触发**:系统运行在Bank1(旧固件),接收新固件数据。
2. **写入Bank2**:将新固件写入Bank2(非活动Bank),写入完成后置位`FLASH_CR`中的`B2SB`位(Bank2 Swap)。
3. **切换启动**:设置`FLASH_OPTCR`的`SWAP_BANK`位,并触发选项字节重载,芯片重启后将从Bank2启动。
4. **运行新固件**:新固件运行后,需在启动早期(如main函数开头)执行“启动成功”标记(如写特定标志到备份寄存器或Flash)。
5. **失败回滚**:若新固件启动失败(如硬件错误、看门狗复位),芯片在复位后检测到上次启动未成功标记,硬件自动将启动地址切换回Bank1,并清除Swap标志,实现回滚。
> **关键点**:硬件回滚依赖于“启动成功标记”的软件配合。若新固件在标记前崩溃,硬件将自动回滚;若标记后崩溃,则视为新固件已正常启动,不再回滚(避免死循环)。
## 3. 实现步骤与代码示例
### 3.1 硬件配置
- **MCU**:STM32H743ZI(双Bank 2MB Flash)
- **开发环境**:STM32CubeIDE + HAL库
- **关键外设**:UART(用于接收固件)、IWDG(独立看门狗)
### 3.2 启动成功标记机制
在备份寄存器(RTC Backup Register)中定义两个标志:`BOOT_SUCCESS_FLAG`和`BOOT_ATTEMPTS`。
```c
#define BOOT_SUCCESS_FLAG_ADDR 0x01 // 备份寄存器索引
#define BOOT_ATTEMPTS_ADDR 0x02
#define BOOT_SUCCESS_MAGIC 0xA5A5 // 成功标记魔数
#define MAX_BOOT_ATTEMPTS 3 // 最大启动尝试次数
void Boot_MarkSuccess(void) {
HAL_RTCEx_BKUPWrite(&hrtc, BOOT_SUCCESS_FLAG_ADDR, BOOT_SUCCESS_MAGIC);
}
void Boot_ClearSuccess(void) {
HAL_RTCEx_BKUPWrite(&hrtc, BOOT_SUCCESS_FLAG_ADDR, 0);
}
uint8_t Boot_IsSuccess(void) {
return (HAL_RTCEx_BKUPRead(&hrtc, BOOT_SUCCESS_FLAG_ADDR) == BOOT_SUCCESS_MAGIC);
}
void Boot_IncrementAttempts(void) {
uint32_t attempts = HAL_RTCEx_BKUPRead(&hrtc, BOOT_ATTEMPTS_ADDR);
HAL_RTCEx_BKUPWrite(&hrtc, BOOT_ATTEMPTS_ADDR, attempts + 1);
}
void Boot_ClearAttempts(void) {
HAL_RTCEx_BKUPWrite(&hrtc, BOOT_ATTEMPTS_ADDR, 0);
}
```
### 3.3 固件升级与Bank切换
升级函数负责接收固件、写入非活动Bank,并设置Swap标志。
```c
void Firmware_Upgrade(uint8_t *data, uint32_t len) {
// 1. 解锁Flash
HAL_FLASH_Unlock();
// 2. 擦除非活动Bank(假设当前从Bank1启动,则擦除Bank2)
FLASH_EraseInitTypeDef erase = {0};
erase.TypeErase = FLASH_TYPEERASE_MASS; // 注意:此处应针对Bank2,但H7的MASS擦除会擦除所有Bank,需使用BANK擦除
erase.Banks = FLASH_BANK_2;
erase.NbPages = 255; // 1MB / 4KB页
uint32_t error = 0;
HAL_FLASHEx_Erase(&erase, &error);
// 3. 写入新固件到Bank2基地址(0x08100000)
uint32_t addr = 0x08100000;
for (uint32_t i = 0; i < len; i += 8) {
uint64_t data64 = *(uint64_t *)(data + i);
HAL_FLASH_Program(FLASH_TYPEPROGRAM_QUADWORD, addr, data64);
addr += 8;
}
// 4. 设置Swap标志,使下次启动从Bank2开始
HAL_FLASH_OB_Unlock();
FLASH_OBProgramInitTypeDef ob = {0};
ob.OptionType = OPTIONBYTE_USER;
ob.USERType = OB_USER_B2SB;
ob.USERConfig = OB_B2SB_ENABLE; // 使能Bank2启动
HAL_FLASHEx_OBProgram(&ob);
HAL_FLASH_OB_Lock();
// 5. 触发系统复位
HAL_FLASH_Lock();
NVIC_SystemReset();
}
```
### 3.4 启动流程中的回滚逻辑
在main函数开头,执行以下逻辑:
```c
int main(void) {
HAL_Init();
SystemClock_Config();
// 检查当前启动Bank
uint32_t current_bank = (FLASH->OPTCR & FLASH_OPTCR_SWAP_BANK) ? 2 : 1;
if (current_bank == 2) {
// 从Bank2启动(新固件)
if (Boot_IsSuccess()) {
// 上次启动成功,本次正常,清除尝试计数
Boot_ClearAttempts();
} else {
// 上次启动未成功,增加尝试次数
Boot_IncrementAttempts();
if (Boot_GetAttempts() > MAX_BOOT_ATTEMPTS) {
// 超过最大尝试,回滚到Bank1
HAL_FLASH_OB_Unlock();
FLASH_OBProgramInitTypeDef ob = {0};
ob.OptionType = OPTIONBYTE_USER;
ob.USERType = OB_USER_B2SB;
ob.USERConfig = OB_B2SB_DISABLE; // 禁用Bank2启动,回滚
HAL_FLASHEx_OBProgram(&ob);
HAL_FLASH_OB_Lock();
NVIC_SystemReset();
}
}
} else {
// 从Bank1启动(旧固件)
// 正常启动,清除尝试计数
Boot_ClearAttempts();
}
// 启动成功标记(在系统初始化完成后调用)
Boot_MarkSuccess();
// 用户应用代码...
}
```
### 3.5 看门狗配合
为了检测新固件是否“卡死”,启用独立看门狗(IWDG),并在主循环中定期喂狗。若新固件崩溃,看门狗复位,从而触发回滚逻辑。
```c
void IWDG_Init(void) {
// 配置IWDG,超时1秒
IWDG_HandleTypeDef hiwdg = {0};
hiwdg.Instance = IWDG;
hiwdg.Init.Prescaler = IWDG_PRESCALER_64;
hiwdg.Init.Reload = 4095; // 约1秒
HAL_IWDG_Init(&hiwdg);
}
// 在主循环中
HAL_IWDG_Refresh(&hiwdg);
```
## 4. 注意事项
- **Bank大小匹配**:确保新固件大小不超过Bank容量(1MB)。若固件超过,需使用外部Flash或压缩。
- **擦除操作**:H7的擦除必须按Bank进行,且擦除期间不可中断,否则可能导致Flash损坏。建议在升级前关闭中断。
- **选项字节重载**:设置Swap后,必须触发系统复位(或调用`HAL_FLASH_OB_Launch()`)才能生效。
- **启动成功标记时机**:务必在系统初始化完成、外设就绪后标记成功,避免过早标记导致回滚失效。
- **备份寄存器供电**:备份寄存器在VDD掉电后数据丢失,若需长期保存,需使用外部电池或超级电容。
- **调试注意事项**:在调试时,若使用ST-Link,可能干扰选项字节,建议在调试前备份选项字节。
## 5. 总结
通过STM32H7的双Bank Flash和硬件Swap机制,我们实现了一个健壮的OTA升级回滚方案。该方案无需外部存储,仅依赖芯片内部资源,即可在升级失败时自动恢复至旧固件,极大提升了产品的可靠性和可维护性。开发者可根据实际需求调整尝试次数、标记时机等参数,以适应不同场景。