2. 创建最小BSP工程框架

任务目标

本案例面向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"

编译与运行

  1. 将上述四个文件保存至同一目录。
  2. 执行 make,生成 bsp.elf 与 bsp.bin。
  3. 连接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()内建函数。