一、stm32启动过程的解析
stm32上电后,是怎么样找到main函数的呢?很显然,stm32没办法从硬件上找到main函数的入口地址,因为使用C语言开发后,变量和函数的地址由编译器自动分配,这样就导致main函数的入口地址在stm32的内部存储空间中不再是固定的。那么stm32是通过什么东西找到main函数的入口地址呢,在跳转到main函数之前,做了什么?
其实,stm32上电后,是通过启动文件(英文为BootLoader)(例如startup_stm32f10x_hd.h)找到main函数的入口地址,在这个过程中主要实现:堆栈大小的定义,中断向量表的重新映射,加载RW段,ZI段清零,用户堆栈初始化,获取main的入口地址及跳转到main函数。Cortex-M3内核规定,起始地址必须存放堆顶指针,而第二个地址则必须存放复位中断入口向量地址,这样在Cortex-M3内核复位后,会自动从起始地址的下一个32位空间取出复位中断向量,跳转执行复位中断服务程序。对比ARM7/ARM9内核,Cortex-M3内核则是固定了中断向量表的位置,而起始地址是可变化的。
为了降低程序开发人员的开发难度,启动文件由ST公司提供,不需要开发人员再干预启动过程。
启动文件的作用是负责stm32从 ”上电”到”开始执行main函数”中间这段时间(称为启动过程)所必须进行的工作。
二、启动文件分析
本文以startup_stm32f10x_hd.h进行分析,该文件分别定义了栈段、堆段、存放中断向量表的数据段、还有一个代码段。
定义大小为0x400的栈段,如图2.1:
图2.1
定义大小为0x200的堆段,如图2.2:
图2.2
以上只是定义堆和栈的大小,并没有对堆栈进行初始化,初始化堆栈由后面的__user_initial_stackheap完成。
存放中断向量的数据段,如图2.3 所示:
图2.2
10 个系统异常过程段和在同一地址的外部中断过程段,下面我们就详细介绍上电复位的代码段,如图 2.4所示:
图2.4
STM32 上电启动,首先从 0x0000 0000 处初始化 sp 的值,然后从 0x0000 0004处取得复位中断处理的地址 0x0800 1F6D,但是Reset_Handler的地址为0x0800 1F6C,这是因为Cortex-M3使用的是thumb-2指令集,其最低位必须为 1;如果为 0,则会出现异常。可以知道先取得 SystemInit 函数的地址,该地址是多少,我们可以跳转到0x08001F94 ,该地址保存了值为 0x0800045D 的数,然后我们跳转到 0x0800045D,发现该处正是我们需要的 SystemInit 函数入口地址,该函数首先保存跳转前的有关状态,进行相应的初始化操作,函数最后重新映射了中断向量的存放地址。进行完相应的初始化,函数跳转到__main函数,__main函数的入口入口地址从0x08001F98 取得,通过查找,发现_main 函数的地址为 0x08000121,跳转到0x08000120证明此处就是我们要找的_main 函数入口。
至此可以总结一下STM32的启动文件和启动过程。首先对栈和堆的大小进行定义,并在代码区的起始处建立中断向量表,其第一个表项是栈顶地址,第二个表项是复位中断服务入口地址。然后在复位中断服务程序中跳转C标准实时库的__main函数,完成用户堆栈等的初始化后,跳转.c文件中的 main函数开始执行C程序。假设STM32被设置为从内部FLASH启动(这也是最常见的一种情况),中断向量表起始地位为0x8000000,则栈顶地址存放于0x8000000处,而复位中断服务入口地址存放于0x8000004处。当STM32遇到复位信号后,则从0x80000004处取出复位中断服务入口地址,继而执行复位中断服务程序,然后跳转__main函数,最后进入mian函数,来到C的世界。