1. Bootloader基础架构搭建
任务目标
本案例实现一个最小Bootloader,运行于STM32F103系列MCU(以STM32F103C8T6为例),完成以下核心功能:
- 系统上电后初始化必要外设(LED指示Bootloader运行状态);
- 检查应用程序起始地址(0x08004000)处是否包含有效栈指针与中断向量;
- 若应用有效,则跳转至应用程序并执行;若无效,则停留在Bootloader并周期性闪烁LED。
环境与依赖
- 硬件:STM32F103C8T6最小系统板,板载LED接PC13(低电平点亮)
- 工具链:arm-none-eabi-gcc + Makefile,或STM32CubeIDE
- 标准外设库:STM32F1xx标准外设库(或直接操作寄存器,本实现使用寄存器方式以保持最小依赖)
实现原理
Bootloader与应用程序分别链接到不同的Flash地址。Bootloader位于0x08000000,应用程序位于0x08004000(偏移16KB)。跳转前需完成:
- 确认应用首字(初始栈指针MSP)位于RAM地址范围(0x20000000~0x20010000);
- 确认应用第二字(复位向量)位于Flash应用区域(0x08004000~0x0800FFFF);
- 关闭全局中断,将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写入全0xFF | LED持续循环闪烁 | 无效应用被正确拒绝,未发生异常跳转 |
通过串口调试助手观察:若在jump_to_app()前添加UART打印(本最小实现未包含),可输出"Jump to app..."确认执行路径。当前实现以LED闪烁次数作为可视判定依据。
常见问题
- 跳转后进入HardFault:检查应用是否已设置
VECT_TAB_OFFSET=0x4000;确认应用链接脚本FLASH起始地址正确。 - LED不亮:确认PC13接线(部分开发板LED为高电平点亮,需反转逻辑)。
- 应用无法被识别:确认应用bin文件确实烧录至0x08004000,且首字为有效栈指针(不应为0xFFFFFFFF)。