1. 最小Bootloader
任务目标
在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"
验证方法
- 烧录后按下开发板复位键,观察板载LED(PC13)是否点亮。
- 若LED常亮,说明启动链路完整:复位向量正确→栈初始化→main执行→GPIO配置成功。
- 若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起始位置。