0 启动流程
- 复位
- 通过boot引脚选择启动模式
- 从地址0x00000000读取 __initial_sp 初始栈顶值到MSP
- 从地址0x00000004读取 Reset_Handler 地址到PC
- 32位系统, 字长4字节
- 指向复位向量的内容
- 执行SystemInit函数, 初始化系统时钟
- 执行__main, 软件设置SP指针, 初始化栈空间, 清除bss段, 最后跳转到mian函数
1 启动模式
BOOT0 | BOOT1 | 启动模式 |
---|---|---|
0 | X | 从FLASH启动0x0800 0000 |
1 | 0 | 从系统存储器启动(bootROM) |
1 | 1 | 从RAM启动0x2000 0000 |
- pc指针在硬件复位后会自动指向0x0000 0000, 而Flash实际起始地址:0x0800 0000, RAM实际起始地址: 0x2000 0000, 硬件会自动将0x0000 0000映射到对应的地址
2 启动流程
2.1 Reset_Handler 复位向量
2.1.1 Reset_Handler 源代码:
c
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
2.1.2 代码分析:
导入 SystemInit 和 __main 两个符号,并LDR数据到R0寄存器,跳转执行(BLX/BX)
2.2 SystemInit
2.2.1 SystemInit源代码:
2.2.1.1HAL库中的SystemInit
c
void SystemInit(void)
{
/* FPU settings ------------------------------------------------------------*/
#if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); /* set CP10 and CP11 Full Access */
#endif
#if defined (DATA_IN_ExtSRAM) || defined (DATA_IN_ExtSDRAM)
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */
/* Configure the Vector Table location -------------------------------------*/
#if defined(USER_VECT_TAB_ADDRESS)
SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
#endif /* USER_VECT_TAB_ADDRESS */
}
2.2.1.2 STD库中的SystemInit
2.2.2 代码分析:
- HAL库中的SystemInit: 根据宏配置初始化FPU, 外部SRAM, 中断向量表(根据用户设置的地址)
- STD库中的SystemInit: 根据宏配置初始化FPU, 外部SRAM, 初始化PLL, 设置系统时钟, 根据宏设置中断向量表地址(只能配置FLASH_BASE和SRAM_BASE)
2.3 __main
2.3.1 代码分析:
1. _初始化RW段和ZI段
- RW:程序中已经初始化的变量所占空间
- ZI:未初始化的static和全局变量以及堆栈所占的空间
2. 调用__rt_entry()函数
1. __user_initial_stackheap 是 标准C库初始化时自动调用的函数,主要用于向C库传递堆(Heap)和栈(Stack)的地址信息。它的执行时机会在复位处理函数跳转到 __main 后,在 C库初始化阶段自动触发
2. 初始化堆栈
3. 初始化库函数
4. 最后跳转到main函数
3 进入mian函数
4 启动文件分析
4.1 __initial_sp
栈顶地址,复位时 CPU 会把这个值加载到堆栈指针 SP,告诉程序临时数据存哪里。
c
Stack_Size EQU 0x800 ; 定义栈大小为 2KB (2048 bytes)
AREA STACK, NOINIT, READWRITE, ALIGN=3 ; 定义一个名为 STACK 的内存区域
Stack_Mem SPACE Stack_Size ; 分配连续 2KB 的栈内存空间
__initial_sp ; 声明栈顶符号
4.2 __heap_limit
c
Heap_Size EQU 0x400 ; 定义堆大小为 1KB (1024 bytes)
AREA HEAP, NOINIT, READWRITE, ALIGN=3 ; 定义可读写、8字节对齐的HEAP段
__heap_base ; 堆起始地址符号(链接器识别)
Heap_Mem SPACE Heap_Size ; 分配连续 1KB 堆内存空间
__heap_limit ; 堆结束地址符号(Heap_Mem + Heap_Size)
4.3 处理器模式设置
c
PRESERVE8 ; 要求堆栈8字节对齐(兼容Cortex-M系列)
THUMB ; 指定使用Thumb指令集
4.4 中断向量表
AREA RESET, DATA, READONLY ; 定义只读数据段
EXPORT __Vectors ; 导出向量表起始地址(用于链接脚本)
EXPORT __Vectors_End
EXPORT __Vectors_Size__Vectors
DCD __initial_sp ; 地址0: 主栈顶地址(硬件自动加载到MSP)
DCD Reset_Handler ; 地址4: 复位处理函数(程序入口)
; ▼ 内核异常向量 ▼
DCD NMI_Handler ; NMI
DCD HardFault_Handler ; 硬件错误
DCD MemManage_Handler ; 内存管理错误
DCD BusFault_Handler ; 总线错误
DCD UsageFault_Handler ; 用法错误
DCD 0 ; 保留
DCD 0
DCD 0
DCD 0
DCD SVC_Handler ; 系统调用
DCD DebugMon_Handler ; 调试监控
DCD 0
DCD PendSV_Handler ; 可挂起系统调用
DCD SysTick_Handler ; 系统节拍定时器
; ▼ 外设中断向量 ▼
DCD WWDG_IRQHandler ; 窗口看门狗
DCD PVD_IRQHandler ; 电源电压检测
DCD TAMP_STAMP_IRQHandler ; 入侵检测和时间戳 ;
...(后续DCD均为具体外设的中断入口)
__Vectors_End ; 向量表结束标识
__Vectors_Size EQU __Vectors_End - __Vectors ; 计算向量表大小
4.5 Reset_Handler
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
4.6 异常处理函数
c
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 TAMP_STAMP_IRQHandler [WEAK]
EXPORT RTC_WKUP_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_Stream0_IRQHandler [WEAK]
EXPORT DMA1_Stream1_IRQHandler [WEAK]
EXPORT DMA1_Stream2_IRQHandler [WEAK]
EXPORT DMA1_Stream3_IRQHandler [WEAK]
EXPORT DMA1_Stream4_IRQHandler [WEAK]
EXPORT DMA1_Stream5_IRQHandler [WEAK]
EXPORT DMA1_Stream6_IRQHandler [WEAK]
EXPORT ADC_IRQHandler [WEAK]
EXPORT EXTI9_5_IRQHandler [WEAK]
EXPORT TIM1_BRK_TIM9_IRQHandler [WEAK]
EXPORT TIM1_UP_TIM10_IRQHandler [WEAK]
EXPORT TIM1_TRG_COM_TIM11_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 EXTI15_10_IRQHandler [WEAK]
EXPORT RTC_Alarm_IRQHandler [WEAK]
EXPORT OTG_FS_WKUP_IRQHandler [WEAK]
EXPORT DMA1_Stream7_IRQHandler [WEAK]
EXPORT SDIO_IRQHandler [WEAK]
EXPORT TIM5_IRQHandler [WEAK]
EXPORT SPI3_IRQHandler [WEAK]
EXPORT DMA2_Stream0_IRQHandler [WEAK]
EXPORT DMA2_Stream1_IRQHandler [WEAK]
EXPORT DMA2_Stream2_IRQHandler [WEAK]
EXPORT DMA2_Stream3_IRQHandler [WEAK]
EXPORT DMA2_Stream4_IRQHandler [WEAK]
EXPORT OTG_FS_IRQHandler [WEAK]
EXPORT DMA2_Stream5_IRQHandler [WEAK]
EXPORT DMA2_Stream6_IRQHandler [WEAK]
EXPORT DMA2_Stream7_IRQHandler [WEAK]
EXPORT USART6_IRQHandler [WEAK]
EXPORT I2C3_EV_IRQHandler [WEAK]
EXPORT I2C3_ER_IRQHandler [WEAK]
EXPORT FPU_IRQHandler [WEAK]
EXPORT SPI4_IRQHandler [WEAK]
EXPORT SPI5_IRQHandler [WEAK]
4.7 __user_initial_stackheap
c
IF :DEF:__MICROLIB ; 当使用微库时的路径
; ▼ 直接导出符号供微库使用 ▼
EXPORT __initial_sp ; 导出初始栈顶地址(MICROLIB需要)
EXPORT __heap_base ; 导出堆起始地址
EXPORT __heap_limit ; 导出堆结束地址
ELSE ; 使用标准C库时的路径
; ▼ 导出动态堆栈初始化函数 ▼
IMPORT __use_two_region_memory ; 声明C库内存模型
EXPORT __user_initial_stackheap ; 导出初始化函数
__user_initial_stackheap ; 堆栈初始化函数(由C库调用)
LDR R0, =Heap_Mem ; R0 = 堆起始地址(用于malloc)
LDR R1, =(Stack_Mem + Stack_Size) ; R1 = 栈顶地址
LDR R2, = (Heap_Mem + Heap_Size) ; R2 = 堆结束地址
LDR R3, =Stack_Mem ; R3 = 栈底地址(监测栈溢出)
BX LR ; 返回调用者(C库)
ENDIF END ; 文件结束
参考文档
[深入剖析STM32]STM32 启动流程详解
stm32的启动文件详解 Reset_Handler做了什么工作 疑问--初始化pc指针的操作在哪里 ---硬件设置SP 和 PC的值_reset handler-CSDN博客
STM32启动代码分析及其汇编学习-ARM - 蓝天上的云℡ - 博客园
STM32_从SystemInit、__main到main() 已修正 - 蓝天上的云℡ - 博客园
STM32启动过程详解-CSDN博客
关于ARM CM3的启动文件分析 - strongwong - 博客园
15. 启动文件详解 --- [野火]STM32库开发实战指南------基于野火霸天虎开发板 文档