1. 最小Bootloader框架

最小Bootloader框架 学习卡片

任务目标

在STM32F103最小系统上实现一个最简Bootloader:上电后从复位向量进入Bootloader代码,完成必要的栈指针初始化后,直接跳转到用户主程序入口,并通过用户主程序点亮LED来验证跳转成功。

环境与依赖

  • 硬件:STM32F103C8T6最小系统板,板载LED接PC13(低电平点亮)
  • 工具链:arm-none-eabi-gcc + Makefile,或STM32CubeIDE
  • 链接脚本:分别链接Bootloader(起始地址0x08000000)和App(起始地址0x08004000)

实现原理

Bootloader与App作为两个独立固件,分别烧录到不同Flash区域。Bootloader位于0x08000000,App位于0x08004000。Bootloader执行后通过函数指针跳转到App的复位向量处,App的复位向量会重新设置栈指针并进入main函数。

关键完整代码

// bootloader.c - 最小Bootloader实现
#include <stdint.h>

// App起始地址(与链接脚本一致)
#define APP_FLASH_BASE  0x08004000

// 从App起始地址处读取初始栈指针和复位向量
#define APP_STACK_ADDR  (*(volatile uint32_t *)(APP_FLASH_BASE))
#define APP_RESET_ADDR  (*(volatile uint32_t *)(APP_FLASH_BASE + 4))

// Bootloader自身栈空间(仅跳转用,无需太大)
uint32_t boot_stack[64] __attribute__((section(".boot_stack")));

// 跳转到App
void jump_to_app(void)
{
    uint32_t app_stack = APP_STACK_ADDR;
    uint32_t app_reset = APP_RESET_ADDR;

    // 基本合法性检查:栈指针和复位向量必须在RAM/Flash范围内
    if ((app_stack & 0x2FFE0000) != 0x20000000) return;
    if ((app_reset & 0x2FFE0000) != 0x08000000) return;

    // 设置主栈指针为App的初始栈指针
    __asm volatile ("msr msp, %0" : : "r" (app_stack) : "memory");

    // 跳转到App复位向量
    void (*app_entry)(void) = (void (*)(void))app_reset;
    app_entry();
}

// 复位向量入口
void reset_handler(void)
{
    jump_to_app();
    while(1); // 跳转失败则停在这里
}

// 向量表(最小化,仅包含初始栈和复位向量)
__attribute__((section(".isr_vector")))
const uint32_t boot_vector[] = {
    (uint32_t)boot_stack + sizeof(boot_stack),  // 初始栈顶
    (uint32_t)reset_handler,                     // 复位向量
};
// app.c - 用户App,验证跳转成功
#include "stm32f1xx.h"

void app_main(void)
{
    // 使能GPIOC时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
    // 配置PC13为推挽输出
    GPIOC->CRH = (GPIOC->CRH & ~0xFF0) | 0x002;
    // 点亮LED(PC13低电平点亮)
    GPIOC->BRR = (1 << 13);
    while(1);
}

// App复位向量
__attribute__((section(".isr_vector")))
const uint32_t app_vector[] = {
    (uint32_t)0x20005000,  // App栈顶(在RAM中)
    (uint32_t)app_main,     // 复位向量指向app_main
};

链接脚本要点

/* bootloader.ld - 关键差异 */
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 16K
RAM (xrw)  : ORIGIN = 0x20000000, LENGTH = 20K

/* app.ld - 关键差异 */
FLASH (rx) : ORIGIN = 0x08004000, LENGTH = 48K
RAM (xrw)  : ORIGIN = 0x20000000, LENGTH = 20K

编译与烧录

# 编译Bootloader
arm-none-eabi-gcc -c -mcpu=cortex-m3 -mthumb bootloader.c -o bootloader.o
arm-none-eabi-ld -T bootloader.ld bootloader.o -o bootloader.elf
arm-none-eabi-objcopy -O binary bootloader.elf bootloader.bin

# 编译App
arm-none-eabi-gcc -c -mcpu=cortex-m3 -mthumb app.c -o app.o
arm-none-eabi-ld -T app.ld app.o -o app.elf
arm-none-eabi-objcopy -O binary app.elf app.bin

# 烧录(使用ST-Link)
st-flash write bootloader.bin 0x08000000
st-flash write app.bin 0x08004000

验证方法

  1. 按上述步骤编译并烧录Bootloader和App
  2. 复位开发板(按复位键或重新上电)
  3. 通过条件:板载LED(PC13)点亮并保持常亮,说明Bootloader成功跳转到App,App的main函数已执行
  4. 若LED不亮,检查:
    • App是否烧录到0x08004000(可用st-flash read验证)
    • Bootloader向量表中栈顶地址是否指向有效的RAM区域
    • App的向量表是否链接到0x08004000起始位置

常见问题

  • 跳转后进入HardFault:多为App栈指针非法或App链接地址与烧录地址不一致,检查app.ld的FLASH ORIGIN是否为0x08004000
  • LED不亮但程序未死机:确认PC13引脚连接正确,部分开发板LED为高电平点亮,需将BRR改为BSRR
  • Bootloader无法跳转:在jump_to_app中增加调试断点,检查APP_STACK_ADDR和APP_RESET_ADDR读取值是否与app.elf中符号表一致