STM32F103XX 04-启动过程
前三篇作为理论基础,这一篇重点看一下在f103中芯片是如何启动的,执行到main函数之前都做了什么工作。
代码编译
编译器的选择
在KeilV5软件中存在两种编译器,分别是基于 Clang/LLVM的ARM Compiler 6和传统的ARM Compiler 5。两种方式可以在下面选项中选择。
如上图所示,选择的ARM V5,对应到Keil安装目录下则是以下几个程序
其中armar是用来管理静态库的,armasm是汇编器,armcc是编译器,armlink是链接器,fromelf是ARM ELF文件转换器。
在项目的编译输出日志中可以找到我们选择的编译器信息。
也可以从项目的生成的batch文件中找到对应的信息
SET PATH=D:\Programs\Keil_v5\ARM\ARMCC\Bin;
SET CPU_TYPE=STM32F103ZE
SET CPU_VENDOR=STMicroelectronics
SET UV2_TARGET=01-FreeRTOS
SET CPU_CLOCK=0x007A1200
编译参数设置
使用Keil软件开发f103单片机,在Keil软件中对工程的编译已经帮我们实现了,下面是对应的编译指令,写在C/C++中。
--c99 -c --cpu Cortex-M3 -g -O3 --apcs=interwork --split_sections
-I ../Core/Inc
-I ../Drivers/STM32F1xx_HAL_Driver/Inc/Legacy
-I ../Drivers/STM32F1xx_HAL_Driver/Inc
-I ../Middlewares/Third_Party/FreeRTOS/Source/include
-I ../Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2
-I ../Middlewares/Third_Party/FreeRTOS/Source/portable/RVDS/ARM_CM3
-I ../Drivers/CMSIS/Device/ST/STM32F1xx/Include
-I ../Drivers/CMSIS/Include
-I ./RTE/_01-FreeRTOS
-I D:/Document/Arm/Packs/ARM/CMSIS/5.7.0/CMSIS/Core/Include
-I D:/Document/Arm/Packs/Keil/STM32F1xx_DFP/2.4.0/Device/Include
-D __UVISION_VERSION="534" -D _RTE_ -D STM32F10X_HD -D USE_HAL_DRIVER -D STM32F103xE
-o 01-FreeRTOS\*.o --omf_browse 01-FreeRTOS\*.crf --depend 01-FreeRTOS\*.d
- -c 仅编译不链接
- –cpu 目标CPU
- –apcs=interwork 代表支持ARM/Thumb指令集混合调用;
- –split_sections代表分段编译;
- -I代表包含的头文件路径;
- -D代表预处理过程中使用到的宏;
- -o 01-FreeRTOS*.o 代表生成一系列的二进制文件
- –omf_browse代表生成浏览信息文件,用于后续调试分析;
- –depend 01-FreeRTOS*.d代表二进制.o文件具体由什么文件组成;
在每一个生成的.i文件中都包含对应的编译参数信息。
--c99 -c --cpu Cortex-M3 -g -O3 --apcs=interwork --split_sections -I ../Core/Inc -I ../Drivers/STM32F1xx_HAL_Driver/Inc/Legacy -I ../Drivers/STM32F1xx_HAL_Driver/Inc -I ../Middlewares/Third_Party/FreeRTOS/Source/include -I ../Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2 -I ../Middlewares/Third_Party/FreeRTOS/Source/portable/RVDS/ARM_CM3 -I ../Drivers/CMSIS/Device/ST/STM32F1xx/Include -I ../Drivers/CMSIS/Include
-I.\RTE\_01-FreeRTOS
-ID:\Document\Arm\Packs\ARM\CMSIS\5.7.0\CMSIS\Core\Include
-ID:\Document\Arm\Packs\Keil\STM32F1xx_DFP\2.4.0\Device\Include
-D__UVISION_VERSION="534" -D_RTE_ -DSTM32F10X_HD -D_RTE_ -DUSE_HAL_DRIVER -DSTM32F103xE
-o 01-freertos\main.o --omf_browse 01-freertos\main.crf --depend 01-freertos\main.d "../Core/Src/main.c"
汇编参数设置
在Options的窗口Asm选项下可以设置汇编器用到的参数。
--cpu Cortex-M3 -g --apcs=interwork
-I ..\Core\Inc
-I .\RTE\_01-FreeRTOS
-I D:\Document\Arm\Packs\ARM\CMSIS\5.7.0\CMSIS\Core\Include
-I D:\Document\Arm\Packs\Keil\STM32F1xx_DFP\2.4.0\Device\Include
--pd "__UVISION_VERSION SETA 534" --pd "_RTE_ SETA 1" --pd "STM32F10X_HD SETA 1" --pd "_RTE_ SETA 1"
--list "*.lst"
--xref -o "*.o"
--depend "*.d"
- –pd代表的预定义;
- –list “*.lst” 生成汇编列表文件;
- –xref -o “*.o” 生成交叉引用文件;
- –depend “*.d” 生成依赖文件
链接参数设置
在Options的窗口Linker选项下可以设置链接器用到的参数。
--cpu Cortex-M3 *.o
--strict --scatter "01-FreeRTOS\01-FreeRTOS.sct"
--summary_stderr --info summarysizes --map --xref --callgraph --symbols
--info sizes --info totals --info unused --info veneers
--list "01-FreeRTOS.map"
-o 01-FreeRTOS\01-FreeRTOS.axf
-
–strict启用严格检查模式;
-
–scatter 使用指定的分散加载描述文件\01-FreeRTOS.sct;
LR_IROM1 0x08000000 0x00080000 { ; load region size_region 加载区域 ER_IROM1 0x08000000 0x00080000 { ; load address = execution address 执行区域1 *.o (RESET, +First) ;所有.o文件,并强制将RESET段放在首位 *(InRoot$$Sections) ;表示ARM C库的根区,包括_main,_scatterload,_rt_entry .ANY (+RO) ;只读代码和数据 .ANY (+XO) ;可执行代码 } RW_IRAM1 0x20000000 0x00010000 { ; RW data 执行区域2 对应内部SRAM,大小为64KB .ANY (+RW +ZI) ;可读写的数据和零初始化数据 } }-
存储固件的区域地址为0x08000000,对应f103内部的flash模块地址,大小为512KB;
-
RESET段的定义为中断向量表,在F103中一共有76项中断向量表,每一项占4个字节,RESET字段大小为0x130字节;
AREA RESET, DATA, READONLY EXPORT __Vectors EXPORT __Vectors_End EXPORT __Vectors_Size __Vectors DCD __initial_sp ; Top of Stack .... DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5 __Vectors_End -
ANY (+RW) 包括已经初始化的全局变量.data段 + 静态变量;
-
ANY (+ZI)包括微初始化的全局变量.bss段 + 系统自动清零;
-
根据工程01-FreeRTOS实际编译后,FLASH和RAM中的存储情况如下图所示。

-
-
–summary_stderr将摘要信息输出到标准错误(stderr);
-
–list “01-FreeRTOS.map” 生成详细的映射文件,文件名称为01-FreeRTOS.map;
-
map文件中包括内存布局、符号地址、段大小等信息;
============================================================================== Section Cross References 交叉引用表 startup_stm32f103xe.o(STACK) refers (Special) to heapauxi.o(.text) for __use_two_region_memory ... ============================================================================== Removing Unused input sections from the image. 移除未使用的段 Removing main.o(.rev16_text), (4 bytes) ... Removing port.o(i.vPortEndScheduler), (32 bytes). 516 unused section(s) (total 36456 bytes) removed from the image. ============================================================================== Image Symbol Table 符号表 Local Symbols Symbol Name Value Ov Type Size Object(Section) ../Core/Src/freertos.c 0x00000000 Number 0 freertos.o ABSOLUTE ... .text 0x08000484 Section 0 exit.o(.text) Global Symbols Symbol Name Value Ov Type Size Object(Section) __main 0x08000131 Thumb Code 8 __main.o(!!!main) ... ============================================================================== Memory Map of the image 内存映射表 Image Entry point : 0x08000131 Load Region LR_IROM1 (Base: 0x08000000, Size: 0x000029dc, Max: 0x00080000, ABSOLUTE) Execution Region ER_IROM1 (Exec base: 0x08000000, Load base: 0x08000000, Size: 0x00002950, Max: 0x00080000, ABSOLUTE) Exec Addr Load Addr Size Type Attr Idx E Section Name Object 0x08000000 0x08000000 0x00000130 Data RO 3 RESET startup_stm32f103xe.o ... Execution Region RW_IRAM1 (Exec base: 0x20000000, Load base: 0x08002950, Size: 0x00002008, Max: 0x00010000, ABSOLUTE) Exec Addr Load Addr Size Type Attr Idx E Section Name Object 0x20000000 0x08002950 0x00000004 Data RW 198 .data freertos.o ... ============================================================================== Image component sizes 各个部分的大小和总计大小 Code (inc. data) RO Data RW Data ZI Data Debug Object Name 380 32 0 4 1720 14358 cmsis_os2.o ... ============================================================================== Total RO Size (Code + RO Data) 10576 ( 10.33kB) Total RW Size (RW Data + ZI Data) 8200 ( 8.01kB) Total ROM Size (Code + RO Data + RW Data) 10716 ( 10.46kB) ============================================================================== -
-o 01-FreeRTOS\01-FreeRTOS.axf 指定输出文件,文件的名称为\01-FreeRTOS.axf;
-
–info summarysizes输出各段大小的摘要;–info sizes --info totals输出每个段的详细大小和总计信息;–info unused显示未使用的段和函数;–info veneers显示veneers信息;
-
–map生成详细的内存映射表,具体参见上述map表;
-
–xref生成交叉引用表;
-
–callgraph生成函数调用图;
-
–symbols输出符号表;
编译过程及输出文件

整个编译过程涉及三类文件,第一类是c源文件,位于各个目录下,第二类是汇编文件startup_stm32f103xe.s,第三类是编译器所携带的库文件,在这个项目中对应的文件为c_w.l,位于Keil IDE的安装目录下。首先对源文件和汇编文件进行编译,生成对应的.o文件,而c_w.l文件中已经存放好编译成功的.o文件;目标.o文件准备好后,进行链接操作,将相同的段组合在一起,分析函数之间的调用关系,最后使用fromelf文件将映像文件转换成二进制文件。
具体的编译过程
以main.c文件为例,分析源文件的编译过程。
-
预处理:预处理包括宏和头文件展开等,预处理后会生成.i文件;
main._i文件内容:
--c99 -c --cpu Cortex-M3 -g -O3 --apcs=interwork --split_sections -I ../Core/Inc -I ../Drivers/STM32F1xx_HAL_Driver/Inc/Legacy -I ../Drivers/STM32F1xx_HAL_Driver/Inc -I ../Middlewares/Third_Party/FreeRTOS/Source/include -I ../Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2 -I ../Middlewares/Third_Party/FreeRTOS/Source/portable/RVDS/ARM_CM3 -I ../Drivers/CMSIS/Device/ST/STM32F1xx/Include -I ../Drivers/CMSIS/Include -I.\RTE\_01-FreeRTOS -ID:\Document\Arm\Packs\ARM\CMSIS\5.7.0\CMSIS\Core\Include -ID:\Document\Arm\Packs\Keil\STM32F1xx_DFP\2.4.0\Device\Include -D__UVISION_VERSION="534" -D_RTE_ -DSTM32F10X_HD -D_RTE_ -DUSE_HAL_DRIVER -DSTM32F103xE -o 01-freertos\main.o --omf_browse 01-freertos\main.crf --depend 01-freertos\main.d "../Core/Src/main.c" -
编译:使用编译器对.i文件进行编译,编译后会生成.o文件、.crf文件、.d文件
- 其中.crf文件为交叉引用文件,包含了浏览信息;
- .d文件描述了.o文件所对应的依赖;
"D:\Programs\Keil_v5\ARM\ARMCC\Bin\ArmCC" --Via "01-freertos\main.__i"
-
链接:使用链接器将.o文件链接
"D:\Programs\Keil_v5\ARM\ARMCC\Bin\ArmLink" --Via "01-FreeRTOS\01-FreeRTOS.lnp"-
01-FreeRTOS\01-FreeRTOS.lnp文件是链接器的参数
--cpu Cortex-M3 "01-freertos\startup_stm32f103xe.o" "01-freertos\main.o" ... "01-freertos\port.o" --strict --scatter "01-FreeRTOS\01-FreeRTOS.sct" --summary_stderr --info summarysizes --map --load_addr_map_info --xref --callgraph --symbols --info sizes --info totals --info unused --info veneers --list "01-FreeRTOS.map" -o 01-FreeRTOS\01-FreeRTOS.axf
-
-
生成二进制
"D:\Programs\Keil_v5\ARM\ARMCC\Bin\fromelf.exe" "01-FreeRTOS\01-FreeRTOS.axf" --i32combined --output "01-FreeRTOS\01-FreeRTOS.hex"
输出文件类型

上述图片来源于https://blog.csdn.net/m0_60633015/article/details/143427274
启动过程
参考stm32的手册,在芯片启动的过程中首先会从地址0x00000000获取栈的地址,然后从0x00000004地址处开始区址执行。可以根据BOOT0/1引脚决定将什么存储区域映射到地址0x00000000,一般常见的启动方式是从FLASH启动,此时BOOT0/1都处于接地的状态,地址0x08000000映射到地址0x00000000。
早期行为

-
为SP赋值,将0x00000000处的数值写入SP寄存器,SP=0x20002008;
-
将0x00000004地址处的内容写入PC寄存器,PC=0x08000264;
-
跳转至PC所指的地址空间执行,也就是执行复位的中断服务函数;

Reset_Handler函数
SystemInit
-
地址:0x080010c6(地址依据具体环境)
-
函数内容
void SystemInit (void) { #if defined(STM32F100xE) || defined(STM32F101xE) || defined(STM32F101xG) || defined(STM32F103xE) || defined(STM32F103xG) #ifdef DATA_IN_ExtSRAM SystemInit_ExtMemCtl(); #endif /* DATA_IN_ExtSRAM */ #endif /* 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 */ } -
函数功能
如果数组在外部存储中,对外部存储器进行初始化,如果需要配置向量表的位置,则按照要求进行配置;
_main函数
-
地址:0x08000130(地址依据具体环境)
-
函数内容
0x08000130 F000F802 BL.W 0x08000138 __scatterload 0x08000134 F000F83A BL.W 0x080001AC __rt_entry 0x08000138 A00A ADR r0,{pc}+0x2C ; @0x08000164 0x0800013A E8900C00 LDM r0,{r10-r11} 0x0800013E 4482 ADD r10,r10,r0 0x08000140 4483 ADD r11,r11,r0 0x08000142 F1AA0701 SUB r7,r10,#0x01 0x08000146 45DA CMP r10,r11 0x08000148 D101 BNE 0x0800014E 0x0800014A F000F82F BL.W 0x080001AC __rt_entry 0x0800014E F2AF0E09 ADR.W lr,{pc}-0x07 ; @0x08000147 0x08000152 E8BA000F LDM r10!,{r0-r3} 0x08000156 F0130F01 TST r3,#0x01 0x0800015A BF18 IT NE 0x0800015C 1AFB SUBS r3,r7,r3这部分内容保存在c_w.l二进制的库中,ARM公司不对外公开,所以只能根据汇编文件大致分析其功能;首先是调用scatterload函数,地址为0x08000138,然后调用rt_entry函数,地址为0x080001AC。
_scatterload
负责将程序所需的代码和数据搬运到它们在内存中的正确位置,并为未初始化的变量清零,为后续运行准备好一个干净、正确的内存环境。
来自于《ARM Compiler C Library Startup and Initialization》文档的描述:

伪代码表示,来自于deepseek:
void __scatterload(void) { // 初始化跳转表 void *handler1 = (void*)(0x08000164 + 0x27CC); void *handler2 = (void*)(0x08000164 + 0x27EC); void *current_handler = handler1; // 遍历区域表 while (current_handler != handler2) { // 从区域表加载一个条目 struct RegionTableEntry entry = *current_handler; current_handler += sizeof(entry); // 准备跳转参数 void *func = entry.handler; if ((uint32_t)func & 1) { // 相对地址处理 func = (void*)((uint32_t)handler1 - 1 - (uint32_t)func); func = (void*)((uint32_t)func | 1); } // 调用处理函数 func(entry.load_addr, entry.exec_addr, entry.size); } // 所有区域处理完成,进入运行时 __rt_entry(); }- 区域表定义
struct RegionTableEntry { uint32_t load_addr; // r0: 源地址(加载视图) uint32_t exec_addr; // r1: 目标地址(执行视图) uint32_t size; // r2: 大小 uint32_t handler; // r3: 处理函数指针 + 标志位 };-
区域表实例
Load Address Execution Address Execution Size Handler Address 0x08002950 0x20000000 0x0000008c 0x0800016c(handler_copy) 0x080029DC 0x2000008c 0x00001f7c 0x08000188(handler_zi) -
在区域表中定义了两个handler函数,一个是handler_copy,对应_scatterload_copy函数,另外一个是handler_zi,对应__scatterload_zeroinit。
-
handler_copy 实现将存储在flash地址处0x08002950位置的可读可写Data数据拷贝至RAM地址0x20000000
//汇编函数 0x0800016C 3A10 SUBS r2,r2,#0x10 0x0800016E BF24 ITT CS 0x08000170 C878 LDM r0!,{r3-r6} 0x08000172 C178 STM r1!,{r3-r6}在map文件中也可以佐证这一点。

例如在地址0x20000010的数据来自于0x08002960,该地址存储的是SystemCoreClock变量的数值,在Mememory表中查找这两个地址的内容,数据相同,均为0x007A0012

-
__scatterload_zeroinit函数清零未初始化的全局/静态变量 (ZI 数据),对应的汇编代码如下:
0x08000188 2300 MOVS r3,#0x00 0x0800018A 2400 MOVS r4,#0x00 0x0800018C 2500 MOVS r5,#0x00 0x0800018E 2600 MOVS r6,#0x00 0x08000190 3A10 SUBS r2,r2,#0x10 0x08000192 BF28 IT CS 0x08000194 C178 STM r1!,{r3-r6} 0x08000196 D8FB BHI 0x08000190在map文件中同样可以佐证这一点

实际内存区域的数据均为0.

-
rt_entry函数:负责设置堆和栈,负责初始化用到的库,调用真正的main函数。
0x080001AC F000F945 BL.W 0x0800043A __user_setup_stackheap 0x080001B2 F7FFFFF7 BL.W 0x080001A4 __rt_lib_init 0x080001B6 F000FFE4 BL.W 0x08001182 main 0x080001BA F000F963 BL.W 0x08000484 exit1. __user_setup_stackheap函数:
0x0800043C F000F82C BL.W 0x08000498 __user_libspace 0x08000452 F7FFFF15 BL.W 0x08000280 __user_initial_stackheap__user_initial_stackheap函数定义在stm32f103xe.s中
__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 ALIGN ENDIF END2. __rt_lib_init函数,负责初始化程序所引用的所有标准 C/C++ 库功能,为执行用户
main()函数准备好完整的运行时环境。R0 R1会作为入口参数,传递至__rt_lib_init。1. _fp_init //初始化浮点环境 2. _init_alloc //初始化堆操作的函数使用的数据结构,会使用R0/R1的数值 3. _rand_init //初始化随机数生成器 4. _get_lc_collate //获取指向包含 LC_COLLATE 语言环境类别设置的默认数据块的指针 5. _get_lc_ctype //获取指向包含 LC_CTYPE 语言环境类别设置的默认数据块的指针 6. _get_lc_monetary //获取指向包含 LC_MONETARY 语言环境类别设置的默认数据块的指针 7. _get_lc_numeric //获取指向包含 LC_NUMERIC 语言环境类别设置的默认数据块的指针 8. _get_lc_time //获取指向包含 LC_TIME 语言环境类别设置的默认数据块的指针 9. _atexit_init //为传递给 atexit() 的函数指针设置 C 库的存储 10. _signal_init //设置包含每个信号编号的当前处理程序的存储 11. _fp_trap_init //设置库的存储 12. _clock_init //读取clock() 使用的定时器的当前值 13. _getenv_init //检索任何需要的数据 14. _initio //设置 stdio 内部状态 15. _ARM_get_argv //获取传递给 main() 的 argc 和 argv 值 16. _alloca_initialize //将 alloca 列表指针设置为 NULL 17. _ARM_exceptions_init//设置 C++ 异常处理状态 18. __cpp_initialize__aeabi_ //调用顶级 C++ 对象的构造函数上述函数具体的每一项请参考《ARM C and C++ Libraries and Floating-Point Support Reference》文档。
3.main函数
真正的main函数。
问题:栈和堆地址如何来的?
-
在启动文件中进行地址区域大小
Stack_Size EQU 0x400 AREA STACK, NOINIT, READWRITE, ALIGN=3 //声明栈对应的段,不初始化,可读可写 Stack_Mem SPACE Stack_Size //在栈对应的段中开辟了大小为0x400的区域,命名为Stack_Mem __initial_sp //标号,代码栈顶 ; <h> Heap Configuration ; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8> ; </h> Heap_Size EQU 0x200 AREA HEAP, NOINIT, READWRITE, ALIGN=3 //声明堆对应的段,不初始化,可读可写 __heap_base Heap_Mem SPACE Heap_Size //在堆对应的段中开辟了大小为0x200的区域,命名为Stack_Mem __heap_limit -
在链接时,链接器会根据sct文件,将堆安排在bss段之后,栈安排在堆之后,同时计算出__initial_sp对应的具体数值, 在map文件中也有体现;

小结
在启动至mian函数的整个过程如下图所示,首先是执行reset_handler函数,如果有MMU/MPU则要在调用_main之前配置完成,接下来是执行C库中的函数,完成代码的拷贝,bss段的清理后调用rt_entry函数。在rt_entry函数中传入堆和栈的信息,初始化C库,调用真正的main函数,最后main函数退出后执行exit。
参考
- https://zhuanlan.zhihu.com/p/610556139
- https://zhuanlan.zhihu.com/p/1897039056765969381
- AN241 - ARM Compiler C Library Startup and Initialization
- https://developer.arm.com/documentation/dai0241/latest/
- ARM Compiler Software Development Guide
- https://developer.arm.com/documentation/dui0471/latest
- ARM Compiler toolchain ARM C and C++ Libraries and Floating-Point Support Reference
- https://developer.arm.com/documentation/dui0492/i/the-c-and-c—libraries
- ARM Compiler toolchain Linker Reference
- https://developer.arm.com/documentation/dui0493/latest/
- ARM Compiler ARM C and C++ Libraries and Floating-Point Support User Guide
65969381- https://developer.arm.com/documentation/dui0475/latest/
更多推荐



所有评论(0)