STM32H7 双 Bank 模式下在线固件升级:地址映射与跳转陷阱深度解析
👁 1 阅读 · 2026-08-27 · 嵌入式
STM32H7 系列凭借其强大的 Cortex-M7 内核和双 Bank Flash 架构,为在线固件升级(OTA)提供了灵活高效的方案。然而,双 Bank 模式下的地址映射、跳转逻辑及中断向量处理隐藏着诸多陷阱,稍有不慎便会导致升级失败或程序跑飞。本文深入剖析 STM32H7 双 Bank 模式的工作原理,详细讲解 Bootloader 与 APP 的地址映射策略、跳转实现步骤及常见陷阱,并提供完整的工程代码示例,助你避开雷区,实现稳定可靠的固件升级。
# STM32H7 双 Bank 模式下在线固件升级:地址映射与跳转陷阱深度解析
## 一、双 Bank 模式概述
STM32H7 系列(如 STM32H743、H750)内置双 Bank Flash,每个 Bank 容量相等(如 1MB+1MB)。双 Bank 模式允许在运行一个 Bank 中的程序时,对另一个 Bank 进行擦写操作,从而实现无缝的固件升级。这种架构天然支持 A/B 分区,即 Bootloader 和 APP 可分别位于不同 Bank,或两个 APP 互为备份。
**关键特性:**
- 双 Bank 可独立擦写,支持同时读写(RWW)
- 支持硬件自动切换 Bank 启动(通过选项字节)
- 每个 Bank 拥有独立的地址空间,但映射方式受选项字节控制
## 二、地址映射与启动机制
### 2.1 默认映射(单 Bank 模式)
默认情况下,STM32H7 的 Flash 从 `0x08000000` 开始连续映射,Bank1 和 Bank2 依次排列。例如,1MB+1MB 的芯片,Bank1 地址为 `0x08000000-0x080FFFFF`,Bank2 为 `0x08100000-0x081FFFFF`。
### 2.2 双 Bank 映射(SWAP 模式)
通过设置选项字节 `nSWAP_BANK`,可以交换 Bank1 和 Bank2 的映射地址。当 `SWAP_BANK=1` 时,Bank2 被映射到 `0x08000000`,而 Bank1 被映射到 `0x08100000`。这种机制允许 Bootloader 在 Bank1 中运行,同时将新固件写入 Bank2,然后通过软件触发交换,使新固件在下次复位后从 `0x08000000` 启动。
**地址映射示意:**
```
默认模式:
0x08000000 - 0x080FFFFF : Bank1 (Bootloader)
0x08100000 - 0x081FFFFF : Bank2 (APP)
SWAP模式:
0x08000000 - 0x080FFFFF : Bank2 (APP)
0x08100000 - 0x081FFFFF : Bank1 (Bootloader)
```
## 三、Bootloader 与 APP 的地址规划
### 3.1 典型分区方案
- **Bootloader**:位于 Bank1 起始地址 `0x08000000`,大小 32KB(`0x08000000 - 0x08007FFF`)
- **APP**:位于 Bank2 起始地址 `0x08100000`(默认映射),或通过 SWAP 后映射到 `0x08000000`
### 3.2 链接脚本配置
**Bootloader 的链接脚本(.ld)关键部分:**
```c
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 32K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
```
**APP 的链接脚本(.ld)关键部分:**
```c
FLASH (rx) : ORIGIN = 0x08100000, LENGTH = 1M - 32K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
```
注意:APP 的起始地址必须与 Bank2 的基地址对齐,且中断向量表需重定位。
## 四、跳转实现与陷阱规避
### 4.1 跳转原理
Bootloader 在收到完整固件并校验通过后,需要跳转到 APP 执行。跳转的核心步骤:
1. 关闭全局中断
2. 设置主栈指针(MSP)为 APP 的初始栈顶值
3. 设置向量表偏移寄存器(VTOR)指向 APP 的中断向量表
4. 跳转到 APP 的复位处理函数(Reset_Handler)
### 4.2 完整跳转代码示例
```c
// Bootloader 跳转函数
void jump_to_app(uint32_t app_addr)
{
// 检查 APP 栈顶地址是否合法(RAM 范围内)
uint32_t app_stack = *(volatile uint32_t *)app_addr;
if ((app_stack & 0xFFF00000) != 0x20000000)
{
return; // 非法地址,拒绝跳转
}
// 关闭全局中断
__disable_irq();
// 关闭所有外设中断(可选,根据实际外设情况)
for (int i = 0; i < 8; i++)
{
NVIC->ICER[i] = 0xFFFFFFFF;
NVIC->ICPR[i] = 0xFFFFFFFF;
}
// 设置主栈指针
__set_MSP(app_stack);
// 设置向量表偏移(仅适用于 Cortex-M7)
SCB->VTOR = app_addr;
// 获取复位处理函数地址
uint32_t reset_handler = *(volatile uint32_t *)(app_addr + 4);
// 跳转
void (*jump_func)(void) = (void (*)(void))reset_handler;
jump_func();
// 不会执行到这里
while (1);
}
```
### 4.3 常见陷阱与解决方案
#### 陷阱1:VTOR 设置无效
**现象**:跳转后中断无法触发或死机。
**原因**:STM32H7 的 VTOR 要求偏移量按 256 字节对齐(`VTOR_TBLBASE` 位域),且 APP 的链接脚本中 `VECT_TAB_OFFSET` 必须与 Flash 地址偏移一致。
**解决**:确保 APP 工程中 `system_stm32h7xx.c` 的 `VECT_TAB_OFFSET` 设置为 `0x100000`(即 Bank2 偏移),并在跳转前正确设置 `SCB->VTOR`。
```c
// APP 工程中设置向量表偏移
#define VECT_TAB_OFFSET 0x100000U /*!< Vector Table base offset field */
```
#### 陷阱2:跳转后系统时钟异常
**现象**:APP 运行但外设工作不正常,如串口乱码。
**原因**:Bootloader 配置了时钟,APP 启动时重新初始化时钟,但若 Bootloader 中启用了看门狗或低功耗模式,可能导致 APP 初始化失败。
**解决**:跳转前关闭所有外设时钟(`__HAL_RCC_DEINIT()`),并复位时钟配置。
```c
// 跳转前复位时钟
HAL_RCC_DeInit();
```
#### 陷阱3:双 Bank 交换后跳转地址错误
**现象**:使用 SWAP 功能后,APP 无法启动。
**原因**:SWAP 操作后,Bank2 映射到 `0x08000000`,但 APP 的链接脚本仍指向 `0x08100000`,导致跳转地址错误。
**解决**:若使用 SWAP,APP 应始终链接到 `0x08000000`(即 Bank1 的地址),但实际存储在 Bank2。Bootloader 跳转时,需根据当前映射状态决定跳转地址。
```c
// 检查当前映射状态(通过选项字节)
if (FLASH->OPTSR_CUR & FLASH_OPTSR_SWAP_BANK)
{
// 已交换,APP 在 0x08000000
jump_to_app(0x08000000);
}
else
{
// 未交换,APP 在 0x08100000
jump_to_app(0x08100000);
}
```
#### 陷阱4:中断向量表对齐问题
**现象**:跳转后,中断服务函数无法执行。
**原因**:Cortex-M7 要求向量表地址按 256 字节对齐,且向量表大小需为 2 的幂次。
**解决**:在 APP 的链接脚本中,确保 `.isr_vector` 段对齐到 256 字节。
```c
.isr_vector :
{
. = ALIGN(256);
KEEP(*(.isr_vector))
. = ALIGN(256);
} >FLASH
```
## 五、完整 Bootloader 升级流程示例
以下是一个简化的 Bootloader 主程序,演示了接收固件、写入 Bank2、校验并跳转的完整流程。
```c
// Bootloader 主程序(简化)
int main(void)
{
HAL_Init();
SystemClock_Config();
UART_Init();
// 检查是否需要升级(如通过串口命令)
if (check_update_request())
{
// 擦除 Bank2
erase_bank2();
// 接收固件并写入 Bank2
receive_firmware_to_bank2();
// 校验固件(CRC或SHA)
if (verify_firmware())
{
// 设置 SWAP 位(可选,若使用硬件交换)
set_swap_bank();
// 跳转到 APP(根据映射状态)
if (FLASH->OPTSR_CUR & FLASH_OPTSR_SWAP_BANK)
jump_to_app(0x08000000);
else
jump_to_app(0x08100000);
}
else
{
// 校验失败,回滚或重试
error_handler();
}
}
else
{
// 直接跳转到现有 APP
jump_to_app(0x08100000);
}
while (1);
}
```
## 六、注意事项与最佳实践
- **固件校验**:务必在跳转前进行完整性校验(如 CRC32),避免执行损坏的固件。
- **看门狗处理**:跳转前关闭看门狗,或在 APP 中尽快重新初始化。
- **外设状态**:跳转前将所有外设恢复到默认状态,避免 APP 初始化冲突。
- **调试接口**:在跳转后,调试器可能无法正常工作,建议在 APP 中重新配置调试端口。
- **双 Bank 交换**:使用 SWAP 时,需确保 APP 链接地址与映射后的地址一致,否则会跳转失败。
## 七、总结
STM32H7 双 Bank 模式为 OTA 提供了强大的硬件支持,但地址映射和跳转逻辑是其中的核心难点。理解默认映射与 SWAP 映射的区别,正确配置链接脚本和向量表偏移,并规避常见陷阱,才能实现稳定可靠的在线升级。希望本文的剖析和代码示例能帮助你少走弯路,构建出健壮的 Bootloader 系统。