# STM32H7双Bank闪存并行编程:实现OTA期间无缝代码执行 ## 一、为什么需要双Bank闪存? 传统单Bank闪存方案中,OTA升级流程通常为:接收固件→擦除旧代码→写入新代码→跳转。擦写期间,CPU必须从闪存读取指令,若闪存被占用,代码执行将停滞,导致系统响应中断。对于电机控制、工业通信等实时性要求高的场景,这种停顿不可接受。 STM32H7系列(如STM32H743、H750)内置双Bank闪存,每个Bank容量相等(如1MB分两个512KB Bank)。硬件支持在Bank1执行代码的同时,对Bank2进行擦除/编程操作,反之亦然。这为OTA提供了天然的无缝切换基础。 ## 二、双Bank并行编程原理 ### 2.1 闪存架构 - **Bank划分**:闪存被分为两个物理Bank,地址连续。例如,STM32H743的Flash起始地址0x08000000,Bank1覆盖0x08000000-0x0807FFFF,Bank2覆盖0x08080000-0x080FFFFF。 - **独立控制**:每个Bank有独立的控制寄存器(FLASH_CR1/CR2)和状态寄存器(FLASH_SR1/SR2),可独立执行擦写操作。 - **总线访问**:CPU取指和DMA访问可同时命中不同Bank,通过AHB总线交叉矩阵实现并行访问。 ### 2.2 并行编程机制 当CPU从Bank1取指时,若对Bank2发起擦除或编程命令,Flash控制器会利用空闲的总线周期处理操作,不会阻塞Bank1的读取。关键点: - **擦除操作**:按扇区擦除(典型扇区大小128KB),擦除期间Bank2不可读,但Bank1不受影响。 - **编程操作**:以双字(256位)为单位写入,编程时间短,冲突概率低。 - **硬件仲裁**:若CPU同时访问两个Bank,硬件自动插入等待周期,但不会导致错误。 ## 三、配置步骤 ### 3.1 使能双Bank模式 默认情况下,STM32H7可能处于单Bank模式(通过选项字节配置)。需在系统初始化时检查并设置: ```c void Flash_EnableDualBank(void) { if (READ_BIT(FLASH->OPTSR_CUR, FLASH_OPTSR_DUALBANK) == 0) { // 解锁选项字节 HAL_FLASH_OB_Unlock(); // 设置双Bank模式 SET_BIT(FLASH->OPTSR_PRG, FLASH_OPTSR_DUALBANK); // 触发选项字节加载 HAL_FLASH_OB_Launch(); // 重新上电或复位后生效 NVIC_SystemReset(); } } ``` 注意:修改选项字节后需系统复位才能生效,因此建议在Bootloader中执行此操作。 ### 3.2 配置Flash控制器 使用HAL库时,需初始化Flash等待周期和预取缓冲: ```c void Flash_Init(void) { HAL_FLASH_Unlock(); // 设置等待周期,根据主频调整(例如主频480MHz时,等待周期为2) __HAL_FLASH_SET_LATENCY(FLASH_LATENCY_2); // 使能指令缓存和数据缓存 HAL_FLASH_EnableIT(FLASH_IT_ICACHE_EN); HAL_FLASH_EnableIT(FLASH_IT_DCACHE_EN); HAL_FLASH_Lock(); } ``` ### 3.3 双Bank并行编程实现 以下代码演示在Bank1执行时,对Bank2进行扇区擦除和编程: ```c // 假设当前代码在Bank1运行,目标Bank2地址0x08080000 void OTA_WriteFirmware(uint32_t destAddr, uint32_t *data, uint32_t len) { // 确保目标地址在Bank2 if (destAddr < 0x08080000 || destAddr >= 0x08100000) return; // 解锁Flash HAL_FLASH_Unlock(); // 擦除目标扇区(例如Bank2的第一个扇区) FLASH_EraseInitTypeDef erase = {0}; erase.TypeErase = FLASH_TYPEERASE_SECTORS; erase.Banks = FLASH_BANK_2; // 指定Bank2 erase.Sector = 0; // 扇区号 erase.NbSectors = 1; erase.VoltageRange = FLASH_VOLTAGE_RANGE_3; uint32_t error = 0; if (HAL_FLASHEx_Erase(&erase, &error) != HAL_OK) { // 处理错误 } // 编程数据(以双字为单位) for (uint32_t i = 0; i < len; i += 8) { uint64_t data64 = *(uint64_t*)(data + i/4); if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_QUADWORD, destAddr + i, data64) != HAL_OK) { // 处理错误 } } HAL_FLASH_Lock(); } ``` 在调用此函数时,主循环和中断照常运行,因为CPU取指在Bank1,而擦写操作在Bank2。 ## 四、完整示例:OTA无缝升级流程 假设Bootloader位于Bank1起始,App1在Bank1后半部分,App2在Bank2。升级时,Bootloader将新固件写入Bank2,完成后设置跳转标志,复位后Bootloader检测标志并跳转至Bank2。 ```c // Bootloader主逻辑 void Bootloader_Main(void) { // 检查跳转标志 if (CheckJumpFlag() == 0xAA55) { // 跳转至Bank2的App2 JumpToApp(0x08080000); } // 若收到升级命令 if (OTA_CommandReceived()) { // 擦写Bank2(此时Bootloader在Bank1运行) OTA_WriteFirmware(0x08080000, newFwData, newFwLen); // 设置跳转标志 SetJumpFlag(0xAA55); NVIC_SystemReset(); } } void JumpToApp(uint32_t appAddr) { // 设置MSP和跳转 uint32_t msp = *(volatile uint32_t*)appAddr; __set_MSP(msp); void (*appEntry)(void) = (void (*)(void))(*(volatile uint32_t*)(appAddr + 4)); appEntry(); } ``` ## 五、注意事项 - **选项字节修改**:双Bank模式必须在首次启动时配置,且修改后需复位。若在App中修改,需确保App有复位能力。 - **中断向量表**:跳转到新Bank后,需重定位中断向量表(通过SCB->VTOR设置)。 - **擦除时间**:虽然并行编程不阻塞执行,但擦除大扇区仍需时间(约几百毫秒),期间Bank2不可访问,需避免从Bank2读取数据。 - **电源稳定性**:擦写期间电流波动较大,需保证电源稳定,建议使用独立LDO或电容。 - **错误处理**:编程前检查地址对齐(双字对齐),编程后校验数据(读回比较)。 - **缓存一致性**:若使能了D-Cache,编程后需Clean&Invalidate相关地址,避免读到旧数据。 ## 六、总结 STM32H7的双Bank闪存并行编程为OTA提供了优雅的解决方案,实现了固件升级过程中的零代码停顿。通过合理配置选项字节、利用HAL库的Bank指定接口,开发者可以轻松构建无缝升级系统。本文提供的代码和步骤可直接应用于实际项目,但需根据具体型号和主频调整参数。掌握这一技术,将显著提升嵌入式产品的可靠性和用户体验。