文件获取
启动文件可以在ST官网上获取,startup_stm32f10x_hd.s
文件Description:
描述了启动文件主要完成的工作:
- 设置SP,也就是初始栈顶指针。
- 设置初始PC的值,设置为复位处理函数的地址。
- 设置中断向量表的各个中断服务函数的入口地址。
- 配置系统时钟。
- 调用C库函数_main()初始化用户,跳转到main()函数
涉及到的汇编指令
- EQU:伪指令。其作用相当于#define,给数字常量定义一个符号名。
- AREA:汇编一个新的代码段或者数据段。
- SPACE:分配内存空间。
- PRESERVES:将当前文件按8字节对齐。
- EXPORT:声明一个标号,可以被外部文件使用。
- IMPORT:标号来自外部文件,类似extern。
- DCD:以字为单位分配内存,4字节对齐,并初始化这些内存。
- PROC:表示子程序的开端。
- ENDP:表示子程序的结束。
- WEAK:弱定义,如果外部文件也声明了一个标号,那么以外部文件的优先。
- B:跳转。
- ALIGN:地址对齐。默认4字节对齐。
WEAK的补充:
启动文件中的内容,一般内核都有所规定,那么为了编译通过,即使芯片厂家不使用那些功能,也会使用WEAK占位。而且通过使用WEAK,也能够使得大部分功能给用户进行二次开发,同时WEAK的内容芯片可以做一个sample,指示用户按sample来进行改写。
在RTOS的源码中就会大量采用这种操作,在编写库函数时,以允许用户自己实现覆盖。
WEAK中,如果用户优先实现了,那么在链接的时候,优先会选用强符号的内容。
链接阶段的补充:
编译阶段可以帮助我们找到语法的错误,那么链接阶段,就会帮我们把有效的程序进行链接。注意是有效的,比如我们写了一个WEAK或者从没有调用的函数,那么就不会被链接到最终的文件当中。
链接阶段也会对变量进行初始化操作,将不同的内容放入不同的内存。若一些定义了并没有赋值的变量,也是由链接帮助我们写0然后放入ZI段中。以及一些已经初始化的值,也会放入对于的data段中。这部分可以结合RT-Thread手册来看具体的数据在哪个区。
堆和栈的占位
修改启动文件:
如果是原厂自带的启动文件一般是不可修改的,但是实际开发中都需要修改启动文件。首先就是1KB的栈不一定够用,而且在单片机的开发中一般都不使用堆,除非Linux这类操作系统。那么512字节的堆空间基本上都必须去除,或者降至最低(去除的话有可能编译不通过,厂家的规范需要有一个堆的占位)。
以及一般来说使用了 IAP + APP 的话,需要重新定位中断向量表的偏移地址。
向量表
直到这边的 DCD _initial_sp才开始真正初始化栈。
中断向量表中每个中断处理函数的位置都是被内核厂家定好的,但是具体的芯片厂家不一定会全部使用,不需要的一般都会写一个0来占位,防止编译不通过。
EXPORT:将三个标识符声明为可被外部引用。
_Vectors:表示向量表的起始地址。向量表起始地址强制性为栈顶指针。第二位为复位处理函数。
DCD:每行都会生成4字节的二进制地址。以此排列下去就是各种系统中断的地址。
DCD 0:这是因为 Cortex-M 的向量表长度必须固定为 16 个字,所以若没用到的异常入口用 DCD 0 来占位。
_Vectors_End:向量表的结束地址。
SystemInit和_main()
AREA:定于一块代码段,只能读。
Reset_Handler:通过Reset_Handler来配置系统时钟和跳转到_main()函数。
Systeminit函数一般是用于配置系统时钟的。
_main()函数最终会跳转到C语言的main函数,开始执行APP的程序。
Systeminit补充
该函数位于配套的.c文件当中,一般都是用于配置系统时钟。芯片厂家一般都会帮写好,通过宏定义来确定具体初始化的时钟源。
这些宏定义可以发现是位于config.h这个头文件当中。
默认中断处理
堆栈的初始化
如果定义了使用 MICROLIB,这个是在keil中配置的,那么就将栈顶指针,堆的起始和结束地址EXPORT,外部可以使用。然后就由_main()函数来完成堆栈的初始化操作。
CMSIS统一标准的定义:
这个是内核和其他芯片厂家规定好的,那么按照这个协议去编写的驱动的可移植性和通用性比较好。