STM32F103使用SPI Flash实现Overlay执行
本文通过一个简单的实验介绍在MCU开发过程中程序通过Overlay执行的方式。
Overlay 简介
Overlay 是一种内存管理技术,通常用于嵌入式系统和资源受限的环境中。 在这种机制下,程序的不同部分(称为“overlay segments”)被分块存储 在外部存储介质(如闪存或外部RAM)中,而不是在内存中一次性加载整个程序。 这些部分只会在需要时被加载到内存中,从而节省了宝贵的内存空间。

如图所示,RAM 除了常驻的程序和数据外,部分程序/数据被划分成多个 模块或段,这部分程序/数据被存储在外部存储介质中;当程序运行时, 只有当前需要执行的模块会从外部存储加载到RAM中,替代掉原本在内存中 占用空间的其他部分,通过动态加载和卸载不同的模块达到对RAM空间的 复用。
实现方案
主要的硬件设备
- MCU: STM32F103ZETX
- SPI Flash: W25Q128BV
基本思路
- 段
segment0和segment1从SPI Flash动态加载到片上SRAM - 系统其余部分常驻片上FLASH
实现细节
链接脚本
MEMORY区增加了RAM_OVERLAY和FLASH_OVERLAY。 RAM_OVERLAY
位于64KB片上SRAM结尾4KB的位置,用于Overlay 加载到SRAM中执行的
空间。FLASH_OVERLAY邻接512KB片上Flash,作为Overlay段加载的
位置,实验中Overlay段仅有两个,每个分配4KB,加载空间8KB:
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 60K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 512K
RAM_OVERLAY (xrw) : ORIGIN = 0x2000f000, LENGTH = 4K
FLASH_OVERLAY (r) : ORIGIN = 0x8080000, LENGTH = 8K
}
使用EXCLUDE_FILE,将segment0和segment1目标文件
中的.text*和.rodata*段从.text和.rodata段排除:
/* The program code and other data goes into FLASH */
.text :
{
. = ALIGN(4);
*(.text) /* .text sections (code) */
*(EXCLUDE_FILE(*segment*.o) .text*)
/* other sections place in .text*/
} >FLASH
/* Constant data goes into FLASH */
.rodata :
{
. = ALIGN(4);
*(.rodata) /* .rodata sections (constants, strings, etc.) */
*(EXCLUDE_FILE(*segment*.o) .rodata*)
. = ALIGN(4);
} >FLASH
使用OVERLAY命令,此处增加两个段segment0和segment1,
各自对应文件的.text*和.rodata*段从FLASH_OVERLAY加载
到RAM_OVERLAY执行:
OVERLAY : NOCROSSREFS
{
segment0{ *segment0.o(.text* .rodata*) }
segment1{ *segment1.o(.text* .rodata*) }
} >RAM_OVERLAY AT> FLASH_OVERLAY
使用NOCROSSREFS标志,segment0和segment1段中的符号不能
互相引用;链接器会为Overlay的段自动生成__load_start_和__load_stop_ +
段名格式的标签,例如,对于段segment0,链接器自动生成如下
标签:
__load_start_segment0: 段segment0起始加载位置__load_stop_segment0: 段segment0结束加载位置
segment*.c
在segment0.c和segment1.c中分别定义了func0和func1:
const uint8_t foo1[4] = {0, 1, 2, 3};
uint8_t foo2[4] = {4, 5, 6, 7};
uint8_t foo3[8];
void func0()
{
printf("executing func0...\r\n");
uint8_t sum = 0;
for(uint32_t i = 0; i < 4; i++){
sum += foo1[i];
}
printf("%d\r\n", sum);
sum = 0;
for(uint32_t i = 0; i < 4; i++){
sum += foo2[i];
}
printf("%d\r\n", sum);
sum = 0;
for(uint32_t i = 0; i < 8; i++){
foo3[i] = i;
}
for(uint32_t i = 0; i < 8; i++){
sum += foo3[i];
}
printf("%d\r\n", sum);
}
void func1()
{
printf("executing func1...\r\n");
}
最终执行时,func0和func1分别位于段segment0和segment1,
由于在链接脚本中将两个文件.rodata*段提取到Overlay段中,
foo1数组在执行时位于段segment0;由于未提取两个文件的
.data*和.bss*段,foo2和foo3执行时位于SRAM前60KB所在
区域。
main.c
从SPI Flash分别加载segment0和segment1并执行func0和func1:
#define OVERLAY_AREA_ADDR 0x2000f000U
#define SEGMENT_LENGTH 4096U
uint8_t valify_segment()
{
uint32_t cs = HAL_CRC_Calculate(&hcrc,
(uint32_t *)OVERLAY_AREA_ADDR,
(SEGMENT_LENGTH - 4) / 4);
if(cs == *(uint32_t *)(OVERLAY_AREA_ADDR + SEGMENT_LENGTH - 4)){
return 1;
}
return 0;
}
void load_segment(uint32_t index)
{
spi_flash_read_buffer((uint8_t *)OVERLAY_AREA_ADDR,
(uint32_t)SEGMENT_LENGTH * index,
SEGMENT_LENGTH);
if(!valify_segment()){
Error_Handler();
}
}
int main()
{
// do some initializing...
load_segment(0);
func0();
load_segment(1);
func1();
while(1);
}
验证
构建之后,查看生成的.bin文件:
ls -lh build/overlay.bin
比512KB稍大:
-rwxr-xr-x 1 jiawei jiawei 524508 Oct 23 09:27 build/overlay.bin
使用xxd查看文件内容,512KB偏移位置后的内容如下,即需要加载
到Overlay段的数据:
0007ffe0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0007fff0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00080000: 6578 6563 7574 696e 6720 6675 6e63 302e executing func0.
00080010: 2e2e 0d00 2564 0d0a 0000 0000 08b5 1948 ....%d.........H
00080020: 00f0 3af8 0023 1946 04e0 174a d25c 1144 ..:..#.F...J.\.D
00080030: c9b2 0133 032b f8d9 1448 00f0 31f8 0023 ...3.+...H..1..#
00080040: 1946 04e0 124a d25c 1144 c9b2 0133 032b .F...J.\.D...3.+
00080050: f8d9 0e48 00f0 24f8 0023 02e0 0d4a d354 ...H..$..#...J.T
00080060: 0133 072b fad9 0023 1946 04e0 094a d25c .3.+...#.F...J.\
00080070: 1144 c9b2 0133 072b f8d9 0448 00f0 10f8 .D...3.+...H....
00080080: 08bd 00bf 00f0 0020 a8f0 0020 14f0 0020 ....... ... ...
00080090: 0c00 0020 3801 0020 5ff8 00f0 bd1a 0008 ... 8.. _.......
000800a0: 5ff8 00f0 ed19 0008 0001 0203 6578 6563 _...........exec
000800b0: 7574 696e 6720 6675 6e63 312e 2e2e 0d00 uting func1.....
000800c0: 08b5 0248 00f0 06f8 08bd 00bf 00f0 0020 ...H...........
000800d0: 0000 0000 5ff8 00f0 bd1a 0008 ...._.......
查看生成的段:
arm-none-eabi-objdump -h build/overlay.elf
可以看到段segment0和segment1大小分别为0xac和0x30,
即十进制的172和48:
11 segment0 000000ac 2000f000 08080000 00005000 2**3
CONTENTS, ALLOC, LOAD, READONLY, CODE
12 segment1 00000030 2000f000 080800ac 00006000 2**3
CONTENTS, ALLOC, LOAD, READONLY, CODE
所以.bin文件从0x80000开始的172字节属于段segment0,
从0x800ac开始的48字节属于段segment1。烧写程序时,
需要将.bin文件裁剪为三部分,开头的512KB烧写到片上Flash,
Overlay段的数据,根据该实验,需填充到4KB并加上CSC,烧写
到SPI Flash。