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模式(需在系统复位后生效)。
零停机升级流程
- 准备阶段:系统运行在Bank A(地址0x08000000),固件版本V1.0。
- 下载新固件:通过通信接口(如UART、以太网)接收新固件V2.0,写入Bank B(地址0x08040000)。写入期间,Bank A继续运行,无中断。
- 校验:对Bank B的固件进行CRC或哈希校验,确保完整性。
-
切换映射:设置
SWAP_BANK=1,硬件立即交换Bank映射,CPU从0x08000000取指时实际访问Bank B。 - 跳转:软件复位(NVIC_SystemReset)后,CPU从0x08000000重新启动,执行新固件。由于映射已交换,新固件直接运行,无需Bootloader干预。
- 回滚(可选):若新固件异常,可再次切换映射恢复旧固件。
配置步骤(以STM32F746为例)
1. 启用双Bank模式
在系统初始化时,通过修改选项字节启用双Bank(需在复位后生效):
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基址:
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位:
void swap_bank(void) {
FLASH_CR->SWAP_BANK = 1;
while (FLASH_SR->BSY); // 等待操作完成
}
注意:切换后,当前执行的代码仍在原Bank中,但地址映射已变。此时若继续执行原Bank代码,会导致取指错误,因此必须立即复位。
4. 复位并运行新固件
void jump_to_new_firmware(void) {
swap_bank();
NVIC_SystemReset(); // 复位后从0x08000000执行,实际访问Bank B
}
完整代码示例
以下是一个简化的OTA升级核心函数:
#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系列项目,但需根据具体型号调整寄存器定义。掌握此技术,可显著提升设备的可用性和升级体验。