STM32 双Bank切换实现OTA零停机:基于F4/F7的Flash重映射实战
👁 1 阅读 · 2026-08-27 · 嵌入式
在嵌入式OTA升级中,停机等待是常见痛点。STM32 F4/F7系列的双Bank Flash架构允许在运行中写入非活动Bank,并通过硬件重映射实现瞬时切换,达成零停机升级。本文深入解析双Bank原理、配置步骤,并给出基于HAL库的完整代码示例,助你掌握这一高级技巧。
# STM32 双Bank切换实现OTA零停机:基于F4/F7的Flash重映射实战
## 1. 为什么需要双Bank?
传统OTA升级流程:擦除旧固件→写入新固件→跳转重启。这个过程通常需要数秒甚至更久,期间设备无法响应外部请求,造成服务中断。对于工业控制、医疗设备或IoT网关,这种停机可能带来严重后果。
STM32 F4/F7系列(如STM32F427、STM32F746)提供了双Bank Flash架构,将Flash分为两个独立的Bank(如Bank1和Bank2),每个Bank可独立擦写。升级时,CPU从Bank1运行,同时擦写Bank2并写入新固件;完成后,通过修改选项字节或触发硬件重映射,CPU在下次复位后直接从Bank2启动,整个过程无需等待Flash擦写,实现零停机。
## 2. 双Bank原理与重映射机制
### 2.1 Flash组织
以STM32F746为例,其Flash大小为1MB,分为两个Bank:
- Bank1:地址0x08000000 - 0x0807FFFF(512KB)
- Bank2:地址0x08080000 - 0x080FFFFF(512KB)
每个Bank内部又分为多个扇区(Sector),擦除操作以扇区为单位。
### 2.2 重映射机制
双Bank切换的核心是`FLASH_OPTCR`寄存器中的`SWAP_BANK`位。当该位设置为1时,硬件将Bank1和Bank2的地址空间互换:
- 原Bank2的地址变为0x08000000,CPU从该地址启动
- 原Bank1的地址变为0x08080000
这种映射是硬件级的,无需修改向量表或链接脚本,切换后中断向量自动指向新Bank。
### 2.3 切换流程
1. 在Bank1中运行当前固件
2. 擦除Bank2,写入新固件(通过IAP或外部通信)
3. 设置`SWAP_BANK`位,并触发系统复位
4. 复位后,CPU从Bank2启动,完成切换
注意:切换操作需要解锁Flash选项字节,并且复位后`SWAP_BANK`位保持有效,直到再次修改。
## 3. 配置步骤
### 3.1 硬件要求
- 支持双Bank的STM32系列(如F4xx、F7xx)
- 外部通信接口(UART、USB、以太网等)用于接收固件
### 3.2 软件配置
1. **使能Flash预取和缓存**:确保Flash读取性能
2. **解锁Flash**:允许擦写操作
3. **擦除目标Bank**:擦除Bank2的所有扇区
4. **写入新固件**:将接收到的数据写入Bank2对应地址
5. **设置SWAP_BANK位**:通过修改选项字节
6. **复位系统**:触发重映射
## 4. 完整代码示例
以下代码基于STM32F746 + HAL库,演示从Bank1切换到Bank2的过程。
```c
#include "stm32f7xx_hal.h"
/* 定义Bank2起始地址和大小 */
#define BANK2_START_ADDR 0x08080000
#define BANK2_SIZE 512 * 1024
/* 函数:擦除Bank2 */
void Flash_EraseBank2(void)
{
FLASH_EraseInitTypeDef eraseInit;
uint32_t sectorError = 0;
eraseInit.TypeErase = FLASH_TYPEERASE_SECTORS;
eraseInit.Banks = FLASH_BANK_2;
eraseInit.Sector = 0;
eraseInit.NbSectors = 8; // 根据实际扇区数调整
eraseInit.VoltageRange = FLASH_VOLTAGE_RANGE_3;
if (HAL_FLASHEx_Erase(&eraseInit, §orError) != HAL_OK)
{
// 错误处理
Error_Handler();
}
}
/* 函数:写入数据到Bank2 */
void Flash_WriteBank2(uint32_t offset, uint32_t *data, uint32_t len)
{
uint32_t addr = BANK2_START_ADDR + offset;
for (uint32_t i = 0; i < len; i++)
{
if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, addr + i*4, data[i]) != HAL_OK)
{
Error_Handler();
}
}
}
/* 函数:切换Bank并复位 */
void Flash_SwapBankAndReset(void)
{
HAL_FLASH_Unlock();
// 设置SWAP_BANK位
FLASH_OBProgramInitTypeDef obInit;
obInit.OptionType = OPTIONBYTE_USER;
obInit.UserType = OB_USER_SWAP_BANK;
obInit.UserConfig = OB_SWAP_BANK_ENABLE;
HAL_FLASHEx_OBProgram(&obInit);
// 触发系统复位
HAL_NVIC_SystemReset();
}
/* 主函数示例 */
int main(void)
{
HAL_Init();
// 解锁Flash
HAL_FLASH_Unlock();
// 擦除Bank2
Flash_EraseBank2();
// 假设通过UART接收固件数据,写入Bank2
uint32_t fw_data[1024];
// 接收数据...
Flash_WriteBank2(0, fw_data, 1024);
// 切换Bank并复位
Flash_SwapBankAndReset();
while(1);
}
```
## 5. 注意事项
- **扇区大小**:不同型号的扇区大小不同,擦除前需确认目标Bank的扇区布局。
- **写入对齐**:HAL的`HAL_FLASH_Program`要求数据为32位对齐,且地址必须按字对齐。
- **中断处理**:在擦写Flash期间,CPU会暂停执行,但双Bank模式下,从Bank1运行不影响Bank2擦写,但需确保中断向量表在Bank1。
- **固件校验**:写入完成后,建议进行CRC或SHA校验,避免损坏固件导致启动失败。
- **回滚机制**:如果新固件启动失败,应提供回滚到旧Bank的机制(如通过Bootloader检测)。
- **选项字节保护**:设置`SWAP_BANK`位后,需重新上电或复位才生效,且该位会保持,直到再次修改。
## 6. 总结
双Bank切换技术充分利用了STM32的硬件特性,将OTA升级的停机时间降至零。通过合理设计,可以实现无缝升级,提升系统可用性。本文提供的代码和步骤可作为基础,实际项目中还需结合通信协议、固件校验和回滚策略,构建健壮的OTA系统。