典型arm32位单片机启动流程(从上电到main.c)
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 源代码:
; 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
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,告诉程序临时数据存哪里。
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
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 处理器模式设置
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 异常处理函数
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
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的启动文件分析 - h3wong - 博客园
15. 启动文件详解 — [野火]STM32库开发实战指南——基于野火霸天虎开发板 文档
评论