3. 启动文件分析
任务目标
分析STM32F103系列MCU的启动文件(startup_stm32f103xe.s),理解堆栈指针初始化、中断向量表定义以及复位处理流程,并修改启动文件以在调用main()之前完成必要的系统初始化。
启动文件核心结构
STM32启动文件(汇编)主要完成三件事:
- 堆栈指针初始化:将栈顶地址加载到SP寄存器
- 中断向量表:定义各中断服务函数的入口地址,向量表首字为栈顶地址,第二个字为复位处理函数地址
- 复位处理:调用
SystemInit()配置时钟,然后跳转到main()
关键完整代码
以下为修改后的启动文件核心部分(基于STM32F103标准外设库的startup_stm32f103xe.s),增加了自定义的PreMainInit()调用:
; 栈配置
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
; 堆配置
Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
; 向量表
PRESERVE8
THUMB
; 向量表段
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
__Vectors DCD __initial_sp ; 栈顶地址
DCD Reset_Handler ; 复位向量
DCD NMI_Handler
DCD HardFault_Handler
; ... 其他中断向量(此处省略中间部分,实际文件中完整保留)
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors
; 代码段
AREA |.text|, CODE, READONLY
; 复位处理
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT PreMainInit
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =PreMainInit
BLX R0
LDR R0, =__main
BX R0
ENDP
; 预主函数初始化(用户自定义)
PreMainInit PROC
EXPORT PreMainInit [WEAK]
; 在此处添加用户自定义初始化代码
; 例如:启用FPU、配置外部存储器等
BX LR
ENDP
; 默认中断处理
NMI_Handler PROC
EXPORT NMI_Handler [WEAK]
B .
ENDP
; ... 其他中断处理函数(省略)
ALIGN
END
修改说明
相比原始启动文件,本次修改的关键点:
- 在
Reset_Handler中,在SystemInit()之后、__main(最终调用main())之前,插入了对PreMainInit()的调用 - 新增了
PreMainInit函数定义,使用[WEAK]属性,允许用户在C代码中重新实现同名的强定义函数来覆盖默认的空实现 - 向量表首字
__initial_sp由链接器根据栈段自动计算,确保上电后SP指向栈顶
C语言侧实现
在C代码中提供强定义的PreMainInit函数,用于在进入main()前执行特定初始化:
/* user_premain.c */
#include <stdint.h>
/* 在main()之前调用的自定义初始化函数 */
void PreMainInit(void)
{
/* 示例:在main之前开启某个外设时钟 */
/* 实际使用时替换为具体寄存器操作 */
volatile uint32_t *RCC_APB2ENR = (uint32_t *)0x40021018;
*RCC_APB2ENR |= (1 << 4); /* 使能GPIOC时钟 */
}
编译与链接
使用ARM Compiler或GCC工具链编译时,需确保启动文件与C文件一同参与链接:
# 使用arm-none-eabi-gcc示例
arm-none-eabi-gcc -c startup_stm32f103xe.s -o startup.o
arm-none-eabi-gcc -c user_premain.c -o premain.o
arm-none-eabi-gcc -c main.c -o main.o
arm-none-eabi-gcc startup.o premain.o main.o -T stm32f103_flash.ld -o firmware.elf
验证方法
完成以下检查即可判定修改生效:
- 反汇编验证:使用
arm-none-eabi-objdump -d firmware.elf查看Reset_Handler的反汇编,确认调用顺序为SystemInit→PreMainInit→__main - 调试器验证:在
PreMainInit函数入口和main()入口各设断点,烧录后运行,观察程序先停在PreMainInit断点,继续运行后停在main()断点 - 向量表检查:在调试器中查看内存地址0x08000000处,确认前两个字分别为栈顶地址(0x20000400附近)和
Reset_Handler地址
若程序能依次经过上述断点且SP初始值正确,说明启动文件修改成功。