3、内存布局与分区:Flash内存分区设计、Bootloader与应用程序的地址映射、向量表重定向
好,咱们今天聊点硬核的。内存布局,说白了就是给芯片的Flash空间「分房子」。你想想看,一个房子里住着Bootloader、应用程序,有时候还有参数存储区,大家得各住各的,不能串门,更不能打架。我刚开始做Bootloader那会儿,就吃过地址重叠的亏,程序跑着跑着就飞了,查了三天才发现是分区没划清楚。
3.1 Flash内存分区设计
Flash分区,核心就一句话:物理上连续,逻辑上隔离。每个分区要有明确的起始地址和大小,谁也别越界。
我一般把Flash分成这么几块:
- Bootloader区:存放启动代码,通常64KB~128KB,看芯片。
- 应用程序区:主程序,剩下的空间基本都给它。
- 参数存储区:存放标定数据、配置字、故障码等。
- 备份区(可选):做OTA时放新固件,防止刷死。
举个例子,假设芯片Flash总大小是1MB(0x100000),起始地址0x08000000。我习惯这么分:
| 分区名称 | 起始地址 | 大小 | 说明 |
|---|---|---|---|
| Bootloader | 0x08000000 | 64KB (0x10000) | 启动代码,向量表 |
| 应用程序 | 0x08010000 | 896KB (0xE0000) | 主程序 |
| 参数区 | 0x080F0000 | 64KB (0x10000) | 标定数据,掉电保存 |
3.2 Bootloader与应用程序的地址映射
地址映射,说白了就是告诉链接器:Bootloader该放哪儿,应用程序该放哪儿。这得靠链接脚本(Linker Script)来搞定。
我拿STM32的GCC链接脚本举个例子。Bootloader的链接脚本里,Flash起始地址就是0x08000000:
/* Bootloader 链接脚本片段 */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 64K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K
}
应用程序的链接脚本呢?起始地址就得往后挪,挪到0x08010000:
/* 应用程序链接脚本片段 */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08010000, LENGTH = 896K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K
}
你看,两个程序各自认领自己的地盘。Bootloader编译出来,烧到0x08000000;应用程序编译出来,烧到0x08010000。谁也不碍谁的事。
3.3 向量表重定向
嗯,这里要注意。芯片上电后,CPU默认从0x08000000取向量表。如果Bootloader跳转到应用程序,但向量表还在原地,那中断一来就乱套了。所以必须做向量表重定向。
说白了,就是告诉CPU:喂,应用程序的向量表在0x08010000,别找错了。
以ARM Cortex-M为例,有个寄存器叫SCB->VTOR(Vector Table Offset Register)。Bootloader跳转前,得把这个寄存器改成应用程序的起始地址:
/* 向量表重定向代码 */
#define APP_VECTOR_TABLE ((uint32_t)0x08010000)
void JumpToApp(void)
{
uint32_t appStack = *(volatile uint32_t*)APP_VECTOR_TABLE;
uint32_t appReset = *(volatile uint32_t*)(APP_VECTOR_TABLE + 4);
/* 关闭全局中断,防止跳转过程中断捣乱 */
__disable_irq();
/* 设置主栈指针 */
__set_MSP(appStack);
/* 重定向向量表 */
SCB->VTOR = APP_VECTOR_TABLE;
/* 跳转到应用程序的复位向量 */
void (*appEntry)(void) = (void (*)(void))appReset;
appEntry();
}
这段代码我用了好多年,基本没出过问题。但有个坑:跳转前一定要关中断。我遇到过一回,跳转时一个定时器中断正好触发,结果向量表还没切过去,直接跑飞了。
- 向量表重定向必须在跳转前完成。
- 跳转前关闭所有外设中断,防止意外触发。
- 应用程序自己的启动代码里,也要重新设置VTOR,防止二次跳转出错。
3.4 实战中的避坑指南
做内存分区,我踩过的坑不少,挑几个典型的说说:
- 分区对齐问题:Flash擦除块大小不同,比如有些芯片前几个Sector是16KB,后面是64KB。分区边界必须对齐到Sector边界,否则擦除时会误伤邻居。
- 参数区放最后:我习惯把参数区放在Flash末尾,这样调整应用程序大小时,参数区地址不用变,省心。
- 备份区别省:做OTA升级时,没有备份区就等于裸奔。刷到一半断电,你就等着变砖吧。
3.5 内存布局示意图
光说不够直观,我画张图给你看。这张图展示了典型的Flash分区和地址映射关系:
这张图里,Bootloader在最前面,应用程序紧随其后,参数区收尾。应用程序的向量表在0x08010000,跳转前必须把VTOR指向这里。
好了,内存布局这块就聊到这儿。分区设计、地址映射、向量表重定向,这三板斧抡好了,Bootloader的底子就算打牢了。下次咱们聊聊刷写流程,那才是真正见真章的地方。