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