嵌入式开发里有个长期存在的限制:CPU 从 Flash 取指,而 Flash 的擦除和编程会占用存储阵列。所以一旦你开始写 Flash,程序就没法继续跑——不是变慢,是停下来。
前段时间 ST 推出了 STM32C5 系列。144 MHz 的 Cortex-M33,最大 1024 KB Flash、256 KB SRAM,定位是入门到中端这一档仔,细翻手册,注意到里面有一个双 Bank 功能。
(Bank在自动翻译被翻译成了银行)它列出双 Bank 结构支持三件事:
一是同时操作——读-写并行是支持的,擦除和编程都算在内。
二是两个 Bank 共享同一个接口。这句话看着像补充说明,其实是个限定:读和写能并行,写和写不能。两个 Bank 的编程和擦除走同一套控制接口,没法真正同时做。
三是 Bank 交换——两个 Bank 的用户 Flash 地址映射可以对调,相应的寄存器也一起交换。后面还跟了一句安全说明:安全标志对物理 Bank 保持有效,所以数据不会因为交换到安全配置更低的 Bank 而泄露。
本期我们介绍一下STM32的双 Bank 功能作用以及如何使用。
1、双BANK并不是直接分两半
以 STM32C562RET6 为例。它的用户 Flash 是 512 KB,在 stm32c562xx.h 里定义得很清楚:
#define FLASH_SIZE_MAX (0x00080000UL) /* 512 Kbytes user flash */#define FLASH_BANK_SIZE (FLASH_SIZE >> 1U) /* 256 Kbytes per bank */#define FLASH_PAGE_SIZE 0x2000U /* 8 Kbytes pages */#define FLASH_BANK_NB (2U)#define FLASH_PAGE_NB (FLASH_BANK_SIZE/FLASH_PAGE_SIZE)
512 KB 平分,每个 Bank 256 KB,页大小 8 KB,每 Bank 32 页。Bank 1 从 0x08000000 开始,Bank 2 从 0x08040000 开始。
关键在于这两个 Bank 有独立的存储阵列。Bank 2 在擦除时占用的是它自己的阵列,Bank 1 的读通路不受影响,CPU 继续从 Bank 1 取指,中断照常进。这就是手册里说的 read-while-write。
这里有个容易误解的地方,手册讲得很明确:两个 Bank 共享同一个接口。所以读和写可以并行,写和写不行——两个 Bank 的编程和擦除走的是同一套控制接口,没法真正同时做。不能指望两个 Bank 能同时进入编程状态做并行写入。
除了 RWW,双 Bank 还提供 Bank 交换:两个 Bank 的地址映射可以对调,让原本从 0x08000000 取指的代码实际执行物理 Bank 2 的内容。这是硬件级的地址重定向,重启后切换固件分区靠的就是它。地址映射对调时,相关的配置寄存器也跟着一起交换。
从HAL的接口能看出这一层。stm32c5xx_hal_flash_itf.c里同时提供了一组功能:
HAL_FLASH_ITF_OB_SetBankTopology() /* 设置单/双 Bank */HAL_FLASH_ITF_OB_GetBankTopology() /* 读取当前配置 */HAL_FLASH_ITF_OB_SetBankSwap() /* 设置 Bank 交换 */HAL_FLASH_ITF_OB_IsBankSwapped() /* 查询是否已交换 */
SetBankTopology 的说明里有一句限定:"for products with less user memory"。也就是说,对于容量较小的型号,Flash 可以在单 Bank 和双 Bank 之间通过选项字节切换。一旦配成单 Bank,整个 Flash 视作一个连续阵列,RWW 就不存在了,你写 Flash 的时候 CPU 一样要停。
SetBankSwap 则是另一个方向的用法。它可以让 Bank 的物理地址映射对调,配合双 Bank 就能做"在 A 区跑、把新固件写进 B 区,重启后切换"的升级流程。双 Bank 的价值不止 RWW 一项,它同时是可靠 OTA 的基础。
2、RRW测试
接着博主在 NUCLEO-C562RE 上跑了一个简单的RRW测试:用 HAL_FLASH_EraseByAddr() 擦掉整个 Bank 2,然后切到 Quad-Word 模式写入几个测试字,再回读校验。
代码和中断向量全部链接在 Bank 1:
ROM (rx) : org = 0x8000000, len = 0x40000
擦除范围从 0x08040000 开始,长度 256 KB。这条边界和代码所在的区间完全不重叠,所以擦除期间取指不发生冲突。SysTick 持续的计数。SysTick 每 1 ms 进一次回调,主程序在擦除前记下计数值,擦除返回后比对:
rww_irq_count = 0;irq_count_before = rww_irq_count;rww_phase = RWW_PHASE_ERASING;if (HAL_FLASH_EraseByAddr(hflash, FLASH_BANK2_BASE_ADDR,FLASH_BANK2_SIZE, FLASH_TIMEOUT_MS) != HAL_OK){rww_phase = RWW_PHASE_FAIL;}elseif (rww_irq_count == irq_count_before){rww_phase = RWW_PHASE_FAIL;}
一次 256 KB 的擦除是阻塞式调用,前台会一直卡在里面。如果这段期间中断计数纹丝不动,说明 CPU 根本没在执行别的代码,RWW 就没实现;计数增长,才证明中断确实从 Bank 1 正常响应了。
这个判据的好处是它会失败。把它放到单 Bank 配置下,或者把擦除地址改成 Bank 1 自己所在的区间,rww_irq_count 就不会变,程序会直接判 FAIL。
同时,使用一个定期来改变,LED 的闪烁频率。擦除和编程期间 PA5 每 25 ms 翻转一次,心跳很快;成功之后放慢到 500 ms;失败则用 100 ms 的快闪区分。人眼看到擦除过程中灯还在快速闪,用来判断 RWW 是否在工作。
3、注意事项
中断向量和中断服务程序的位置是硬约束。 RWW 生效的前提是执行代码不在被操作的 Bank 里。链接脚本里把 .isr_vector、.text 放在 0x08000000 起的 256 KB 内,是这套方案成立的一部分,不是可选的优化。如果你后续改动了内存布局,把一部分代码放到了 Bank 2,那部分代码在擦 Bank 2 期间就不可用。
擦除耗时决定了你的实时性预算。 RWW 解决的是"能不能继续运行",不是"擦除变快了"。前台仍然被阻塞在擦除调用里,只是中断能进来。如果应用里有硬实时的中断任务,还得算清楚最坏情况下的中断延迟是否可接受——可以进一步考虑用 HAL_FLASH_EraseByAddr_IT() 这样的中断版本,把阻塞也去掉。
结语
有的方案靠 RAM 执行加一次短停摆凑合,有的干脆上双机热备,双 Bank 的意义可以概括成一句话:它把"更新 Flash"从一件必须停机的事,变成了一件可以和主程序并行的事。对需要在现场升级固件、又不能让设备停机的产品,这一条就够决定选型了。
292