STM32F103使用SPI Flash实现Overlay执行

目录

本文通过一个简单的实验介绍在MCU开发过程中程序通过Overlay执行的方式。

Overlay 简介

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

overlay

如图所示,RAM 除了常驻的程序和数据外,部分程序/数据被划分成多个 模块或段,这部分程序/数据被存储在外部存储介质中;当程序运行时, 只有当前需要执行的模块会从外部存储加载到RAM中,替代掉原本在内存中 占用空间的其他部分,通过动态加载和卸载不同的模块达到对RAM空间的 复用。

实现方案

主要的硬件设备

  • MCU: STM32F103ZETX
  • SPI Flash: W25Q128BV

基本思路

  • segment0segment1从SPI Flash动态加载到片上SRAM
  • 系统其余部分常驻片上FLASH

实现细节

链接脚本

MEMORY区增加了RAM_OVERLAYFLASH_OVERLAYRAM_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,将segment0segment1目标文件 中的.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命令,此处增加两个段segment0segment1, 各自对应文件的.text*.rodata*段从FLASH_OVERLAY加载 到RAM_OVERLAY执行:

  OVERLAY : NOCROSSREFS
  {
    segment0{ *segment0.o(.text* .rodata*) }
    segment1{ *segment1.o(.text* .rodata*) }
  } >RAM_OVERLAY AT> FLASH_OVERLAY

使用NOCROSSREFS标志,segment0segment1段中的符号不能 互相引用;链接器会为Overlay的段自动生成__load_start___load_stop_ + 段名格式的标签,例如,对于段segment0,链接器自动生成如下 标签:

  • __load_start_segment0: 段segment0起始加载位置
  • __load_stop_segment0: 段segment0结束加载位置

segment*.c

segment0.csegment1.c中分别定义了func0func1

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");
}

最终执行时,func0func1分别位于段segment0segment1, 由于在链接脚本中将两个文件.rodata*段提取到Overlay段中, foo1数组在执行时位于段segment0;由于未提取两个文件的 .data*.bss*段,foo2foo3执行时位于SRAM前60KB所在 区域。

main.c

从SPI Flash分别加载segment0segment1并执行func0func1:

#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

可以看到段segment0segment1大小分别为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。

参考

  1. wikipedia: Overlay (programming)
  2. GNU ld: Overlay Description
  3. NXP Semiconductors: AN14260 Dynamic Loading of Code by Overlay