1. Bootloader基础框架搭建

Bootloader基础框架搭建 学习卡片

任务说明

本案例实现一个最小可用的Bootloader,运行于STM32F103系列MCU上。该Bootloader完成以下核心功能:

  1. 初始化系统时钟与串口(USART1,115200-8-N-1)
  2. 通过串口打印Bootloader版本信息
  3. 检查应用程序区(0x08008000)是否有效(栈顶地址在RAM范围内)
  4. 若应用有效,则跳转执行应用程序;否则打印错误信息并停留在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。

编译与烧录

  1. 将上述代码保存为main.c,配合标准外设库头文件与启动文件startup_stm32f103.s编译。
  2. 生成bootloader.bin后,通过ST-Link或串口ISP烧录至地址0x08000000。
  3. 准备一个简单的应用程序(如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,且栈大小设置合理。