1. 最小Bootloader

任务目标

在STM32F103开发板上实现一个最小Bootloader:上电后从Flash启动,完成必要的硬件初始化(时钟、GPIO),最终点亮板载LED,以此验证芯片复位后能够正确执行Flash中的启动代码。

硬件与依赖

  • 硬件:STM32F103C8T6最小系统板(蓝色Pill),板载LED接PC13(低电平点亮)
  • 工具链:arm-none-eabi-gcc + OpenOCD + ST-Link(或使用Makefile + arm-none-eabi-gcc)
  • 无外部库依赖,直接操作寄存器

核心实现

Bootloader的本质是芯片复位后执行的第一段代码。STM32从0x08000000(Flash起始地址)读取初始栈指针和复位向量,随后跳转到复位处理函数。因此最小Bootloader只需:定义向量表、编写复位函数完成时钟与GPIO初始化、点亮LED并进入死循环。

// main.c - 最小Bootloader:点亮LED
#include <stdint.h>

// STM32F103 寄存器地址
#define RCC_BASE    0x40021000
#define GPIOC_BASE  0x40011000

#define RCC_APB2ENR (*(volatile uint32_t *)(RCC_BASE + 0x18))
#define GPIOC_CRH   (*(volatile uint32_t *)(GPIOC_BASE + 0x04))
#define GPIOC_ODR   (*(volatile uint32_t *)(GPIOC_BASE + 0x0C))

// 向量表:栈顶地址 + 复位向量(必须位于0x08000000起始处)
// 栈顶指向RAM末尾(STM32F103C8 有20KB RAM,即0x20005000)
__attribute__((section(".isr_vector")))
const uint32_t vector_table[] = {
    0x20005000,          // 初始栈指针
    (uint32_t)_reset      // 复位向量
};

void _reset(void) __attribute__((naked, section(".text._reset")));

void _reset(void) {
    // 1. 使能GPIOC时钟(APB2总线,位4)
    RCC_APB2ENR |= (1 << 4);
    
    // 2. 配置PC13为推挽输出,最大速度2MHz
    // CRH寄存器控制PC13(对应位[23:20]),设置CNF13=00(推挽)、MODE13=10(2MHz)
    GPIOC_CRH &= ~(0xF << 20);   // 先清零
    GPIOC_CRH |=  (0x2 << 20);   // MODE13=10, CNF13=00
    
    // 3. 点亮LED(PC13输出低电平)
    GPIOC_ODR &= ~(1 << 13);
    
    // 4. 死循环,保持LED点亮
    while(1);
}

链接脚本(linker.ld)

/* 最小链接脚本:将向量表放在Flash起始地址 */
ENTRY(_reset)

MEMORY
{
    FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 64K
    RAM (rwx)  : ORIGIN = 0x20000000, LENGTH = 20K
}

SECTIONS
{
    .isr_vector :
    {
        KEEP(*(.isr_vector))
    } > FLASH

    .text :
    {
        *(.text*)
    } > FLASH
}

编译与烧录

# 编译命令
arm-none-eabi-gcc -c -mcpu=cortex-m3 -mthumb main.c -o main.o
arm-none-eabi-ld -T linker.ld main.o -o bootloader.elf
arm-none-eabi-objcopy -O binary bootloader.elf bootloader.bin

# 烧录(使用ST-Link)
st-flash write bootloader.bin 0x08000000

验证方法

  1. 烧录完成后,按一下开发板上的复位键(NRST)
  2. 通过条件:板载LED(PC13)点亮并保持常亮
  3. 若LED不亮,检查:
    • 烧录地址是否为0x08000000
    • 向量表首字是否为0x20005000(栈顶地址)
    • PC13是否被其他跳线占用

此Bootloader验证了芯片上电后从Flash起始地址正确取指、加载栈指针并执行复位函数的完整启动链路。后续案例可在此基础上增加外设初始化、跳转至应用程序等功能。