本文以STM32F103为例,介绍STM32的启动文件。
一般而言,系统上电后第一个执行的是由汇编所编写的启动文件,其主要工作为一下五部分:
(1)、初始化堆栈指针SP=_initial_sp
(2)、初始化PC指针,令其=Reset_Handler
(3)、初始化中断向量表
(4)、配置系统时钟
(5)、调用C库函数_main初始化用户堆栈,从而最终调用main函数进入C的世界
这上述的五个功能一般都由STM32官方在它们提供的官方库里的ASM文件(汇编启动文件)startup_stm32f10x_hd.s实现,因此在实际中只需要根据所用编译软件的不同选择对应的ASM文件,然后将之加入编译的工程中,再编写自己的main文件便可,而系统时钟已在ASM文件中设为72M,以及向量表已经设置完成(向量表的介绍在别的文章里介绍)。
在startup_stm32f10x_hd.s文件中,开始为上述代码,其将栈大小设为0x0000 0400(1KB),栈名为STACK,不初始化,可读可写,8(2^3)字节对齐。最后的__initial_sp表示栈的结束地址,即栈顶地址,因为栈是由高地址向低地址生长。
栈的主要作用是用于局部变量、函数调用以及函数形参等的开销,其大小应小于内部RAM的大小,以及考虑到局部变量的需求,防止栈溢出。
在栈的代码后面便是初始化堆的代码,其中堆的大小设为0x0000 0200(512B),栈名为HEAP,不初始化,可读可写,8(2^3)字节对齐。而__heap_base为堆的起始地址,__heap_limit为堆的结束地址,因为堆的由低地址向高地址生长。
堆的作用是用于malloc()之类函数申请的动态内存的分配。
内部一般用于存放全局变量以及静态局部变量,其在程序结束后释放。初始化文件里对其没设置。
对于这三者更加详细的介绍可参考博客:http://blog.csdn.net/liubing8609/article/details/42362179
启动文件中已分配好向量表,此处在另一篇文章中再介绍。
因为Reset_Handler是系统上电后自动跳转至该处,然后开始执行其后的相关代码,其中_main并非C中的main函数,而是C库函数,其会初始化栈、堆,配置系统环境,在函数的最后调用用户编写的main函数,进入C的世界。
(1)首先是调用SystemInit函数来初始化系统时钟以及中断向量表
查看STM32的官方手册可知,SystemInit函数主要是对RCC进行相关的操作,其中RCC在官方的固件库里的定义如下:
#define RCC ((RCC_TypeDef *) RCC_BASE)
#define RCC_BASE (AHBPERIPH_BASE + 0x1000)
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
#define PERIPH_BASE ((uint32_t)0x40000000)
再调用SetSysClock()函数来将系统时钟配置为72M,然后配置一下向量表的地址,便可调到下一步,即调用main()函数从而进入C的世界。
四、用户堆栈的设置
;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
IF :DEF:__MICROLIB
EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit
ELSE
IMPORT __use_two_region_memory ;该函数可有用户自己实现
EXPORT __user_initial_stackheap
__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
END
如上述代码所述,根据宏__MICROLIB是否定义来决定用户堆栈初始化的方法。
在STM32官方提供的ASM启动文件里,其已完成了系统时钟以及向量表的相关设置,因此如果在main函数中如果时钟频率没要求改变的话,其可以忽略时钟一类信息的设置,而是直接进行相关操作。