STM32H7 双 Bank Flash 在线升级失败后自动回滚的硬件看门狗联动策略
👁 1 阅读 · 2026-08-27 · 嵌入式
在 STM32H7 系列 MCU 中,双 Bank Flash 为在线升级(OTA)提供了硬件级的安全保障,但升级过程中若发生意外(如断电、程序跑飞),可能导致系统变砖。本文深入探讨如何将硬件看门狗(IWDG)与双 Bank 切换机制联动,实现升级失败后的自动回滚,确保系统始终运行在可用固件上。文章涵盖原理分析、配置步骤、完整代码示例及关键注意事项,帮助开发者构建高可靠性的嵌入式升级方案。
# STM32H7 双 Bank Flash 在线升级失败后自动回滚的硬件看门狗联动策略
## 1. 为什么需要双 Bank + 看门狗联动?
在 STM32H7 系列中,Flash 被划分为两个独立的 Bank(Bank0 和 Bank1),每个 Bank 可独立擦写。利用这一特性,我们可以将当前运行固件放在一个 Bank,将新固件下载到另一个 Bank,然后通过切换启动地址完成升级。然而,升级过程并非绝对可靠:
- 固件传输中断(如网络异常)导致写入不完整。
- 固件校验失败(CRC 或哈希不匹配)。
- 新固件本身存在缺陷,启动后立即崩溃。
若没有回滚机制,系统可能永久变砖。硬件看门狗(IWDG)能在程序跑飞或死循环时复位 MCU,但单纯复位无法解决固件损坏问题。因此,我们需要将 IWDG 与双 Bank 状态标志结合,在复位后检测上次启动是否成功,若失败则自动回滚到旧版本。
## 2. 原理:双 Bank 切换与 IWDG 的协同工作
### 2.1 双 Bank 启动机制
STM32H7 的 Flash 选项字节(Option Bytes)中的 `nBOOT1` 和 `nBOOT0` 位控制启动 Bank。更灵活的方式是使用 `FLASH_OTP` 或备份寄存器(如 RTC 备份寄存器)存储当前活动 Bank 的标识。我们通常采用以下策略:
- **Bank0**:存放当前稳定运行的固件(旧版本)。
- **Bank1**:存放新下载的固件(待验证)。
升级流程:
1. 将新固件写入 Bank1。
2. 校验 Bank1 固件完整性(如 CRC32)。
3. 设置“待切换”标志,并复位。
4. 启动时检查标志,若存在则尝试从 Bank1 启动。
### 2.2 IWDG 的角色
IWDG 独立于主时钟,即使系统死机也能复位。我们利用它来检测新固件是否“健康”:
- 新固件启动后,必须在规定时间内(如 1 秒)喂狗,否则 IWDG 复位。
- 复位后,启动代码检查“启动失败计数”,若超过阈值(如 3 次),则判定新固件不可用,自动回滚到 Bank0。
### 2.3 状态标志存储
状态标志可存放在备份寄存器(如 `RTC->BKPxR`)或 Flash 的专用区域。备份寄存器在复位后不丢失,且无需擦写 Flash,推荐使用。
## 3. 配置步骤
### 3.1 硬件准备
- STM32H743 开发板(或其他 H7 系列)。
- 外部或内部复位源(IWDG 使用 LSI 时钟,无需外部晶振)。
### 3.2 软件配置
#### 步骤 1:使能 IWDG
在初始化代码中配置 IWDG,超时时间设为 1 秒(根据实际需求调整)。
```c
// 初始化 IWDG,LSI 约 32kHz,预分频 256,重载值 125 => 1s 超时
void IWDG_Init(void) {
// 解锁写保护
IWDG->KR = 0x5555;
// 设置预分频:256
IWDG->PR = 0x06;
// 设置重载值:125 (约 1s)
IWDG->RLR = 125;
// 等待更新完成
while (IWDG->SR & IWDG_SR_PVU);
while (IWDG->SR & IWDG_SR_RVU);
// 启动看门狗
IWDG->KR = 0xCCCC;
}
// 喂狗函数
void IWDG_Feed(void) {
IWDG->KR = 0xAAAA;
}
```
#### 步骤 2:备份寄存器读写
使用 RTC 备份寄存器存储状态标志。注意需使能 PWR 和 RTC 时钟。
```c
// 使能备份寄存器访问
void BKP_Enable(void) {
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_RCC_RTC_ENABLE();
HAL_PWR_EnableBkUpAccess();
}
// 写入标志
void BKP_Write(uint32_t reg, uint32_t val) {
RTC->BKPxR[reg] = val;
}
// 读取标志
uint32_t BKP_Read(uint32_t reg) {
return RTC->BKPxR[reg];
}
```
#### 步骤 3:启动流程设计
在 `main()` 函数开头,执行以下逻辑:
```c
#define BOOT_MAGIC_OK 0xA5A5A5A5 // 新固件启动成功标志
#define BOOT_MAGIC_FAIL 0xDEADBEEF // 新固件启动失败标志
#define MAX_RETRY 3 // 最大尝试次数
int main(void) {
// 初始化系统时钟等...
BKP_Enable();
// 读取当前启动计数
uint32_t retry_count = BKP_Read(0);
// 检查是否有待切换标志
if (BKP_Read(1) == 0x12345678) { // 待切换标志
// 尝试从 Bank1 启动(新固件)
if (retry_count < MAX_RETRY) {
retry_count++;
BKP_Write(0, retry_count);
// 设置启动地址为 Bank1
// 注意:实际切换需修改 VTOR 和向量表,或使用系统 Bootloader
// 此处简化:直接跳转
void (*jump)(void) = (void (*)(void))0x08100000; // Bank1 基地址
jump();
} else {
// 超过重试次数,回滚到 Bank0
BKP_Write(0, 0);
BKP_Write(1, 0); // 清除待切换标志
// 继续执行 Bank0 固件(当前代码)
}
}
// 正常启动流程
IWDG_Init();
// 如果是新固件首次启动,设置成功标志
if (BKP_Read(1) == 0x12345678) {
// 新固件运行正常,清除待切换标志,并设置成功标志
BKP_Write(1, 0);
BKP_Write(2, BOOT_MAGIC_OK);
}
// 主循环
while (1) {
IWDG_Feed(); // 喂狗
// 应用逻辑...
}
}
```
#### 步骤 4:升级函数示例
在 Bank0 固件中,执行升级操作:
```c
void OTA_Upgrade(void) {
// 1. 擦除 Bank1
FLASH_Erase_Bank(FLASH_BANK_1);
// 2. 写入新固件到 Bank1(通过串口/网络等)
// ... 写入过程 ...
// 3. 校验固件(如 CRC32)
if (CRC_Check(BANK1_ADDR, firmware_size) != 0) {
// 校验失败,中止升级
return;
}
// 4. 设置待切换标志
BKP_Write(1, 0x12345678);
BKP_Write(0, 0); // 重置重试计数
// 5. 复位系统
NVIC_SystemReset();
}
```
## 4. 完整代码示例(简化)
以下是一个整合的启动代码片段,展示了核心逻辑:
```c
void SystemInit(void) {
// ... 系统时钟初始化 ...
BKP_Enable();
uint32_t retry = BKP_Read(0);
uint32_t pending = BKP_Read(1);
if (pending == 0x12345678) {
if (retry < MAX_RETRY) {
retry++;
BKP_Write(0, retry);
// 跳转到 Bank1
JumpToBank1();
} else {
// 回滚
BKP_Write(0, 0);
BKP_Write(1, 0);
// 留在 Bank0
}
}
IWDG_Init();
// 新固件成功启动后,清除标志
if (pending == 0x12345678 && retry <= MAX_RETRY) {
BKP_Write(1, 0);
BKP_Write(2, BOOT_MAGIC_OK);
}
}
void JumpToBank1(void) {
// 设置主栈指针
uint32_t msp = *(volatile uint32_t*)0x08100000;
__set_MSP(msp);
// 跳转到复位向量
void (*reset)(void) = (void (*)(void))(*(volatile uint32_t*)0x08100004);
reset();
}
```
## 5. 注意事项
- **IWDG 超时时间**:需大于新固件初始化所需时间,否则可能误判失败。建议设置为 1-2 秒,并在新固件启动后尽早喂狗。
- **备份寄存器初始化**:首次使用前,确保备份寄存器已清零,否则可能误触发回滚。
- **Flash 擦写保护**:操作 Flash 时需解锁,并注意中断优先级,避免擦写过程中被中断导致错误。
- **向量表重定位**:从 Bank1 启动时,必须将 VTOR 设置为 Bank1 的基地址,否则中断向量错误。
- **固件校验**:写入后务必进行 CRC 或哈希校验,防止不完整固件启动。
- **回滚次数限制**:设置最大重试次数(如 3 次),避免无限循环。
- **调试注意事项**:使用调试器时,IWDG 可能因暂停而复位,建议在调试时禁用 IWDG 或延长超时。
## 6. 总结
通过将 STM32H7 的双 Bank Flash 与硬件看门狗联动,我们构建了一个健壮的 OTA 升级回滚机制。核心思想是:利用备份寄存器记录状态,利用 IWDG 检测新固件是否正常运行,并在多次失败后自动回滚到旧版本。这种方法无需外部存储,成本低且可靠性高,适用于工业控制、物联网设备等对稳定性要求极高的场景。开发者可根据实际需求调整超时和重试策略,并确保在升级过程中严格遵循 Flash 操作规范。