2. Bootloader串口打印Hello World
任务目标
本案例面向STM32F407平台,建立一个可编译、可烧录的最小BSP工程框架。工程包含三个核心文件:启动文件(汇编)、链接脚本(.ld)和Makefile构建脚本。最终产物为bsp.elf与bsp.bin,可通过OpenOCD+ST-Link烧录至开发板。
环境依赖
- ARM交叉编译器:
arm-none-eabi-gcc(版本 ≥ 10.0) - 构建工具:
make - 烧录工具:
openocd(可选,用于验证) - 目标芯片:STM32F407VET6(1MB Flash,192KB RAM)
工程结构
bsp_minimal/
├── startup.s # 启动文件:向量表 + Reset_Handler
├── linker.ld # 链接脚本:内存布局与段定义
├── Makefile # 编译、链接、生成bin
└── main.c # 最小主程序(LED闪烁验证)
关键完整代码
startup.s — 定义初始栈指针、中断向量表及复位入口:
.syntax unified
.cpu cortex-m4
.thumb
.section .isr_vector
.word _estack /* 栈顶地址,由链接脚本提供 */
.word Reset_Handler /* 复位向量 */
.section .text
.thumb_func
.global Reset_Handler
Reset_Handler:
ldr r0, =_sdata
ldr r1, =_edata
ldr r2, =_sidata
copy_data:
cmp r0, r1
beq zero_bss
ldr r3, [r2], #4
str r3, [r0], #4
b copy_data
zero_bss:
ldr r0, =_sbss
ldr r1, =_ebss
movs r2, #0
clear_bss:
cmp r0, r1
beq main_call
str r2, [r0], #4
b clear_bss
main_call:
bl main
loop:
b loop
linker.ld — 定义Flash/RAM地址与段布局:
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1M
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 192K
}
_estack = ORIGIN(RAM) + LENGTH(RAM);
SECTIONS
{
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector))
. = ALIGN(4);
} > FLASH
.text :
{
. = ALIGN(4);
*(.text)
*(.text*)
. = ALIGN(4);
} > FLASH
.rodata :
{
. = ALIGN(4);
*(.rodata)
*(.rodata*)
. = ALIGN(4);
} > FLASH
_sidata = LOADADDR(.data);
.data :
{
. = ALIGN(4);
_sdata = .;
*(.data)
*(.data*)
. = ALIGN(4);
_edata = .;
} > RAM AT> FLASH
.bss :
{
. = ALIGN(4);
_sbss = .;
*(.bss)
*(.bss*)
*(COMMON)
. = ALIGN(4);
_ebss = .;
} > RAM
}
main.c — 最小功能验证(LED闪烁):
#include <stdint.h>
#define RCC_AHB1ENR (*(volatile uint32_t *)0x40023830)
#define GPIOG_MODER (*(volatile uint32_t *)0x40021800)
#define GPIOG_ODR (*(volatile uint32_t *)0x40021814)
void delay(volatile uint32_t count) {
while (count--) { __asm volatile("nop"); }
}
int main(void) {
RCC_AHB1ENR |= (1 << 6); /* 使能GPIOC时钟 */
GPIOG_MODER &= ~(3 << 28); /* PC14 设为输出 */
GPIOG_MODER |= (1 << 28);
while (1) {
GPIOG_ODR ^= (1 << 14); /* 翻转PC14 */
delay(2000000);
}
}
Makefile — 编译与链接规则:
TARGET = bsp
PREFIX = arm-none-eabi-
CC = $(PREFIX)gcc
OBJCOPY = $(PREFIX)objcopy
CFLAGS = -mcpu=cortex-m4 -mthumb -Wall -O2 -nostdlib -ffreestanding
LDFLAGS = -T linker.ld -nostdlib
all: $(TARGET).bin
$(TARGET).elf: startup.s main.c linker.ld
$(CC) $(CFLAGS) $(LDFLAGS) startup.s main.c -o $@
$(TARGET).bin: $(TARGET).elf
$(OBJCOPY) -O binary $< $@
clean:
rm -f $(TARGET).elf $(TARGET).bin
flash: $(TARGET).bin
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg \
-c "program $< verify reset exit"
编译与运行
- 将上述四个文件保存至同一目录。
- 执行
make,生成bsp.elf与bsp.bin。 - 连接ST-Link与开发板,执行
make flash烧录。
验证方法
- 编译验证:
make无报错即通过,生成文件大小约为数百字节(不含未初始化数据)。 - 功能验证:烧录后观察开发板PC14引脚所接LED以约1Hz频率闪烁。若LED不亮,检查RCC寄存器地址(STM32F407为0x40023830)及GPIOG基地址(0x40021800)是否正确。
- 调试验证:执行
arm-none-eabi-nm bsp.elf | grep main,应输出main符号地址(位于0x08000000之后),确认链接布局正确。
常见问题
- 链接错误“undefined reference to _estack”:确认链接脚本中
_estack定义未被注释,且Makefile的LDFLAGS正确指定了-T linker.ld。 - 烧录后无反应:检查启动文件中向量表首项是否为栈顶地址(应为
0x20030000),以及Reset_Handler是否完成.data和.bss段初始化。 - 编译警告“implicit declaration of __asm”:将
__asm改为__asm__或直接使用__NOP()内建函数。