STM32 双Bank切换实现OTA零停机:基于F4/F7的Bank交错映射实战
👁 3 阅读 · 2026-08-27 · 嵌入式
在嵌入式设备中,OTA升级常因Flash擦写导致系统停机。本文深入探讨STM32 F4/F7系列的双Bank架构,通过Bank交错映射实现应用无缝切换,达成零停机升级。你将掌握双Bank原理、配置步骤、完整代码示例及关键注意事项,为高可用固件更新提供实战方案。
# STM32 双Bank切换实现OTA零停机:基于F4/F7的Bank交错映射实战
## 1. 双Bank架构原理
STM32 F4/F7系列(如STM32F427、STM32F746)的Flash被划分为两个独立的Bank,每个Bank大小相等(例如1MB Flash分为两个512KB Bank)。这种设计允许在运行一个Bank时,对另一个Bank进行擦写操作,从而实现OTA升级不中断当前应用。
**关键特性:**
- **Bank交错映射**:通过配置选项字节(Option Bytes)中的BOR_LEV和DBANK位,可以切换Flash的映射方式。默认情况下,Flash从地址0x08000000开始连续映射;启用双Bank后,地址空间被交错映射,使得两个Bank的起始地址均为0x08000000,但通过高位地址选择Bank。
- **无缝切换**:升级完成后,通过软件复位或跳转指令,将PC指针切换到新Bank的复位向量,实现快速切换,无需外部干预。
## 2. 硬件与软件准备
- **硬件**:STM32F4/F7开发板(如Nucleo-F746ZG),外部Flash可选(用于备份固件)。
- **软件**:STM32CubeIDE(或Keil MDK),STM32 HAL库。
## 3. 配置步骤
### 3.1 启用双Bank模式
1. 在STM32CubeMX中,选择目标芯片,进入`Option Bytes`配置。
2. 设置`DBANK`位为`1`(双Bank模式),并配置`BOR_LEV`为合适的电平(如3.3V)。
3. 生成初始化代码。
### 3.2 链接脚本调整
修改链接脚本(.ld文件),将Flash区域划分为两个Bank:
```c
/* 假设Flash总大小1MB,每个Bank 512KB */
MEMORY
{
FLASH_BANK0 (rx) : ORIGIN = 0x08000000, LENGTH = 512K
FLASH_BANK1 (rx) : ORIGIN = 0x08080000, LENGTH = 512K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 256K
}
```
### 3.3 编写双Bank切换代码
```c
#include "stm32f7xx_hal.h"
// 定义Bank地址
#define BANK0_START 0x08000000
#define BANK1_START 0x08080000
// 当前运行Bank标志
extern uint32_t _estack; // 栈顶地址
void JumpToBank(uint32_t bank_addr) {
// 检查栈顶地址是否合法
if ((*(volatile uint32_t*)bank_addr & 0x2FFE0000) != 0x20000000) {
return;
}
// 设置主栈指针
__set_MSP(*(volatile uint32_t*)bank_addr);
// 获取复位向量地址
uint32_t reset_vector = *(volatile uint32_t*)(bank_addr + 4);
// 跳转
void (*reset_handler)(void) = (void (*)(void))reset_vector;
reset_handler();
}
void OTA_Update(void) {
// 假设新固件已写入非活动Bank(例如当前运行Bank0,则写入Bank1)
uint32_t target_bank = (GetCurrentBank() == BANK0_START) ? BANK1_START : BANK0_START;
// 擦除目标Bank(示例:擦除Bank1)
FLASH_EraseInitTypeDef erase_init;
erase_init.TypeErase = FLASH_TYPEERASE_SECTORS;
erase_init.Banks = FLASH_BANK_1;
erase_init.Sector = 0;
erase_init.NbSectors = 8; // 根据实际扇区数
erase_init.VoltageRange = FLASH_VOLTAGE_RANGE_3;
uint32_t sector_error = 0;
HAL_FLASHEx_Erase(&erase_init, §or_error);
// 写入固件(此处省略具体写入逻辑,通常从外部存储或网络读取)
// WriteFirmwareToBank(target_bank);
// 设置标志位,表示新固件就绪
SetUpdateFlag(target_bank);
// 软件复位
NVIC_SystemReset();
}
uint32_t GetCurrentBank(void) {
// 通过PC指针判断当前运行Bank
uint32_t pc = (uint32_t)GetPC();
if (pc >= BANK0_START && pc < BANK0_START + 0x80000) {
return BANK0_START;
} else {
return BANK1_START;
}
}
// 获取PC指针(内联汇编)
__attribute__((always_inline)) static inline uint32_t GetPC(void) {
uint32_t pc;
__asm volatile ("mov %0, pc" : "=r" (pc));
return pc;
}
```
### 3.4 启动代码修改
在系统启动时,检查更新标志,决定跳转到哪个Bank:
```c
void SystemInit(void) {
// ... 其他初始化
if (CheckUpdateFlag() == BANK1_START) {
JumpToBank(BANK1_START);
}
// 否则继续运行Bank0
}
```
## 4. 完整示例:双Bank升级流程
以下是一个简化的升级流程示例,展示如何从Bank0切换到Bank1:
```c
int main(void) {
HAL_Init();
SystemClock_Config();
// 检查是否有待升级固件
if (CheckUpdateFlag() == BANK1_START) {
// 验证固件完整性(如CRC)
if (VerifyFirmware(BANK1_START)) {
// 清除标志
ClearUpdateFlag();
// 跳转到Bank1
JumpToBank(BANK1_START);
} else {
// 固件无效,回滚到Bank0
JumpToBank(BANK0_START);
}
}
// 正常运行当前Bank应用
while (1) {
// 应用逻辑
}
}
```
## 5. 注意事项
- **地址映射**:启用双Bank后,Flash地址映射发生变化,务必确保链接脚本和代码中的地址正确。
- **中断向量表**:每个Bank的应用必须有自己的中断向量表,并在启动时重定位到对应Bank的起始地址。
- **Flash擦写时间**:虽然双Bank允许并行操作,但擦写期间CPU仍可能被阻塞(取决于Flash接口),建议使用中断或DMA优化。
- **固件验证**:切换前必须验证新固件的完整性(如CRC、签名),避免启动失败。
- **回滚机制**:设计回滚逻辑,若新固件启动失败,自动回退到旧Bank。
- **电源稳定性**:升级过程中确保电源稳定,防止Flash写入失败。
## 6. 总结
通过STM32双Bank架构,我们实现了OTA升级的零停机切换。核心在于利用Bank交错映射,使两个应用独立运行,并通过跳转实现快速切换。本文提供了原理、配置、代码和注意事项,为高可靠性嵌入式系统提供了实用方案。实际项目中,还需结合具体硬件和需求优化细节。