1. Bootloader基础架构搭建

Bootloader基础架构搭建 学习卡片

任务目标

本案例实现一个最小Bootloader,运行于STM32F103系列MCU(以STM32F103C8T6为例),完成以下核心功能:

  1. 系统上电后初始化必要外设(LED指示Bootloader运行状态);
  2. 检查应用程序起始地址(0x08004000)处是否包含有效栈指针与中断向量;
  3. 若应用有效,则跳转至应用程序并执行;若无效,则停留在Bootloader并周期性闪烁LED。

环境与依赖

  • 硬件:STM32F103C8T6最小系统板,板载LED接PC13(低电平点亮)
  • 工具链:arm-none-eabi-gcc + Makefile,或STM32CubeIDE
  • 标准外设库:STM32F1xx标准外设库(或直接操作寄存器,本实现使用寄存器方式以保持最小依赖)

实现原理

Bootloader与应用程序分别链接到不同的Flash地址。Bootloader位于0x08000000,应用程序位于0x08004000(偏移16KB)。跳转前需完成:

  1. 确认应用首字(初始栈指针MSP)位于RAM地址范围(0x20000000~0x20010000);
  2. 确认应用第二字(复位向量)位于Flash应用区域(0x08004000~0x0800FFFF);
  3. 关闭全局中断,将MSP设置为应用首字,然后通过函数指针跳转到复位向量。

关键完整代码

/* main.c - Bootloader for STM32F103C8T6 */
#include "stm32f10x.h"

#define APP_BASE_ADDR   0x08004000UL
#define APP_MSP_ADDR    (*(volatile uint32_t *)APP_BASE_ADDR)
#define APP_RESET_ADDR  (*(volatile uint32_t *)(APP_BASE_ADDR + 4))
#define SRAM_BASE       0x20000000UL
#define SRAM_END        0x20010000UL
#define FLASH_END       0x08010000UL

void delay_ms(volatile uint32_t ms) {
    for (volatile uint32_t i = 0; i < ms * 4000; i++) { __NOP(); }
}

void led_init(void) {
    RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;   /* 使能GPIOC时钟 */
    GPIOC->CRH &= ~(0xF << 20);           /* PC13清配置 */
    GPIOC->CRH |= (0x2 << 20);            /* PC13推挽输出50MHz */
    GPIOC->BRR = GPIO_BRR_BR13;           /* 初始点亮LED */
}

void led_blink(int times) {
    for (int i = 0; i < times; i++) {
        GPIOC->BSRR = GPIO_BSRR_BS13;     /* 灭 */
        delay_ms(200);
        GPIOC->BRR = GPIO_BRR_BR13;       /* 亮 */
        delay_ms(200);
    }
}

int is_app_valid(void) {
    uint32_t msp = APP_MSP_ADDR;
    uint32_t rst = APP_RESET_ADDR;
    /* 栈指针必须在SRAM范围内 */
    if (msp < SRAM_BASE || msp >= SRAM_END) return 0;
    /* 复位向量必须在Flash应用区域内 */
    if (rst < APP_BASE_ADDR || rst >= FLASH_END) return 0;
    return 1;
}

typedef void (*pFunction)(void);

void jump_to_app(void) {
    uint32_t msp = APP_MSP_ADDR;
    pFunction jump = (pFunction)APP_RESET_ADDR;

    __disable_irq();                      /* 关闭全局中断 */
    SCB->VTOR = APP_BASE_ADDR;            /* 重定位中断向量表 */

    __set_MSP(msp);                       /* 设置应用栈指针 */
    jump();                               /* 跳转至应用复位向量 */
}

int main(void) {
    led_init();
    led_blink(2);                         /* Bootloader启动指示:闪2次 */

    if (is_app_valid()) {
        led_blink(1);                     /* 检测到有效应用:闪1次后跳转 */
        jump_to_app();
    }

    /* 应用无效:无限循环,LED持续闪烁 */
    while (1) {
        led_blink(3);
    }
}

链接配置说明

Bootloader与应用程序需使用不同的链接脚本。Bootloader链接脚本的FLASH起始地址为0x08000000,长度为0x4000(16KB)。应用程序链接脚本的FLASH起始地址为0x08004000,长度为0xC000(48KB)。应用程序编译时需在系统启动文件(如startup_stm32f103xb.s)中定义VECT_TAB_OFFSET=0x4000,或在system_stm32f10x.c中设置VECT_TAB_OFFSET宏,确保应用程序的中断向量表重定位到0x08004000。

构建与烧录

# Bootloader编译(以Makefile为例)
make clean && make

# 烧录Bootloader至0x08000000
st-flash write bootloader.bin 0x08000000

# 烧录应用程序至0x08004000
st-flash write app.bin 0x08004000

验证方法

场景操作预期现象判定标准
仅烧录Bootloader只烧录bootloader.bin,复位LED持续循环闪烁(亮200ms灭200ms,每组3次)Bootloader正确运行且检测到应用无效
Bootloader+有效应用先烧bootloader.bin,再烧app.bin,复位LED先闪2次(Bootloader启动),再闪1次(检测到有效应用),随后应用代码接管(应用自身LED行为)跳转成功,应用正常运行
应用校验失败向0x08004000写入全0xFFLED持续循环闪烁无效应用被正确拒绝,未发生异常跳转

通过串口调试助手观察:若在jump_to_app()前添加UART打印(本最小实现未包含),可输出"Jump to app..."确认执行路径。当前实现以LED闪烁次数作为可视判定依据。

常见问题

  • 跳转后进入HardFault:检查应用是否已设置VECT_TAB_OFFSET=0x4000;确认应用链接脚本FLASH起始地址正确。
  • LED不亮:确认PC13接线(部分开发板LED为高电平点亮,需反转逻辑)。
  • 应用无法被识别:确认应用bin文件确实烧录至0x08004000,且首字为有效栈指针(不应为0xFFFFFFFF)。