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的过程。

#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, &sectorError) != 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系统。