2、硬件基础:MCU Flash控制器原理、Bank切换的硬件信号、地址映射机制

好,咱们进入正题。这一章讲的是硬件基础,说白了就是Bootloader双Bank切换的“地基”。你如果不懂Flash控制器怎么工作,不知道Bank切换时硬件信号怎么拉,也不清楚地址映射的规则,那后面写代码就是空中楼阁。

我个人习惯,讲硬件之前先问自己三个问题:

  • Flash怎么读写的?
  • Bank切换时,芯片内部发生了什么?
  • CPU看到的地址空间,到底是怎么映射到物理Flash上的?

搞懂这三个,这章就过了。

2.1 MCU Flash控制器原理

Flash控制器,你可以把它想象成一个“管家”。CPU说“我要读地址0x08000000的数据”,管家就去Flash里把数据取出来。但这里有个坑——Flash读速度比CPU慢得多。

我记得第一次调STM32的Flash预取缓存时,发现程序跑飞了。后来查手册才明白,Flash控制器内部有预取缓冲区和指令缓存。你如果不配置好等待周期(Wait States),CPU读到的就是乱码。

核心要点:

  • 读操作:CPU通过AHB总线发地址,Flash控制器解析地址,从Flash阵列中读出数据,再通过总线返回。这个过程需要插入等待周期。
  • 写操作:Flash写之前必须先擦除。擦除是按扇区(Sector)或页(Page)进行的。写的时候,控制器会把数据锁存到写缓冲区,然后启动编程流程。
  • 预取缓存:Flash控制器会预判CPU的下一条指令,提前把数据读到缓存里。这样CPU下次读的时候,直接从缓存拿,不用等Flash。

你想想看,如果预取缓存没开,或者等待周期设错了,CPU每读一条指令都要等几十个时钟周期。那性能直接崩了。

我的经验: 在初始化代码里,一定要先配置Flash的等待周期和预取使能。我见过有人把这两步放在后面,结果程序在Flash配置之前就跑飞了。顺序很重要:先配时钟,再配Flash,最后才初始化外设。

2.2 Bank切换的硬件信号

双Bank切换,不是软件里改个变量那么简单。它背后有实实在在的硬件信号在控制。

以STM32G0/G4系列为例,Bank切换的核心信号是BOOT_SELBANK_SEL。这两个信号决定了CPU复位后从哪个Bank启动。

信号 作用 说明
BOOT_SEL 选择启动模式 0:从主Flash启动;1:从系统存储器或SRAM启动
BANK_SEL 选择Bank 0:Bank1;1:Bank2
SWAP 交换Bank映射 通过Flash控制器的寄存器位控制,将Bank1和Bank2的地址空间互换

嗯,这里要注意。SWAP信号是通过软件写的寄存器来触发的。但硬件上,这个信号会直接改变地址译码器的映射关系。也就是说,你写一个位,整个地址空间就变了。

避坑指南: 我曾经在切换Bank时,忘了检查Flash控制器是否处于忙状态。结果SWAP位写进去了,但Flash还在擦除,直接导致系统死锁。正确的做法是:先查FLASH_SR寄存器的BSY位,确认空闲了,再写SWAP位。写完之后,最好再读回来确认一下。

为什么会这样?因为SWAP操作本质上是硬件重新配置地址译码器。如果此时Flash还在做擦写操作,译码器一换,擦写目标地址就乱了。硬件设计上,有些MCU会直接忽略SWAP请求,有些则会报错。但不管哪种,你的代码都得防一手。

2.3 地址映射机制

地址映射,说白了就是CPU看到的地址,和物理Flash地址之间的对应关系。

以STM32G0B1为例,它的Flash总大小是512KB,分成两个Bank,每个Bank 256KB。默认情况下:

  • Bank1映射到地址0x08000000 ~ 0x0803FFFF
  • Bank2映射到地址0x08040000 ~ 0x0807FFFF

但当你使能了SWAP之后,映射关系就变了:

  • Bank2映射到地址0x08000000 ~ 0x0803FFFF
  • Bank1映射到地址0x08040000 ~ 0x0807FFFF

你想想看,这意味着什么?意味着CPU永远从0x08000000开始执行代码。但0x08000000指向的物理Bank,可以通过SWAP来切换。这就是双Bank OTA的核心原理。

关键点:

  • 复位向量表固定在0x08000000(或0x00000000,取决于启动配置)。
  • 通过SWAP,我们可以让Bank1或Bank2“占据”这个地址。
  • 切换后,CPU无需修改任何代码,直接跑新Bank里的程序。

我记得有一次调试,发现OTA升级后程序跑不起来。查了半天,原来是中断向量表没重映射。因为SWAP之后,Bank2里的程序虽然跑在0x08000000,但它的中断向量表还是指向Bank1的地址。解决办法是在程序启动时,重新设置SCB->VTOR寄存器,让它指向当前Bank的起始地址。

我的建议: 在Bootloader里,每次跳转到App之前,都强制设置一下VTOR。不要依赖默认值。因为SWAP之后,默认值可能就不对了。代码示例如下:

// 假设当前运行在Bank1,App在Bank2
// 先执行SWAP,让Bank2映射到0x08000000
FLASH->ACR |= FLASH_ACR_SWAP_BANK;

// 等待SWAP完成
while (FLASH->SR & FLASH_SR_BSY);

// 设置中断向量表偏移
SCB->VTOR = 0x08000000;

// 跳转到App入口
JumpToApp(0x08000000);

说白了,地址映射就是一张“地图”。你只要搞清楚这张地图怎么画,以及怎么切换地图,双Bank切换就没什么难的。

2.4 小结

这一章我们聊了三个东西:

  1. Flash控制器:它负责读写Flash,需要配置等待周期和预取缓存。
  2. Bank切换的硬件信号:BOOT_SEL、BANK_SEL、SWAP,这些信号直接控制地址译码器。
  3. 地址映射机制:SWAP改变了物理Bank和逻辑地址的对应关系,但CPU永远从0x08000000启动。

下一章,我们会基于这些硬件知识,开始写真正的Bootloader代码。到时候你会发现,理解了硬件,写软件就是水到渠成的事。