1. 最小Bootloader

最小Bootloader:点亮LED 学习卡片

任务目标

在STM32F103C8T6最小系统板上,编写一个不依赖任何库函数的最简Bootloader:上电后完成必要的硬件初始化(时钟、GPIO),然后点亮板载LED并进入死循环。该案例用于验证裸机启动流程的基本框架——从复位向量到C入口函数的完整链路。

硬件与依赖

  • 硬件:STM32F103C8T6开发板(蓝色Pill板),板载LED接PC13(低电平点亮)
  • 工具链:arm-none-eabi-gcc、OpenOCD、ST-Link调试器
  • 无需任何HAL/标准库,纯寄存器操作

启动流程要点

STM32上电后从0x08000000读取初始SP,从0x08000004读取复位向量并跳转执行。本例的startup.s完成栈指针设置后直接跳入main(),不复制数据段(无全局变量)、不初始化BSS(无未初始化全局变量),保持最小启动链路。

完整代码

文件1:startup.s(复位向量与启动入口)

.syntax unified
.cpu cortex-m3
.thumb

.section .isr_vector
.word _estack          @ 初始栈顶(由链接脚本提供)
.word Reset_Handler    @ 复位向量

.section .text
.thumb_func
.global Reset_Handler
Reset_Handler:
    ldr sp, =_estack   @ 确保栈指针正确
    bl main
    b .                 @ 死循环(main不应返回)

.section .text
.thumb_func
.global main
main:
    @ 使能 GPIOC 时钟(RCC_APB2ENR 地址 0x40021018,bit4)
    ldr r0, =0x40021018
    ldr r1, [r0]
    orr r1, r1, #(1 << 4)
    str r1, [r0]

    @ 配置 PC13 为推挽输出(GPIOC_CRH 地址 0x40011004)
    @ PC13 对应 CRH 的 bit20-23,设置 MODE=10(2MHz), CNF=00(推挽)
    ldr r0, =0x40011004
    ldr r1, [r0]
    bic r1, r1, #(0xF << 20)
    orr r1, r1, #(0x2 << 20)
    str r1, [r0]

    @ 点亮 LED:PC13 输出低电平(ODR 地址 0x4001100C,bit13)
    ldr r0, =0x4001100C
    ldr r1, [r0]
    bic r1, r1, #(1 << 13)
    str r1, [r0]

loop:
    b loop

文件2:linker.ld(最小链接脚本)

ENTRY(Reset_Handler)

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

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

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

    _estack = ORIGIN(RAM) + LENGTH(RAM);
}

编译与烧录

# 编译(生成elf)
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -nostdlib \
  -T linker.ld startup.s -o bootloader.elf

# 生成hex(用于烧录)
arm-none-eabi-objcopy -O ihex bootloader.elf bootloader.hex

# 通过ST-Link烧录
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg \
  -c "program bootloader.elf verify reset exit"

验证方法

  1. 烧录后按下开发板复位键,观察板载LED(PC13)是否点亮。
  2. 若LED常亮,说明启动链路完整:复位向量正确→栈初始化→main执行→GPIO配置成功。
  3. 若LED不亮,依次检查:
    - 烧录是否成功(OpenOCD输出无error)
    - 链接脚本中_estack是否指向RAM末尾(0x20005000)
    - 代码中GPIO时钟是否已使能(遗漏则写CRH无效)

预期结果:PC13输出低电平,LED点亮并保持常亮。该结果由代码逻辑直接确定——只要复位后能执行到bic r1, r1, #(1 << 13)且时钟已使能,LED必然点亮。

常见问题

  • LED不亮且调试器连不上:检查BOOT0引脚是否拉低(应从Flash启动),以及ST-Link接线是否正确。
  • 编译报错“undefined symbol: _estack”:确认链接脚本已通过-T参数传入,且_estack定义在SECTIONS之外。
  • 烧录后无反应但调试正常:在main入口设断点,单步确认是否进入;若未进入,检查复位向量表是否被链接到0x08000000起始位置。