STM32F4双Bank闪存并行编程:利用ART加速实现OTA零等待切换
👁 1 阅读 · 2026-08-27 · 嵌入式
在嵌入式OTA升级中,传统方案常因擦写期间CPU停顿导致系统卡顿或实时性下降。STM32F4系列凭借双Bank闪存架构与ART(自适应实时加速器),可实现一边运行一边编程的并行操作,配合Bank切换实现零等待固件更新。本文深入解析双Bank原理、ART加速机制,并给出基于HAL库的完整配置与代码示例,助你构建高可靠OTA方案。
# 一、双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`。若手动操作,使用以下代码:
```c
/* 解锁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写入新固件,并在完成后切换启动。
```c
#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能力。