STM32H7 双Bank闪存并行编程:利用ART加速器实现零等待OTA固件切换

引言:OTA升级的痛点与H7的破局

传统嵌入式OTA升级常面临两大痛点:升级期间系统必须停机(单Bank方案),或升级后需重启切换(A/B方案虽可避免停机但切换延迟明显)。STM32H7系列通过双Bank闪存架构与ART(Adaptive Real-Time)加速器的协同设计,实现了真正的零等待固件切换:后台Bank可并行写入新固件,而前台Bank继续执行旧代码,切换时仅需修改标志位并复位,ART缓存自动失效,新固件即刻运行,无任何性能回退。

原理剖析:双Bank与ART的协同机制

双Bank闪存架构

STM32H7的闪存分为两个独立的Bank(Bank1和Bank2),每个Bank容量可达1MB(视型号而定)。关键特性:

  • 独立擦写:两个Bank可同时进行读写操作,但同一时刻只能有一个Bank被编程/擦除,另一个可被读取执行。
  • 无缝切换:通过设置选项字节或系统控制寄存器中的SWAP位,可将物理Bank映射到固定地址空间(0x08000000),实现固件切换。
  • 并行编程:利用双Bank的独立性,可在Bank2后台写入新固件,同时Bank1继续运行当前程序。

ART加速器与缓存一致性

ART加速器是H7的指令缓存和预取单元,它缓存闪存中的指令,减少CPU等待周期。当发生Bank切换时,ART缓存内容可能来自旧Bank,导致执行错误。因此,切换前必须失效ART缓存(通过SCB_InvalidateICache或ART外设的CACHE_CR寄存器)。H7的ART支持按Bank独立缓存,但切换后物理地址映射变化,统一失效是最安全策略。

配置步骤:从零到零等待切换

1. 启用双Bank模式

默认情况下,H7可能处于单Bank模式。需在编程选项字节中启用双Bank(若芯片支持)。在代码中可通过读取FLASH_OPTR寄存器确认。若需切换,使用HAL_FLASH_OB_Program配置,但通常出厂已启用,此处略过。

2. 配置Flash控制器

/* 解锁Flash */
HAL_FLASH_Unlock();

/* 设置编程延迟(根据主频调整) */
FLASH_WaitForLastOperation(1000);
__HAL_FLASH_SET_LATENCY(FLASH_LATENCY_4);  // 主频400MHz时需4个等待周期

/* 启用ART加速器(若未启用) */
__HAL_FLASH_ART_ENABLE();

3. 后台Bank编程(以Bank2为例)

#define BANK2_BASE_ADDR  0x08100000  // 假设Bank2起始地址

void Flash_WriteBank2(uint32_t offset, uint32_t *data, uint32_t len) {
    uint32_t addr = BANK2_BASE_ADDR + offset;
    
    /* 擦除Bank2的扇区(按需擦除) */
    FLASH_EraseInitTypeDef erase = {0};
    erase.TypeErase = FLASH_TYPEERASE_SECTORS;
    erase.Banks = FLASH_BANK_2;
    erase.Sector = 0;
    erase.NbSectors = 8;  // 根据实际扇区数
    erase.VoltageRange = FLASH_VOLTAGE_RANGE_3;
    uint32_t error = 0;
    HAL_FLASHEx_Erase(&erase, &error);
    
    /* 编程数据 */
    for (uint32_t i = 0; i < len; i += 4) {
        HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, addr + i, data[i/4]);
        while (FLASH_WaitForLastOperation(100) != HAL_OK);
    }
}

注意:H7的编程以256位(32字节)为最小单位,数据需对齐。

4. 执行固件切换

void SwitchFirmware(void) {
    /* 设置切换标志(例如在备份寄存器中) */
    HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR0, 0xA5A5);
    
    /* 失效ART缓存和指令缓存 */
    SCB_InvalidateICache();
    SCB_InvalidateDCache();
    __HAL_FLASH_ART_CACHE_RESET();
    
    /* 触发系统复位 */
    NVIC_SystemReset();
}

/* 在启动代码或main早期检查标志 */
void CheckAndSwapBank(void) {
    if (HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0) == 0xA5A5) {
        /* 清除标志 */
        HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR0, 0);
        
        /* 切换Bank映射 */
        FLASH_BankSwap_Enable();  // 自定义函数,设置SWAP位
        
        /* 再次失效缓存,确保新代码加载 */
        SCB_InvalidateICache();
        __HAL_FLASH_ART_CACHE_RESET();
    }
}

5. 完整示例:后台下载与切换

int main(void) {
    HAL_Init();
    SystemClock_Config();
    
    /* 检查是否需要切换Bank */
    CheckAndSwapBank();
    
    /* 正常应用代码 */
    while (1) {
        // 运行当前固件
        
        if (OTA_RequestReceived()) {
            // 下载新固件到Bank2(通过UART/以太网)
            uint8_t buffer[1024];
            uint32_t offset = 0;
            while (OTA_ReceiveData(buffer, sizeof(buffer))) {
                Flash_WriteBank2(offset, (uint32_t*)buffer, sizeof(buffer));
                offset += sizeof(buffer);
            }
            
            // 校验固件(CRC等)
            if (VerifyFirmware(BANK2_BASE_ADDR)) {
                SwitchFirmware();  // 切换并重启
            }
        }
    }
}

注意事项与最佳实践

  • 缓存一致性:切换后必须失效ART和CPU缓存,否则执行旧指令。建议在复位后立即执行失效操作。
  • 中断处理:在后台编程期间,避免中断访问Flash(如中断向量表在Bank1),否则可能因Flash忙导致延迟。可考虑将中断向量表重映射到RAM。
  • 电源稳定性:Flash编程需要较高电压,确保供电稳定,否则可能导致编程失败。
  • 固件校验:切换前务必校验新固件完整性(CRC或签名),防止变砖。
  • ART性能:ART加速器对顺序执行代码效果显著,但跳转密集代码可能命中率低。合理布局代码可提升性能。

总结

STM32H7的双Bank闪存与ART加速器组合,为OTA升级提供了工业级解决方案。通过后台编程和快速切换,系统可实现近乎零停机的固件更新,且ART缓存管理确保了切换后性能无损。掌握这一技术,你的产品将具备更强大的远程维护能力。