STM32H7 双 Bank 模式下在线固件升级的地址映射陷阱与规避
👁 1 阅读 · 2026-08-27 · 嵌入式
STM32H7 系列凭借双 Bank Flash 架构,可实现无缝在线固件升级(OTA),但地址映射的复杂性常导致升级失败或程序跑飞。本文深入剖析双 Bank 模式下的地址映射陷阱,包括 Bank 切换、Flash 重映射及中断向量表重定位,并提供完整的配置步骤与代码示例,帮助开发者规避常见坑点,确保升级稳定可靠。
# STM32H7 双 Bank 模式下在线固件升级的地址映射陷阱与规避
## 引言
在嵌入式系统中,在线固件升级(OTA)是提升产品可维护性的关键功能。STM32H7 系列凭借其双 Bank Flash 架构,支持在运行一个 Bank 的同时擦写另一个 Bank,从而实现无缝升级。然而,双 Bank 模式下的地址映射机制复杂,稍有不慎便会导致升级失败、程序跑飞甚至变砖。本文基于 STM32H743 平台,深入剖析双 Bank 模式下的地址映射陷阱,并提供实用的规避策略。
## 双 Bank 模式基础
STM32H7 的 Flash 容量通常为 1MB 或 2MB,被划分为两个 Bank:Bank1 和 Bank2。每个 Bank 可独立擦写,且支持两种映射模式:
- **单 Bank 模式**:两个 Bank 连续编址,例如 0x08000000-0x080FFFFF(1MB),0x08100000-0x081FFFFF(1MB)。
- **双 Bank 模式**:两个 Bank 交替映射到低地址区域,例如 0x08000000 对应 Bank1 的扇区 0,0x08020000 对应 Bank2 的扇区 0,以此类推。这种模式使得 CPU 可以在 Bank1 运行时,对 Bank2 进行擦写操作。
双 Bank 模式通过选项字节 `FLASH_OPTCR` 中的 `DBANK` 位配置。默认情况下,STM32H7 处于单 Bank 模式,需在烧录选项字节时启用双 Bank。
## 地址映射陷阱
### 陷阱一:Bank 切换后的地址错乱
在双 Bank 模式下,Flash 的物理地址与逻辑地址不再一一对应。例如,Bank1 的扇区 0 在逻辑地址 0x08000000,而 Bank2 的扇区 0 在逻辑地址 0x08020000(假设每个扇区 128KB)。如果固件代码中直接使用物理地址访问 Flash,升级后可能访问到错误的 Bank。
**规避**:始终使用逻辑地址进行 Flash 读写,并通过 `FLASH_Bank1` 或 `FLASH_Bank2` 枚举指定目标 Bank,避免硬编码地址。
### 陷阱二:中断向量表未重定位
升级后,新固件可能位于 Bank2,但 CPU 默认从 0x08000000 读取中断向量表。若未重定位向量表,中断将跳转到旧固件的处理函数,导致系统崩溃。
**规避**:在启动代码中,根据当前运行的 Bank 动态设置 `SCB->VTOR` 寄存器。例如,若固件运行在 Bank2,则设置 `SCB->VTOR = 0x08020000`。
### 陷阱三:Flash 重映射(Remap)的误区
STM32H7 支持通过 `FLASH_CR` 寄存器的 `REMAP` 位,将 Bank2 重映射到地址 0x08000000,从而让 CPU 从 Bank2 启动。但此操作必须在 Bank1 完全停止运行后进行,否则会导致总线冲突。
**规避**:在跳转到新固件前,先关闭所有中断,确保无 Flash 访问,再设置重映射位,并执行系统复位。
## 配置步骤
### 1. 启用双 Bank 模式
在烧录选项字节时,设置 `DBANK=1`。可使用 STM32CubeProgrammer 或代码中调用 HAL 库函数:
```c
HAL_FLASH_Unlock();
FLASH_OB_Program(OB_DBANK_MODE, 0); // 0 表示双 Bank
HAL_FLASH_Lock();
// 需复位生效
```
### 2. 设计 Bootloader 与 App 分区
建议将 Bootloader 放在 Bank1 起始区域,App1 放在 Bank1 剩余空间,App2 放在 Bank2。例如:
- Bootloader: 0x08000000 - 0x0801FFFF (128KB)
- App1: 0x08020000 - 0x080FFFFF (896KB)
- App2: 0x08100000 - 0x081FFFFF (1MB)
注意:双 Bank 模式下,Bank2 的逻辑地址可能被重映射,因此 App2 的链接地址需根据实际映射调整。
### 3. Bootloader 跳转逻辑
Bootloader 根据标志位决定跳转到 App1 或 App2。跳转前需重定位向量表:
```c
void jump_to_app(uint32_t app_addr) {
// 关闭全局中断
__disable_irq();
// 设置向量表偏移
SCB->VTOR = app_addr;
// 获取栈顶指针和复位向量
uint32_t stack_top = *(volatile uint32_t*)app_addr;
void (*reset_handler)(void) = (void (*)(void))(*(volatile uint32_t*)(app_addr + 4));
// 设置 MSP 并跳转
__set_MSP(stack_top);
reset_handler();
}
```
### 4. App 固件中的向量表重定位
App 固件在启动时需再次确认向量表位置,防止 Bootloader 跳转后未正确设置:
```c
// 在 main 函数开头
if ((*(volatile uint32_t*)0x08100000) != 0xFFFFFFFF) {
SCB->VTOR = 0x08100000; // 若运行在 Bank2
} else {
SCB->VTOR = 0x08020000; // 若运行在 Bank1 的 App1
}
```
## 完整代码示例
以下是一个简化的 Bootloader 跳转示例,包含 Bank 切换和向量表重定位:
```c
#include "stm32h7xx_hal.h"
#define APP1_ADDR 0x08020000
#define APP2_ADDR 0x08100000
void jump_to_app(uint32_t app_addr) {
__disable_irq();
SCB->VTOR = app_addr;
uint32_t stack_top = *(volatile uint32_t*)app_addr;
void (*reset_handler)(void) = (void (*)(void))(*(volatile uint32_t*)(app_addr + 4));
__set_MSP(stack_top);
reset_handler();
}
int main(void) {
HAL_Init();
// 检查升级标志,假设在备份寄存器中
if (__HAL_RCC_GET_FLAG(RCC_FLAG_BORRST) == RESET) {
// 默认跳转 App1
jump_to_app(APP1_ADDR);
} else {
// 跳转 App2
jump_to_app(APP2_ADDR);
}
while (1);
}
```
App 固件中,需在启动文件或 main 开头重定位向量表:
```c
// 在 main 函数最前面
void SystemInit(void) {
// 检查当前运行地址
if ((uint32_t)&main > 0x08100000) {
SCB->VTOR = 0x08100000;
} else {
SCB->VTOR = 0x08020000;
}
}
```
## 注意事项
- **Flash 擦写时序**:在双 Bank 模式下,擦写 Bank2 时,Bank1 仍可执行代码,但需确保中断服务函数不访问 Flash,否则会触发总线错误。
- **选项字节修改**:修改 `DBANK` 位后必须复位才能生效,且复位后 Flash 映射会改变,需重新调整链接脚本。
- **链接脚本**:App2 的链接脚本中,`FLASH` 起始地址应设为 0x08100000,但若启用了重映射,实际运行时地址可能变为 0x08000000,需在代码中处理。
- **调试技巧**:使用 ST-Link 的 `set var` 命令查看 `SCB->VTOR` 值,确认向量表是否正确。
## 结语
STM32H7 双 Bank 模式为 OTA 提供了强大支持,但地址映射的复杂性要求开发者深入理解硬件机制。通过合理规划分区、正确重定位向量表、谨慎使用重映射,可有效规避陷阱,实现稳定可靠的在线升级。希望本文能帮助你在实际项目中少走弯路。