一、双Bank闪存与ART加速原理

STM32F4系列(如STM32F407/427)内置1MB闪存,分为两个独立的Bank(Bank0和Bank1),每个Bank大小512KB。双Bank的核心优势在于:当一个Bank正在擦除或编程时,CPU仍可从另一个Bank正常取指执行,因为两个Bank拥有独立的控制逻辑和总线接口。

ART(Adaptive Real-Time Accelerator)是ST专为闪存设计的加速器,它通过指令缓存和预取缓冲,将闪存访问延迟从几十个周期降至接近零。在双Bank模式下,ART可同时为两个Bank提供独立的缓存路径,确保切换时无性能抖动。

关键点:

  • 双Bank支持“读-写-读”并行操作,但同一Bank内不能同时读写。
  • 通过设置选项字节(Option Bytes)中的nDBANK位,可将闪存配置为单Bank或双Bank模式。
  • OTA时,固件A运行于Bank0,新固件写入Bank1,完成后切换启动地址,实现原子性更新。

二、硬件与软件准备

  • 硬件:STM32F407开发板(或其他F4系列),外部Flash可选,但本例使用内部双Bank。
  • 软件:STM32CubeIDE(或Keil),HAL库,STM32F4xx HAL驱动。
  • 注意:需确认芯片型号支持双Bank(如F407/427/437等),且Flash大小≥1MB。

三、配置步骤

1. 启用双Bank模式

双Bank模式通过修改选项字节实现。在CubeMX中,可进入Option Bytes配置,将DBANK设置为Dual Bank。若手动操作,使用以下代码:

/* 解锁Flash */
HAL_FLASH_Unlock();
/* 设置选项字节:双Bank模式 */
FLASH_OBProgramInitTypeDef ob;
HAL_FLASH_OBGetConfig(&ob);
ob.OptionType = OPTIONBYTE_BYTE;
ob.WRPState = OB_WRPSTATE_DISABLE;
ob.USERType = OB_USER_BANK;
ob.USERConfig = OB_BANK_DUAL;
HAL_FLASH_OBProgram(&ob);
/* 重新加载选项字节 */
HAL_FLASH_OBLaunch();
HAL_FLASH_Lock();

配置后需复位生效。

2. 规划内存布局

  • Bank0:起始地址0x08000000,存放当前运行固件(App0)。
  • Bank1:起始地址0x08080000,存放待升级固件(App1)。
  • 向量表偏移:App0使用默认偏移,App1需设置SCB->VTOR = 0x08080000

3. 编写并行编程驱动

核心思路:在Bank0运行时,对Bank1进行擦写。由于双Bank独立,CPU可继续执行Bank0代码,但需注意中断向量表位于Bank0,因此擦写Bank1不会影响中断响应。

四、完整代码示例

以下示例演示如何从Bank0向Bank1写入新固件,并在完成后切换启动。

#include "stm32f4xx_hal.h"

#define BANK1_BASE 0x08080000
#define APP_SIZE   0x40000  // 256KB,根据实际调整

void Flash_Write_Bank1(uint32_t *data, uint32_t size) {
    HAL_FLASH_Unlock();
    __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_ALL_ERRORS);
    
    uint32_t addr = BANK1_BASE;
    for (uint32_t i = 0; i < size; i += 8) {
        // 擦除页(每页16KB)
        if ((i % 0x4000) == 0) {
            FLASH_EraseInitTypeDef erase;
            erase.TypeErase = FLASH_TYPEERASE_PAGES;
            erase.PageAddress = addr;
            erase.NbPages = 1;
            uint32_t pageError = 0;
            HAL_FLASHEx_Erase(&erase, &pageError);
        }
        // 编程64位(双字)
        uint64_t val = ((uint64_t)data[i+1] << 32) | data[i];
        HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, addr, val);
        addr += 8;
    }
    HAL_FLASH_Lock();
}

void Jump_To_Bank1(void) {
    // 设置向量表偏移
    SCB->VTOR = BANK1_BASE;
    // 获取新固件复位向量和栈顶
    uint32_t stack_top = *(volatile uint32_t*)BANK1_BASE;
    uint32_t reset_handler = *(volatile uint32_t*)(BANK1_BASE + 4);
    
    // 关闭中断,跳转
    __disable_irq();
    typedef void (*pFunction)(void);
    pFunction jump = (pFunction)reset_handler;
    __set_MSP(stack_top);
    jump();
}

// 调用示例:从串口接收固件数据并写入
void OTA_Update(uint8_t *fw_data, uint32_t fw_size) {
    // 确保数据对齐
    Flash_Write_Bank1((uint32_t*)fw_data, fw_size);
    // 验证(可选)
    if (memcmp((void*)BANK1_BASE, fw_data, fw_size) == 0) {
        Jump_To_Bank1();
    }
}

五、注意事项

  • 擦除时间:擦除一个16KB页约需1秒,期间Bank1不可访问,但Bank0运行不受影响。
  • 中断处理:编程时若中断触发,HAL库会等待操作完成,导致短暂阻塞。建议在编程期间屏蔽可屏蔽中断,或使用DMA传输数据。
  • 电源稳定性:擦写时电流较大,确保供电稳定,否则可能导致编程失败。
  • 双Bank切换:切换后需重新配置时钟和所有外设,因为新固件会重新初始化。
  • ART缓存:切换后ART缓存可能残留旧数据,需执行SCB_InvalidateICache()SCB_InvalidateDCache()
  • 固件签名:生产环境应增加CRC或签名校验,防止损坏固件被加载。

六、总结

利用STM32F4双Bank闪存和ART加速,OTA升级不再需要停机等待。通过并行编程,系统可在运行中完成固件更新,并在瞬间切换,极大提升用户体验和系统可用性。本文提供的代码框架可直接应用于实际项目,但需根据具体硬件调整地址和大小。掌握这一技术,你的嵌入式设备将具备工业级OTA能力。