STM32 双 Bank 切换实现 OTA 零停机:基于 F4/F7 的 Flash 重映射细节
👁 1 阅读 · 2026-08-27 · 嵌入式
在工业物联网和远程维护场景中,OTA 升级的停机时间往往意味着生产中断或数据丢失。STM32 F4/F7 系列的双 Bank Flash 结构,配合硬件重映射机制,可实现应用无缝切换,达成真正意义上的零停机升级。本文深入剖析双 Bank 切换的原理、Flash 重映射的寄存器级细节,并给出基于 HAL 库的完整工程配置与代码示例,助你掌握这一高价值嵌入式技能。
# STM32 双 Bank 切换实现 OTA 零停机:基于 F4/F7 的 Flash 重映射细节
## 1. 为什么需要双 Bank?
传统 OTA 方案通常采用 Bootloader + App 模式,升级时需擦写 App 区,期间系统必须停机。对于要求 7x24 小时连续运行的系统(如电力监控、医疗设备),这是不可接受的。STM32 F4/F7 系列(如 STM32F427、STM32F746)提供双 Bank Flash,每个 Bank 容量相等,可独立擦写。通过硬件重映射,CPU 可瞬间从 Bank1 跳转到 Bank2,无需复位,实现零停机升级。
## 2. 双 Bank 与 Flash 重映射原理
### 2.1 内存映射基础
以 STM32F746 为例,Flash 总容量 1MB,分为 Bank1(0x08000000-0x0807FFFF)和 Bank2(0x08080000-0x080FFFFF)。默认情况下,CPU 从 Bank1 启动。当启用双 Bank 模式并设置重映射位后,Bank2 的地址会被映射到 0x08000000,而 Bank1 则被映射到 0x08080000。
### 2.2 关键寄存器
- **FLASH_CR (Flash Control Register)**:位 31 (DBANK) 用于选择单 Bank 或双 Bank 模式(仅部分型号支持,需在空片时设置)。
- **FLASH_CR 位 30 (SWAP_BANK)**:当置 1 时,交换 Bank1 和 Bank2 的映射地址。
- **FLASH_SR (Status Register)**:位 15 (BSY) 表示 Flash 忙,切换前必须等待。
### 2.3 切换流程
1. 确保当前运行在 Bank1(或 Bank2),且目标 Bank 已写入新固件。
2. 设置 FLASH_CR 的 SWAP_BANK 位。
3. 等待 BSY 位清零。
4. 硬件自动重映射,PC 指针无需改动,但需注意中断向量表位置。
## 3. 工程配置步骤
### 3.1 链接脚本调整
需为两个 Bank 分别生成固件,或生成一个包含双 Bank 的镜像。以 STM32F746 为例,修改 `.ld` 文件:
```c
/* Bank1 应用 */
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
/* Bank2 应用 */
/* FLASH (rx) : ORIGIN = 0x08080000, LENGTH = 512K */
```
编译两个版本,分别烧录到对应 Bank。
### 3.2 中断向量表重定位
切换后,中断向量表仍指向旧地址,必须重定位。在系统初始化时,设置 SCB->VTOR:
```c
#define APP_ADDRESS_BANK1 0x08000000
#define APP_ADDRESS_BANK2 0x08080000
void relocate_vector_table(uint32_t app_address) {
SCB->VTOR = app_address;
__DSB();
__ISB();
}
```
## 4. 完整代码示例(基于 HAL 库)
### 4.1 双 Bank 切换函数
```c
#include "stm32f7xx_hal.h"
#define FLASH_BANK1_BASE 0x08000000
#define FLASH_BANK2_BASE 0x08080000
/**
* @brief 切换当前执行 Bank
* @param target_bank: 1 或 2
* @retval HAL_StatusTypeDef
*/
HAL_StatusTypeDef Flash_SwitchBank(uint8_t target_bank) {
uint32_t current_bank;
uint32_t primask;
// 判断当前运行 Bank
if ((SCB->VTOR & 0x000FFFFF) < 0x00080000) {
current_bank = 1;
} else {
current_bank = 2;
}
if (current_bank == target_bank) {
return HAL_OK; // 无需切换
}
// 关闭全局中断,防止切换期间中断触发
primask = __get_PRIMASK();
__disable_irq();
// 等待 Flash 空闲
while (FLASH->SR & FLASH_SR_BSY);
// 设置 SWAP_BANK 位
FLASH->CR |= FLASH_CR_SWAP_BANK;
// 等待切换完成
while (FLASH->SR & FLASH_SR_BSY);
// 重定位中断向量表到新 Bank
if (target_bank == 1) {
SCB->VTOR = FLASH_BANK1_BASE;
} else {
SCB->VTOR = FLASH_BANK2_BASE;
}
__DSB();
__ISB();
// 恢复中断
__set_PRIMASK(primask);
return HAL_OK;
}
```
### 4.2 固件写入函数(简化)
```c
HAL_StatusTypeDef Flash_WriteFirmware(uint32_t dest_addr, uint8_t *data, uint32_t len) {
uint32_t i = 0;
uint32_t word;
// 解锁 Flash
HAL_FLASH_Unlock();
// 擦除目标 Bank(示例擦除整个 Bank2)
FLASH_EraseInitTypeDef erase;
erase.TypeErase = FLASH_TYPEERASE_SECTORS;
erase.Banks = FLASH_BANK_2;
erase.Sector = 0;
erase.NbSectors = 8; // 根据实际扇区数调整
erase.VoltageRange = FLASH_VOLTAGE_RANGE_3;
uint32_t error;
HAL_FLASHEx_Erase(&erase, &error);
// 写入数据(按 32 位字)
for (i = 0; i < len; i += 4) {
word = (data[i] | (data[i+1] << 8) | (data[i+2] << 16) | (data[i+3] << 24));
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, dest_addr + i, word);
}
HAL_FLASH_Lock();
return HAL_OK;
}
```
### 4.3 主程序示例
```c
int main(void) {
HAL_Init();
SystemClock_Config();
// 假设当前运行在 Bank1,新固件已下载到 RAM 或外部存储
uint8_t new_firmware[1024]; // 示例数据
// 1. 将新固件写入 Bank2
Flash_WriteFirmware(FLASH_BANK2_BASE, new_firmware, sizeof(new_firmware));
// 2. 验证固件(CRC 等)
// ...
// 3. 切换 Bank
Flash_SwitchBank(2);
// 4. 切换后,代码继续执行,但实际已运行在 Bank2 的固件中
while (1) {
// 应用逻辑
}
}
```
## 5. 注意事项与陷阱
- **双 Bank 模式启用**:部分型号(如 STM32F4 早期版本)默认单 Bank,需在空片时通过选项字节设置 DBANK=1,否则无法使用双 Bank。
- **中断安全**:切换 Bank 期间必须关闭全局中断,否则中断服务函数可能访问旧向量表导致 HardFault。
- **Flash 擦写时间**:擦写 Bank 时系统仍可运行(从另一 Bank 执行),但需确保代码不在被擦写的 Bank 中。
- **固件验证**:切换前务必验证新固件完整性(CRC32 或 SHA256),避免变砖。
- **看门狗**:若使用独立看门狗,需在擦写过程中喂狗,否则系统复位。
- **编译器优化**:链接脚本中需将只读数据(如字符串)放在当前 Bank,否则切换后可能访问错误地址。
## 6. 总结
双 Bank 切换技术利用 STM32 硬件重映射,实现了 OTA 升级的零停机。关键在于理解 Flash 映射机制、正确配置寄存器,并妥善处理中断向量表和固件验证。本文提供的代码可直接应用于 F4/F7 系列,为你的产品赋予无缝升级能力。