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_CRSWAP_BANK位(bit 31)控制映射交换。
  • FLASH_SRBSY位表示忙,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(需在复位后生效):

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系列项目,但需根据具体型号调整寄存器定义。掌握此技术,可显著提升设备的可用性和升级体验。