STM32启动文件剖析
1.启动文件介绍
启动文件是系统上电复位后第一个执行的程序,从启动文件的介绍可以看出,该文件主要做了5个工作:
1. 初始化堆栈指针 SP(__initial_sp)2. 初始化 PC 指针(Reset_Handler)3. 初始化中断向量表(__Vectors)4. 配置系统时钟(SystemInit)5. 调用 C 库函数 _main 初始化用户堆栈,从而最终调用 main 函数去到 C 的世界
2.堆栈空间
2.1代码解释_栈
Stack_Size EQU 0x400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
- 定义了栈的大小为0x400(十六进制)字节,即1024字节。
- 声明一个名为STACK的内存区域,NOINIT:表示该区域不需要初始化,READWRITE:表示该区域可读可写,ALIGN=3:表示该区域按2^3=8字节对齐。
- 在STACK区域中分配Stack_Size大小的空间,Stack_Mem是该空间的标签名。
__initial_sp 紧挨着 SPACE 语句放置,表示栈的结束地址,即栈顶地址(栈是由高向低生长的)。栈顶指针的初始位置标签,用于系统启动时初始化SP寄存器。
2.2代码解释_堆
Heap_Size EQU 0x200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
- 定义堆的大小为0x200(十六进制)字节,即512字节。
- 声明一个名为HEAP的内存区域,属性与STACK区域相同。
- __heap_base 表示堆的起始地址。
- 在HEAP区域中分配Heap_Size大小的空间。
- __heap_limit 表示堆的结束地址。(堆是由低向高生长的,跟栈的生长方向相反)
2.3字节对齐&指令集
PRESERVE8
THUMB
- PRESERVE8:指定当前文件的堆栈按照 8 字节对齐。
- THUMB:使用Thumb指令集,THUBM 是 ARM 以前的指令集,16bit
3.向量表
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
- 向量表在复位时会被映射到地址
0(Cortex-M 内核特性)。- 定义一个名为
RESET的只读数据段。- 导出三个符号供链接器或其他文件使用。
__Vectors:向量表的起始地址。__Vectors_End:向量表的结束地址。__Vectors_Size:向量表的大小(字节数)。
向量表从 FLASH 的 0 地址开始放置,以 4 个字节为一个单位,地址 0 存放的是栈顶地址,0x04 存放的是复位程序的地址,以此类推。从代码上看,向量表中存放的都是中断服务函数的函数名, 函数名本质上就是一个地址。
__Vectors 为向量表起始地址,__Vectors_End 为向量表结束地址,两个相减即可算出向量表大小。
4.复位程序
AREA |.text|, CODE, READONLY
定义一个名称为 .text 的代码段,属性为可读。
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
Reset_Handler:复位处理函数的标签,是 Cortex-M 向量表中的第二个条目(地址0x00000004)。PROC:表示一个子程序的开始。EXPORT:使Reset_Handler符号对其他文件(如链接器)可见。[weak]:弱定义,允许在其它文件中重新定义Reset_Handler。IMPORT:声明外部符号(类似 C 的extern)。__main:C 库的入口函数,负责初始化全局变量、堆栈等,最终调用用户的main();SystemInit:系统初始化函数。- 将
SystemInit函数的地址加载到寄存器R0。然后跳转到R0指向的地址(即调 用SystemInit),并保存返回地址到LR。- 跳转到
__main(不返回,因为__main会最终调用main())。
拓展1:汇编跳转指令B、BL、BX、BLX 和 BXJ的区别_b和blx指令的区别-CSDN博客
4.1关于__main
__main函数里做了什么?鄙人才学疏浅,暂未研究明白。我看到网上有个大佬写的很好,链接:
STM32 _main 里做了什么 - elmagnifico's blog
https://elmagnifico.tech/2017/04/01/STM32-Startup-_main/STM32_从SystemInit、__main到main() 已修正 - 蓝天上的云℡ - 博客园
https://chuna2.787528.xyz/yucloud/p/stm32_SystemInit_to_main.html
4.2关于“复位”
复位一般分为三种情况:上电复位、外部复位(复位引脚)、软件复位。不管那一种复位,其结果都是一样的,CPU被清零,然后从一个固定地址0x00000000开始执行代码(这个地址存的是Reset_Handler的入口)。
处理器上电会首先从0x00000000地址读取8字节内容,前4字节为MSP指针,确认栈的起始地址。后4字节为复位函数的地址,并将其赋给PC指针,从而确定程序下一步执行的跳转地址。通过PC指针程序跳转到Reset_Handler,再由Reset_Handler跳转执行main函数,如下图所示,其比较详细的阐述了程序是如何从FLASH启动的。
STM32可以有3种不同的启动模式,分别是从FLASH、system memory、SRAM启动。我们一般使用FLASH启动,当选择FLASH启动,处理器会将FLASH的地址重映射到0x00000000,当我们访问0x00000000地址时实际上就是访问FLASH区域的起始地址,所以当我们将bin文件烧录到0x08000000地址,程序也能沿着0x00000000地址一步步执行。(system memory、SRAM启动方式同理)。
5.中断服务函数
; Dummy Exception Handlers (infinite loops which can be modified)
NMI_Handler PROC
EXPORT NMI_Handler [WEAK]
B .
ENDP
HardFault_Handler\
PROC
EXPORT HardFault_Handler [WEAK]
B .
ENDP
MemManage_Handler\
PROC
EXPORT MemManage_Handler [WEAK]
B .
ENDP
BusFault_Handler\
PROC
EXPORT BusFault_Handler [WEAK]
B .
ENDP
UsageFault_Handler\
PROC
EXPORT UsageFault_Handler [WEAK]
B .
ENDP
SVC_Handler PROC
EXPORT SVC_Handler [WEAK]
B .
ENDP
DebugMon_Handler\
PROC
EXPORT DebugMon_Handler [WEAK]
B .
ENDP
PendSV_Handler PROC
EXPORT PendSV_Handler [WEAK]
B .
ENDP
SysTick_Handler PROC
EXPORT SysTick_Handler [WEAK]
B .
ENDP
Default_Handler PROC
EXPORT WWDG_IRQHandler [WEAK]
EXPORT PVD_IRQHandler [WEAK]
EXPORT TAMPER_IRQHandler [WEAK]
EXPORT RTC_IRQHandler [WEAK]
EXPORT FLASH_IRQHandler [WEAK]
EXPORT RCC_IRQHandler [WEAK]
EXPORT EXTI0_IRQHandler [WEAK]
EXPORT EXTI1_IRQHandler [WEAK]
EXPORT EXTI2_IRQHandler [WEAK]
EXPORT EXTI3_IRQHandler [WEAK]
EXPORT EXTI4_IRQHandler [WEAK]
EXPORT DMA1_Channel1_IRQHandler [WEAK]
EXPORT DMA1_Channel2_IRQHandler [WEAK]
EXPORT DMA1_Channel3_IRQHandler [WEAK]
EXPORT DMA1_Channel4_IRQHandler [WEAK]
EXPORT DMA1_Channel5_IRQHandler [WEAK]
EXPORT DMA1_Channel6_IRQHandler [WEAK]
EXPORT DMA1_Channel7_IRQHandler [WEAK]
EXPORT ADC1_2_IRQHandler [WEAK]
EXPORT USB_HP_CAN1_TX_IRQHandler [WEAK]
EXPORT USB_LP_CAN1_RX0_IRQHandler [WEAK]
EXPORT CAN1_RX1_IRQHandler [WEAK]
EXPORT CAN1_SCE_IRQHandler [WEAK]
EXPORT EXTI9_5_IRQHandler [WEAK]
EXPORT TIM1_BRK_IRQHandler [WEAK]
EXPORT TIM1_UP_IRQHandler [WEAK]
EXPORT TIM1_TRG_COM_IRQHandler [WEAK]
EXPORT TIM1_CC_IRQHandler [WEAK]
EXPORT TIM2_IRQHandler [WEAK]
EXPORT TIM3_IRQHandler [WEAK]
EXPORT TIM4_IRQHandler [WEAK]
EXPORT I2C1_EV_IRQHandler [WEAK]
EXPORT I2C1_ER_IRQHandler [WEAK]
EXPORT I2C2_EV_IRQHandler [WEAK]
EXPORT I2C2_ER_IRQHandler [WEAK]
EXPORT SPI1_IRQHandler [WEAK]
EXPORT SPI2_IRQHandler [WEAK]
EXPORT USART1_IRQHandler [WEAK]
EXPORT USART2_IRQHandler [WEAK]
EXPORT USART3_IRQHandler [WEAK]
EXPORT EXTI15_10_IRQHandler [WEAK]
EXPORT RTCAlarm_IRQHandler [WEAK]
EXPORT USBWakeUp_IRQHandler [WEAK]
WWDG_IRQHandler
PVD_IRQHandler
TAMPER_IRQHandler
RTC_IRQHandler
FLASH_IRQHandler
RCC_IRQHandler
EXTI0_IRQHandler
EXTI1_IRQHandler
EXTI2_IRQHandler
EXTI3_IRQHandler
EXTI4_IRQHandler
DMA1_Channel1_IRQHandler
DMA1_Channel2_IRQHandler
DMA1_Channel3_IRQHandler
DMA1_Channel4_IRQHandler
DMA1_Channel5_IRQHandler
DMA1_Channel6_IRQHandler
DMA1_Channel7_IRQHandler
ADC1_2_IRQHandler
USB_HP_CAN1_TX_IRQHandler
USB_LP_CAN1_RX0_IRQHandler
CAN1_RX1_IRQHandler
CAN1_SCE_IRQHandler
EXTI9_5_IRQHandler
TIM1_BRK_IRQHandler
TIM1_UP_IRQHandler
TIM1_TRG_COM_IRQHandler
TIM1_CC_IRQHandler
TIM2_IRQHandler
TIM3_IRQHandler
TIM4_IRQHandler
I2C1_EV_IRQHandler
I2C1_ER_IRQHandler
I2C2_EV_IRQHandler
I2C2_ER_IRQHandler
SPI1_IRQHandler
SPI2_IRQHandler
USART1_IRQHandler
USART2_IRQHandler
USART3_IRQHandler
EXTI15_10_IRQHandler
RTCAlarm_IRQHandler
USBWakeUp_IRQHandler
B .
ENDP
ALIGN
5.1代码概述
这段代码定义了ARM Cortex-M系列处理器的异常处理程序和中断服务程序(ISR)的默认实现。其主要由两部分程序组成:
- 系统异常处理程序:包括NMI、HardFault、MemManage等系统级异常处理
- 外设中断处理程序:包括各种外设(如WWDG、PVD、TIMER等)的中断处理
5.2代码结构分析
5.2.1系统异常处理程序
代码定义了以下系统异常处理程序:
NMI_Handler:不可屏蔽中断处理HardFault_Handler:硬件错误处理MemManage_Handler:内存管理错误处理BusFault_Handler:总线错误处理UsageFault_Handler:使用错误处理SVC_Handler:系统服务调用处理DebugMon_Handler:调试监视器处理PendSV_Handler:可挂起系统调用处理SysTick_Handler:系统滴答定时器处理
每个处理程序的实现都采用了相同的模式:
NMI_Handler PROC
EXPORT NMI_Handler [WEAK]
B .
ENDP
PROC/ENDP:标记处理程序的开始和结束EXPORT xxx_Handler [WEAK]:将处理程序作为弱符号导出,允许在其他文件中重新定义B .:表示无限循环(跳转到当前位置),作为默认的空实现
5.2.2外设中断处理程序
代码通过Default_Handler导出了所有外设中断处理程序,包括:
- 窗口看门狗(WWDG)中断
- 电源电压检测(PVD)中断
- 定时器(TIM)中断
- 串口(USART)中断
- I2C、SPI等通信接口中断
- 外部中断(EXTI)
- DMA中断等
这些处理程序同样被定义为弱符号,实现为无限循环。
拓展:为什么程序会卡死在B .?
答:触发了未处理的中断或异常,检查是否使能了某个中断但未重写其 handler。
6.用户堆栈初始化

6.1代码概述
这段代码处理栈和堆的初始化配置,根据编译时是否定义了__MICROLIB宏(使用MicroLIB库)来选择不同的初始化方式。
6.1.1两种初始化模式
6.1.1.1使用MicroLIB时(IF分支)
IF :DEF:__MICROLIB
EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit
当使用MicroLIB库时,代码导出三个关键符号:
__initial_sp:栈指针的初始值__heap_base:堆的起始地址__heap_limit:堆的结束地址(限制)
这种方式更简单,由MicroLIB自行管理内存分配。这个__MICROLIB宏我们在 KEIL 里面配置,然后堆栈的初始化就由 C 库函数 _main 来完成。
6.1.1.2不使用MicroLIB时(ELSE分支)
ELSE
IMPORT __use_two_region_memory
EXPORT __user_initial_stackheap
__user_initial_stackheap
LDR R0, = Heap_Mem
LDR R1, = (Stack_Mem + Stack_Size)
LDR R2, = (Heap_Mem + Heap_Size)
LDR R3, = Stack_Mem
BX LR
当不使用MicroLIB时,代码采用标准的ARM两区域内存模型:
- 导入
__use_two_region_memory符号(由C库定义)- 导出
__user_initial_stackheap函数- 函数内设置四个寄存器:
- R0:堆的起始地址(Heap_Mem)
- R1:栈顶地址(Stack_Mem + Stack_Size)
- R2:堆的结束地址(Heap_Mem + Heap_Size)
- R3:栈的起始地址(Stack_Mem)
BX LR指令返回到调用者
7.总结
STM32的启动流程主要分为三大步骤:
- 上电复位:芯片一上电或者被复位,CPU就从一个固定的地址(通常是0x00000000)开始跑代码。
- 系统初始化:设置堆栈、准备数据、配置时钟等。
- 跳转到main函数:一切就绪后,跳转到用户编写的main函数,开始执行用户的程序逻辑。
浙公网安备 33010602011771号