1. Bootloader启动流程解析

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);
    }
}

编译与烧录

  1. 在STM32CubeIDE中新建STM32F103C8T6空项目,将上述代码保存为bootloader.c并替换main.c内容
  2. 编译生成.hex文件
  3. 使用ST-Link或串口ISP将Bootloader烧录到地址0x08000000(Flash起始地址)
  4. 应用程序需编译链接到0x08004000起始地址,并在其Flash末尾写入标志0xA5A5(地址0x0800FFFE)

验证方法与判定标准

  1. Bootloader模式验证:上电后不按PA0按键,串口应输出Bootloader v1.0、System init done、Stay in Bootloader,板载LED以0.5秒间隔闪烁
  2. 跳转验证:在应用程序有效标志已写入的前提下,按住PA0按键同时上电复位,串口应输出Jump condition met和Jump to APP...,随后LED停止闪烁(由应用程序接管)
  3. 无效应用验证:若Flash中无有效标志(全0xFF),即使按下按键也不会跳转,串口输出APP SP invalid或停留在Bootloader模式

通过标准:上述三种场景的串口输出和LED行为与预期一致,且跳转后应用程序能正常运行(如应用程序LED常亮或按自身逻辑工作)。