1. 车载Bootloader需求分析与功能定义
任务目标
本案例实现一个最小可运行的Bootloader启动流程:上电后完成必要的硬件初始化,检查跳转条件(此处以外部触发标志和应用程序有效标志作为条件),满足条件则跳转到应用程序入口执行,否则停留在Bootloader中等待。通过串口打印和LED状态区分当前所处阶段。
环境与依赖
- 硬件:STM32F103C8T6最小系统板(蓝丸),板载LED(PC13),USART1(PA9/PA10)
- 软件:STM32CubeIDE 或 Keil MDK-ARM,STM32标准外设库或HAL库均可
- 本案例代码基于寄存器操作,不依赖具体库,可直接移植
关键完整代码
// bootloader.c - 最小Bootloader启动流程实现
#include "stm32f10x.h"
// 应用程序入口地址(存放在0x08004000,即偏移16KB处)
#define APP_ADDRESS 0x08004000
// 应用程序有效标志(存放在内部Flash最后2字节)
#define APP_FLAG_ADDR 0x0800FFFE
#define APP_FLAG_VALUE 0xA5A5
// 跳转条件:外部触发(此处用按键PA0模拟,低电平有效)
#define JUMP_TRIGGER() (GPIOA->IDR & (1 << 0)) == 0
// 简单延时
static void delay_ms(volatile uint32_t ms) {
while (ms--) {
for (volatile uint32_t i = 0; i < 8000; i++);
}
}
// 串口发送字符串
static void uart_send_str(const char *str) {
while (*str) {
while (!(USART1->SR & USART_SR_TXE));
USART1->DR = *str++;
}
}
// 系统时钟初始化(HSI 8MHz,PLL倍频到64MHz)
static void system_clock_init(void) {
// 使能HSE(外部8MHz晶振)
RCC->CR |= RCC_CR_HSEON;
while (!(RCC->CR & RCC_CR_HSERDY));
// 配置FLASH预取缓冲,2等待周期(64MHz)
FLASH->ACR = FLASH_ACR_PRFTBE | FLASH_ACR_LATENCY_2;
// PLL倍频:HSE 8MHz × 8 = 64MHz
RCC->CFGR = RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL8;
RCC->CR |= RCC_CR_PLLON;
while (!(RCC->CR & RCC_CR_PLLRDY));
// 切换系统时钟到PLL
RCC->CFGR |= RCC_CFGR_SW_PLL;
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
// 外设时钟使能
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPCEN | RCC_APB2ENR_USART1EN;
}
// GPIO和USART初始化
static void gpio_uart_init(void) {
// PA0按键输入(上拉),PA9=TX,PA10=RX,PC13=LED
GPIOA->CRL = (GPIOA->CRL & 0xFFFFFF0F) | 0x00000080; // PA0浮空输入
GPIOA->CRH = (GPIOA->CRH & 0xFFFFF00F) | 0x000004B0; // PA9=AF推挽50MHz, PA10=浮空输入
GPIOC->CRH = (GPIOC->CRH & 0xFF0FFFFF) | 0x00300000; // PC13=推挽输出50MHz
// USART1: 115200-8-N-1
USART1->BRR = 0x00000347; // 64MHz/115200 ≈ 347
USART1->CR1 = USART_CR1_UE | USART_CR1_TE;
}
// 检查应用程序是否有效(Flash中标志字)
static uint8_t app_is_valid(void) {
uint16_t *flag = (uint16_t *)APP_FLAG_ADDR;
return (*flag == APP_FLAG_VALUE) ? 1 : 0;
}
// 跳转到应用程序
static void jump_to_app(void) {
uint32_t app_sp = *(volatile uint32_t *)APP_ADDRESS; // 取栈顶地址
uint32_t app_pc = *(volatile uint32_t *)(APP_ADDRESS + 4); // 取复位向量
// 检查栈顶地址是否在RAM范围(0x20000000-0x20010000)
if ((app_sp & 0x2FFE0000) != 0x20000000) {
uart_send_str("APP SP invalid\r\n");
return;
}
uart_send_str("Jump to APP...\r\n");
delay_ms(10);
// 关闭全局中断
__disable_irq();
// 设置主栈指针并跳转
__set_MSP(app_sp);
// 定义函数指针并调用
void (*app_entry)(void) = (void (*)(void))app_pc;
app_entry();
// 不应返回
while (1);
}
int main(void) {
// 1. 上电初始化
system_clock_init();
gpio_uart_init();
// 2. 打印Bootloader启动信息
uart_send_str("Bootloader v1.0\r\n");
uart_send_str("System init done\r\n");
// 3. 跳转条件判断
// 条件1:外部触发(PA0按键按下)
// 条件2:应用程序有效标志存在
if (JUMP_TRIGGER() && app_is_valid()) {
uart_send_str("Jump condition met\r\n");
jump_to_app();
}
// 4. 条件不满足,停留在Bootloader
uart_send_str("Stay in Bootloader\r\n");
while (1) {
GPIOC->ODR ^= (1 << 13); // LED闪烁表示在Bootloader
delay_ms(500);
}
}
编译与烧录
- 在STM32CubeIDE中新建STM32F103C8T6空项目,将上述代码保存为
bootloader.c并替换main.c内容 - 编译生成
.hex文件 - 使用ST-Link或串口ISP将Bootloader烧录到地址
0x08000000(Flash起始地址) - 应用程序需编译链接到
0x08004000起始地址,并在其Flash末尾写入标志0xA5A5(地址0x0800FFFE)
验证方法与判定标准
- Bootloader模式验证:上电后不按PA0按键,串口应输出
Bootloader v1.0、System init done、Stay in Bootloader,板载LED以0.5秒间隔闪烁 - 跳转验证:在应用程序有效标志已写入的前提下,按住PA0按键同时上电复位,串口应输出
Jump condition met和Jump to APP...,随后LED停止闪烁(由应用程序接管) - 无效应用验证:若Flash中无有效标志(全0xFF),即使按下按键也不会跳转,串口输出
APP SP invalid或停留在Bootloader模式
通过标准:上述三种场景的串口输出和LED行为与预期一致,且跳转后应用程序能正常运行(如应用程序LED常亮或按自身逻辑工作)。