一、双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能力。