1. Bootloader基础框架搭建
任务说明
本案例实现一个最小可用的Bootloader,运行于STM32F103系列MCU上。该Bootloader完成以下核心功能:
- 初始化系统时钟与串口(USART1,115200-8-N-1)
- 通过串口打印Bootloader版本信息
- 检查应用程序区(0x08008000)是否有效(栈顶地址在RAM范围内)
- 若应用有效,则跳转执行应用程序;否则打印错误信息并停留在Bootloader
硬件与依赖
- MCU:STM32F103C8T6(或兼容型号)
- 串口:USART1(PA9-TX,PA10-RX),USB-TTL转换器连接PC
- 编译工具:arm-none-eabi-gcc + Makefile(或使用STM32CubeIDE直接导入)
- 标准外设库:STM32F10x_StdPeriph_Lib(或使用寄存器操作,本案例采用寄存器方式以保持最小依赖)
完整实现代码
以下代码为单个main.c文件,包含全部必要逻辑。链接脚本需将代码加载地址设为0x08000000,并将VECT_TAB_OFFSET设为0x0000(Bootloader自身中断向量位于Flash起始处)。
#include "stm32f10x.h"
#include <string.h>
#define APP_ADDRESS 0x08008000UL
#define APP_STACK_ADDR (*(volatile uint32_t *)APP_ADDRESS)
#define APP_RESET_HANDLER (*(volatile uint32_t *)(APP_ADDRESS + 4))
#define SRAM_BASE 0x20000000UL
#define SRAM_END 0x20005000UL // 20KB for STM32F103C8
static void SystemClock_Config(void) {
// 使用内部8MHz HSI,倍频至64MHz(PLL x8)
RCC->CR |= RCC_CR_HSION;
while (!(RCC->CR & RCC_CR_HSIRDY));
RCC->CFGR = RCC_CFGR_PLLMULL8 | RCC_CFGR_PLLSRC_HSI_Div2;
RCC->CR |= RCC_CR_PLLON;
while (!(RCC->CR & RCC_CR_PLLRDY));
RCC->CFGR |= RCC_CFGR_SW_PLL;
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
SystemCoreClockUpdate();
}
static void USART1_Init(void) {
RCC->APB2ENR |= RCC_APB2ENR_USART1EN | RCC_APB2ENR_IOPAEN;
// PA9-TX复用推挽输出,PA10-RX浮空输入
GPIOA->CRH = (GPIOA->CRH & 0xFFFFF00F) | 0x000008B0;
USART1->BRR = 0x000034BC; // 64MHz/115200 = 555.56 → 0x22B,取整为0x22C
USART1->CR1 = USART_CR1_UE | USART_CR1_TE | USART_CR1_RE;
}
static void USART1_SendChar(char c) {
while (!(USART1->SR & USART_SR_TXE));
USART1->DR = c;
}
static void USART1_SendString(const char *s) {
while (*s) USART1_SendChar(*s++);
}
static void USART1_SendHex(uint32_t val) {
char buf[11];
buf[0] = '0'; buf[1] = 'x';
const char hex[] = "0123456789ABCDEF";
for (int i = 0; i < 8; i++) {
buf[2 + i] = hex[(val >> (28 - 4 * i)) & 0xF];
}
buf[10] = '\0';
USART1_SendString(buf);
}
static int IsAppValid(void) {
uint32_t sp = APP_STACK_ADDR;
return (sp >= SRAM_BASE && sp < SRAM_END);
}
static void JumpToApp(void) {
uint32_t app_sp = APP_STACK_ADDR;
uint32_t app_pc = APP_RESET_HANDLER;
typedef void (*pFunction)(void);
pFunction jump = (pFunction)app_pc;
// 设置主栈指针
__set_MSP(app_sp);
// 关闭全局中断(跳转前)
__disable_irq();
// 重映射中断向量表到应用区
SCB->VTOR = APP_ADDRESS;
// 跳转
jump();
}
int main(void) {
SystemClock_Config();
USART1_Init();
USART1_SendString("\r\n===== Bootloader v1.0 =====\r\n");
USART1_SendString("App Address: ");
USART1_SendHex(APP_ADDRESS);
USART1_SendString("\r\n");
if (IsAppValid()) {
USART1_SendString("App found, jumping...\r\n");
JumpToApp();
} else {
USART1_SendString("ERROR: No valid app!\r\n");
USART1_SendString("Stay in bootloader.\r\n");
}
while (1) {
// 停留在此处(无有效应用时)
}
}
链接脚本关键配置
使用GCC链接脚本时,FLASH起始地址为0x08000000,长度0x8000(32KB,为应用预留从0x08008000开始的32KB空间)。应用工程的链接脚本需将FLASH起始地址设为0x08008000,并在其system_stm32f10x.c中设置VECT_TAB_OFFSET=0x8000。
编译与烧录
- 将上述代码保存为
main.c,配合标准外设库头文件与启动文件startup_stm32f103.s编译。 - 生成
bootloader.bin后,通过ST-Link或串口ISP烧录至地址0x08000000。 - 准备一个简单的应用程序(如LED闪烁),将其编译并烧录至
0x08008000。
验证方法
- 场景1(无应用):仅烧录Bootloader,上电后串口输出
ERROR: No valid app!并停留在Bootloader。 - 场景2(有应用):在0x08008000处烧录有效应用后复位,串口输出
App found, jumping...,随后应用开始运行(如LED闪烁),证明跳转成功。 - 判定标准:串口输出与上述逻辑一致,且应用代码能够正常执行,即视为通过。
常见问题
- 跳转后无反应:检查应用工程的
VECT_TAB_OFFSET是否设为0x8000,否则中断向量表指向错误。 - 串口无输出:确认USART1波特率计算是否正确(本案例HSI经PLL倍频至64MHz,BRR=0x22C对应115200)。
- 栈顶地址校验失败:确认应用工程的链接脚本已将
FLASH起始地址改为0x08008000,且栈大小设置合理。