STM32双Bank切换实现OTA零停机:基于F4/F7的Bank映射与跳转细节
👁 1 阅读 · 2026-08-27 · 嵌入式
在工业物联网和远程升级场景中,OTA固件更新往往面临停机风险。STM32 F4/F7系列的双Bank闪存架构,通过硬件级Bank切换实现无缝固件升级,无需重启即可运行新程序。本文深入解析双Bank的地址映射、标志位配置、跳转细节及实际代码实现,助你掌握零停机OTA的核心技术。
# STM32双Bank切换实现OTA零停机:基于F4/F7的Bank映射与跳转细节
## 为什么需要双Bank?
传统OTA升级通常采用A/B分区方案:将固件写入备用分区,校验后通过Bootloader跳转。但跳转过程需要复位,导致设备短暂停机(通常数百毫秒至数秒)。对于要求高可用性的系统(如工业控制器、医疗设备),这种停机不可接受。STM32 F4/F7系列(如STM32F427、STM32F746)内置双Bank闪存,允许在运行Bank A时擦写Bank B,并通过硬件寄存器即时切换映射,实现零停机升级。
## 双Bank架构与地址映射
### 物理结构
- 闪存被分为两个相等的Bank:Bank 1(地址0x08000000)和Bank 2(地址0x08040000,以1MB闪存为例)。
- 每个Bank独立擦除/编程,但共享数据总线。
### 映射机制
通过设置选项字节中的`DBANK`位(如F7系列)或`BKB`位(部分F4系列),可切换Bank映射:
- **单Bank模式**:默认,闪存连续编址,Bank 2地址紧随Bank 1。
- **双Bank模式**:两个Bank独立编址,但可通过`FLASH_CR`寄存器的`SWAP_BANK`位动态交换映射。例如,当`SWAP_BANK=1`时,Bank 1映射到0x08040000,Bank 2映射到0x08000000。
### 关键寄存器
- `FLASH_CR`:`SWAP_BANK`位(bit 31)控制映射交换。
- `FLASH_SR`:`BSY`位表示忙,`WBNE`位表示写缓冲非空。
- 选项字节:`DBANK`位(F7)或`BKB`位(F4)决定是否启用双Bank模式(需在系统复位后生效)。
## 零停机升级流程
1. **准备阶段**:系统运行在Bank A(地址0x08000000),固件版本V1.0。
2. **下载新固件**:通过通信接口(如UART、以太网)接收新固件V2.0,写入Bank B(地址0x08040000)。写入期间,Bank A继续运行,无中断。
3. **校验**:对Bank B的固件进行CRC或哈希校验,确保完整性。
4. **切换映射**:设置`SWAP_BANK=1`,硬件立即交换Bank映射,CPU从0x08000000取指时实际访问Bank B。
5. **跳转**:软件复位(NVIC_SystemReset)后,CPU从0x08000000重新启动,执行新固件。由于映射已交换,新固件直接运行,无需Bootloader干预。
6. **回滚**(可选):若新固件异常,可再次切换映射恢复旧固件。
## 配置步骤(以STM32F746为例)
### 1. 启用双Bank模式
在系统初始化时,通过修改选项字节启用双Bank(需在复位后生效):
```c
void enable_dual_bank(void) {
FLASH_OBProgramInitTypeDef ob;
HAL_FLASHEx_OBGetConfig(&ob);
ob.DBANK = FLASH_BANK_DUAL; // 双Bank模式
HAL_FLASHEx_OBProgram(&ob);
HAL_FLASH_OB_Launch(); // 触发选项字节重载,系统复位
}
```
注意:此操作会立即复位,因此应在首次部署时执行,而非OTA过程中。
### 2. 写入Bank B固件
使用标准HAL库函数,但需指定目标地址为Bank B基址:
```c
void write_firmware_to_bank2(uint32_t *data, uint32_t len) {
uint32_t addr = FLASH_BANK2_BASE; // 0x08040000
HAL_FLASH_Unlock();
__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL);
for (uint32_t i = 0; i < len; i += 4) {
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr + i, data[i/4]) != HAL_OK) {
// 错误处理
}
}
HAL_FLASH_Lock();
}
```
### 3. 切换Bank映射
写入完成后,设置`SWAP_BANK`位:
```c
void swap_bank(void) {
FLASH_CR->SWAP_BANK = 1;
while (FLASH_SR->BSY); // 等待操作完成
}
```
注意:切换后,当前执行的代码仍在原Bank中,但地址映射已变。此时若继续执行原Bank代码,会导致取指错误,因此必须立即复位。
### 4. 复位并运行新固件
```c
void jump_to_new_firmware(void) {
swap_bank();
NVIC_SystemReset(); // 复位后从0x08000000执行,实际访问Bank B
}
```
## 完整代码示例
以下是一个简化的OTA升级核心函数:
```c
#include "stm32f7xx_hal.h"
extern uint32_t _estack; // 栈顶地址(来自链接脚本)
void ota_update(uint8_t *new_fw, uint32_t size) {
// 1. 写入Bank B
HAL_FLASH_Unlock();
uint32_t addr = FLASH_BANK2_BASE;
for (uint32_t i = 0; i < size; i += 4) {
uint32_t word = *(uint32_t *)(new_fw + i);
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr + i, word);
}
HAL_FLASH_Lock();
// 2. 校验(此处省略,建议使用CRC)
// 3. 切换Bank并复位
FLASH_CR->SWAP_BANK = 1;
while (FLASH_SR->BSY);
NVIC_SystemReset();
}
// 新固件入口(在Bank B中)
void new_firmware_main(void) {
// 实际应用中,复位后自动执行,无需手动调用
}
```
## 跳转细节与注意事项
### 1. 中断向量表重定位
复位后,CPU从0x08000000读取栈顶指针和复位向量。由于映射已交换,该地址实际指向Bank B。但若新固件的中断向量表未重定位,中断服务函数可能指向旧地址。解决方案:
- 在新固件启动代码中,设置`SCB->VTOR = 0x08000000`(或实际Bank基址)。
- 确保链接脚本将向量表放在Bank起始地址。
### 2. 固件大小限制
双Bank模式下,每个Bank大小等于总闪存的一半。例如1MB闪存,每个Bank 512KB。若固件超过此限制,需改用单Bank模式或压缩。
### 3. 切换时的时序
`SWAP_BANK`操作需要等待`BSY`清零,但切换本身是瞬间完成的。复位后,新固件立即执行,但需确保电源稳定,避免在切换瞬间掉电。
### 4. 回滚机制
若新固件运行失败,可通过外部信号或看门狗触发回滚:再次设置`SWAP_BANK=1`并复位,即可恢复旧固件。注意,回滚前需确保旧固件仍存在于原Bank(未被覆盖)。
### 5. 调试注意事项
- 使用ST-Link调试时,双Bank模式可能影响调试器的闪存识别,需在IDE中配置对应Bank。
- 在切换Bank前,建议关闭所有中断,避免在切换过程中产生异常。
## 总结
STM32双Bank切换技术通过硬件映射交换,实现了OTA升级的零停机。关键在于理解Bank映射机制、正确配置选项字节、以及处理中断向量表重定位。本文提供的代码和步骤可直接应用于F4/F7系列项目,但需根据具体型号调整寄存器定义。掌握此技术,可显著提升设备的可用性和升级体验。