STM32启动文件分析
- 前言
- 启动文件解析
-
- 1. 堆和栈的初始化
- 2. 初始化中断向量表
- 3. 复位中断函数
- 4. 其余中断函数
- 5. 堆和栈的初始化
- 启动文件用到的ARM指令表
前言
- STM32的启动文件是用汇编编写的,以.S作为文件后缀。
- 对于同系列不同型号的SOC进行工程适配时,一般只需要更改SOC型号、SOC宏定义及启动文件即可。
启动文件解析
1. 堆和栈的初始化
伪指令 EQU 作用是将一个确定的数值赋给一个变量名。
伪指令 AREA 用于定义数据和代码段。
伪指令 SPACE 用于分配一段连续的内存空间。
所以这一部分的操作就是:
- 定义 堆/栈 的大小,分别为 Stack_Size 和 Heap_Size。
- 定义 堆/栈 段,不初始化,可读可写,以8字节对齐
- 为 堆/栈 段分配之前所定义大小的连续内存空间,分别命名为 Heap_Mem 和 Stack_Mem。
- 紧跟其后的 __initial_sp 用来表示栈顶地址,而 __heap_base 用来表示堆的起始地址,__heap_limit 用来表示堆的结束地址
- 栈为向下生长,堆为向上生长,所以需要决定的是栈顶地址、堆的起始和结束地址。
- 栈的作用主要是局部变量,函数调用,函数形参等的开销,栈的大小不能超过内部SRAM的大小。如果编写的程序规模比较大,定义的局部变量很多,那么就对应需要增加栈的大小,否则会产生内存溢出导致硬件错误。
- 堆主要用来动态内存的分配,像malloc()函数申请的内存就在堆里,在STM32中使用较少。
2. 初始化中断向量表
- 首先定义一个 数据段,段名为 RESET ,只读 ,用于存放接下来这段代码
- 通过 EXPORT 指令引出 __Vectors,__Vectors_End,__Vectors_Size 三个标号供外部使用,这三个标号分别表示中断向量表的 起始地址、结束地址和规模大小
- 开始定义中断向量表,__Vectors 标号记录向量表的起始地址。
- 通过 DCD 指令(申请一段连续的字储存空间并分配初始值)来初始化中断向量表。
中断向量表里存放的其实就是一个个中断服务的函数地址。中断向量表里的第一个存放的是SP(堆栈指针)地址,第二个是复位程序的地址。ARM规定程序上电后,先加载SP和PC:从0地址处加载SP,从偏移为4的地址加载PC。
也就是说上电之后通过第一条向量加载SP,通过第二条向量加载PC,而第二条向量指向复位程序 Reset_Handler ,于是系统通过复位程序之后进入用户程序。
__Vectors_End 表示中断向量表的结束地址
__Vectors_Size 通过计算得出:结束地址-起始地址
3. 复位中断函数
复位中断函数最主要由两部分组成:
1. 系统初始化
2. 运行环境初始化并进入用户函数
依旧是通过 AREA 关键词先定义一个代码段,只读,名为 “.text”。
然后使用两个 IMPORT 关键词导入 “SystemInit” 和 “__main” 两个标号。
- “SystemInit” 是官方编写的系统初始化函数,主要作用时初始化时钟设置,
- “__main” 是C库函数,主要是初始化C运行环境,并最终引导跳转到用户函数 “main()”,
跳转 SystemInit 时使用的是带返回的跳转指令 BLX ,而跳转 “__main” 时则是不带返回的跳转指令 BX ,因为最终会引导进入用户函数 “main()”。
4. 其余中断函数
这些函数大多数都是空的,并且通过 [WEAK] 关键词修饰,当外部定义了该标号则优先使用外部的标号。
子程序内部只有一条 “B .” 跳转指令,这条指令等同于 “while(1)”,也就是会让程序在此死循环。
系统中断一一列出,除了复位函数之外,都是这样的空函数。
而外部中断函数则是统一列在 Default_Handler 之中。而在末尾也是同样的 “B .” 跳转指令。
这些中断服务函数的使用方法都是通过 [WEAK] 关键字来进行:在外部使用相同标号编写函数之后,触发中断时就会直接跳到外部自己编写的中断服务函数之中,从而完成相关功能。
需要注意的是,如果打开了某个中断而 没有编写对应中断服务函数 或是 函数名没有对应上给定的标号,就会导致程序跑入预先写好的空函数中死循环跑死。
5. 堆和栈的初始化
堆和栈的初始化可以通过C库函数来进行,如果定义了 MICROLIB 则程序引出3个堆栈起始结束的地址标号给C库函数进行初始化。如果没有使用C库函数,则通过下面一段程序进行堆和栈的初始化