2. 实现Bootloader与应用程序的内存布局划分

任务目标

在嵌入式系统中,Bootloader与应用程序共存于同一颗Flash芯片。若两者地址重叠,应用程序升级时可能覆盖Bootloader,导致设备变砖。本案例通过GNU ld链接脚本,将Bootloader固定在Flash起始区域(0x08000000),应用程序紧随其后(0x08008000),并分别配置独立的向量表与RAM区间,实现物理隔离。

环境与依赖

  • 工具链:arm-none-eabi-gcc(含ld)
  • 目标芯片:STM32F103系列(Flash 512KB,RAM 64KB,起始地址0x20000000)
  • 工程文件:bootloader.ld、app.ld、common.ld(公共定义)

关键完整代码

以下为最小可复现的链接脚本与启动验证代码。三个文件需放置于同一目录。

// common.ld —— 公共芯片定义
MEMORY
{
    FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 512K
    RAM   (xrw) : ORIGIN = 0x20000000, LENGTH = 64K
}

// bootloader.ld —— Bootloader区域划分
INCLUDE common.ld

/* Bootloader占用Flash前32KB */
MEMORY
{
    BOOT_FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 32K
    APP_FLASH  (rx) : ORIGIN = 0x08008000, LENGTH = 480K
}

REGION_ALIAS("TEXT_FLASH", BOOT_FLASH);
REGION_ALIAS("DATA_RAM", RAM);

INCLUDE stm32f1_layout.inc

// app.ld —— 应用程序区域划分
INCLUDE common.ld

MEMORY
{
    APP_FLASH (rx) : ORIGIN = 0x08008000, LENGTH = 480K
}

REGION_ALIAS("TEXT_FLASH", APP_FLASH);
REGION_ALIAS("DATA_RAM", RAM);

INCLUDE stm32f1_layout.inc

// stm32f1_layout.inc —— 段布局模板(两个脚本共用)
SECTIONS
{
    .isr_vector :
    {
        . = ALIGN(4);
        KEEP(*(.isr_vector))
        . = ALIGN(4);
    } > TEXT_FLASH

    .text :
    {
        . = ALIGN(4);
        *(.text*)
        *(.rodata*)
        . = ALIGN(4);
        _etext = .;
    } > TEXT_FLASH

    .data :
    {
        . = ALIGN(4);
        _sdata = .;
        *(.data*)
        . = ALIGN(4);
        _edata = .;
    } > DATA_RAM AT> TEXT_FLASH

    .bss :
    {
        . = ALIGN(4);
        _sbss = .;
        *(.bss*)
        *(COMMON)
        . = ALIGN(4);
        _ebss = .;
    } > DATA_RAM

    _estack = ORIGIN(DATA_RAM) + LENGTH(DATA_RAM);
}

编译时分别指定链接脚本:

# Bootloader编译
arm-none-eabi-gcc -T bootloader.ld -nostdlib -Wl,--no-warn-rwx-segments \
    boot_startup.s main.c -o boot.elf

# 应用程序编译(需在代码中定义APP_BASE=0x08008000)
arm-none-eabi-gcc -T app.ld -nostdlib -Wl,--no-warn-rwx-segments \
    -DAPP_BASE=0x08008000 app_startup.s main.c -o app.elf

落地与验证方法

  1. 检查地址分配:编译后执行 arm-none-eabi-nm boot.elf | grep _estack 应输出 20010000(RAM顶端);arm-none-eabi-nm app.elf | grep _etext 应大于 0x08008000。
  2. 确认镜像边界:arm-none-eabi-objdump -h boot.elf 查看 .isr_vector 的VMA应为 08000000;app.elf 的VMA应为 08008000。
  3. 烧录验证:将 boot.bin 烧录至0x08000000,app.bin 烧录至0x08008000。复位后若Bootloader能跳转至 APP_BASE 且应用程序正常执行,则划分成功。
  4. 互不干扰测试:仅擦除并重写应用程序区(0x08008000之后),Bootloader区数据保持不变;反之亦然。

常见问题

  • 链接报错“region FLASH overflowed”:Bootloader代码超过32KB,需增大 BOOT_FLASH 长度并同步调整 APP_FLASH 起始地址。
  • 应用程序无法中断响应:需在应用程序启动代码中设置 SCB->VTOR = APP_BASE,否则向量表仍指向Bootloader区域。
  • RAM重叠风险:两个程序各自独立编译,若都使用 0x20000000 起始的RAM,运行时需确保Bootloader在跳转前不占用应用程序所需RAM区间。