STM32 启动文件分析
基于STM32F103VET6芯片的 startup_stm32f10x_hd.s 启动文件分析
- 设置栈,将栈的大小Stack_Size设置为0x00004900(18688/1024=18KB),即局部变量不能大于18KB。(EQU等值指令,将0x00004900地址赋给标号Stack_Size。)
- AREA 指令用于定义程序的不同区域,STACK表示栈区域,NOINIT表示该区域不需要进行初始化,READWRITE 表示该区域可读可写。ALIGN=3 表示该区域的起始地址需要按照 2^3 = 8 字节对齐。
- Stack_Mem SPACE Stack_Size ;开辟一段大小为Stack_Size的内存空间作为栈。
- __initial_sp表示程序的初始栈指针。栈通常从高地址向低地址生长。__initial_sp 定义为栈顶地址。程序启动时,编译器会使用 __initial_sp的值来初始化硬件的栈寄存器。
- 设置堆,将堆的大小Heap_Size设置为0x00002400(9216/1024=9KB),堆用于动态分配内存。
- AREA 定义HEAP堆区域,不需要初始化,可读可写,ALIGN=3 表示该区域的起始地址需要按照 2^3 = 8 字节对齐。
- __heap_base 表示堆的起始地址。
- Heap_Mem SPACE Heap_Size 开辟一段大小为Heap_Size的内存空间作为堆。
- __heap_limit 堆空间结束地址。
- PRESERVE8当前文件的堆栈按照8字节对齐,确保在运行程序时,堆栈数据能够正确地在内存中加载和存储。
- THUMB表示后面指令兼容THUMB指令。THUMB是ARM以前的指令集,16位的,而现在Cortex-M系列都使用THUMB-2指令集,是32位的。
- AREA RESET, DATA, READONLY 表示定义一个只读数据段,数据段名称为RESET。(DATA表示数据段,用于存储全局变量和静态变量等可读写的数据。READONLY存储只读的数据,如常量字符串和只读变量等。这句话表示RESET数据段在程序执行期间可读写,在程序运行期间只读。)
- EXPORT __Vectors 表示在程序中声明一个全局的标号__Vectors,该标号可在其他的文件中引用,中断向量表的入口地址。
- EXPORT __Vectors_End 表示在程序中声明一个全局的标号__Vectors_End,中断向量表的结束地址。
- EXPORT __Vectors_Size 在程序中声明一个全局的标号__Vectors_Size,中断向量表的大小。
- 建立中断向量表,DCD表示分配一个4字节的空间。首地址一定要是栈顶指针,Top of Stack,存放于FLASH中的0x8000000地址处,这部分是系统内部中断。
- DCD指令:作用是开辟一段空间,其意义等价于 C 语言中的地址符 “&” 。中断向量表的建立类似于使用C语言定义了一个指针数组,其每一个成员都是一个函数指针,分别指向各个中断服务函数。
- 这部分是外部中断。
- __Vectors_End 中断向量表的结束地址
- __Vectors_Size EQU __Vectors_End - __Vectors 中断向量表的大小
- AREA |.text|, CODE, READONLY 定义只读代码段.text
- Reset_Handler 复位中断服务程序,PROC…ENDP结构表示程序的开始和结束。
- EXPORT Reset_Handler [WEAK] 声明复位中断向量Reset_Handler为全局属性,外部文件就可以调用此复位中断服务。[WEAK]弱定义(Weak Definition),表示如果其他地方有Reset_Handler这个函数,用其他地方的Reset_Handler,如果没有,使用这里的Reset_Handler。
- IMPORT 告诉编译器要使用的标号在其他源文件中定义,IMPORT __main,IMPORT SystemInit表示要调用__main和SystemInit两个函数。
- LDR R0, =SystemInit 表示通过LDR将SystemInit地址给R0寄存区。(LDR 指令的作用是从指定的源地址读取数据,并将其加载到目标寄存器中。)
- BLX 指令的作用是将当前指令的下一条指令的地址存储到链接寄存器(LR)中,并跳转到目标地址。这里会记下下一条指令的地址放入链接寄存器中,然后跳转到SystemInit地址执行,最后跳到链接寄存器地址继续执行。(BLX还会改变当前指令 THUMB—>ARM。)
- LDR R0, =__main,将__main存储到R0,BX R0 表示跳转到刚刚R0存储值__main位置执行,并且最终会跳转到main(),进入C语言运行环境。(BX是无条件分支跳转指令,没有像BLX一样将下一条指令的地址存储到链接寄存器。跳转后就不会回来,BX也会改变当前指令THUMB—>ARM。)
- 这一段是异常处理程序,定义的都是弱函数,以NMI_Handler为例,如果程序其他地方没有NMI_Handler函数,会执行这里的函数,以确保程序的运行。
- 这里只是给出基础的函数框架,具体的实现需要手动填充。可以在这里的弱函数里填充,也可以自己定义对应函数。一般,在stm32f10x_it.c文件中,可以填充这里同名的中断服务函数。也可以注释掉stm32f10x_it.c文件中的一些中断服务函数,在想要实现的文件中自己定义实现。
- 这些是外围设备的中断处理函数,在使用对应中断事件时,需要在程序中编写处理函数来响应中断事件。
- 堆栈初始化,在上文中已经知道,中断向量表规定第一行必须是SP地址,第二行是复位中断入口地址,上电后,CPU首先就会读这两个值。上电时已经初始了SP,为什么这里又初始化堆栈?因为上电只是将_initial_sp值存入了SP寄存器,这只是一个栈顶指针,但堆的大小和栈的大小并没有初始化,所以在_main中要把在启动文件中定义好的值传给C库进行堆栈大小的初始化。
- IF :DEF:__MICROLIB 如果使用Micro LIB微库(在keil的Target中可选择),将栈顶指针、堆起始地址、堆结束地址赋予全局属性供外部程序调用。
- IMPORT __use_two_region_memory 定义全局标号
- EXPORT __user_initial_stackheap 声明全局标号
- __user_initial_stackheap 表示用户堆栈初始化程序入口
- LDR R0, = Heap_Mem 加载堆的起始地址到寄存器 R0,
- LDR R1, =(Stack_Mem + Stack_Size) 加载栈的结束地址到R1,
- LDR R2, = (Heap_Mem + Heap_Size) 加载堆的结束地址到R2,
- LDR R3, = Stack_Mem 加载栈的起始地址到寄存器 R3,
- BX LR 返回到调用这个子程序的位置。
- ALIGN用于在代码中插入一个对齐标记,以确保后续代码的对齐。