stm32如何执行软复位
我们日常使用的嵌入式应用程序开发中都是编写C/C++语言,我们平时编辑一个工程的话,只用从main函数开始编辑,单片机程序也是从这个函数开始进行执行的。但是单片机上电后是如何寻找到并执行main函数的呢?我们其实都会在心里有一个答案:"启动文件",还记得我们当初配置第一个stm32工程的时候在工程中添加过一个startup_stm32f10x_hd.s的文件,这个文件的作用就是负责执行从"复位"到开始执行main函数中间的启动过程,下面我将对这个文件和stm32的相关知识进行阐述。
首先了解一下stm32的三种启动方式:在每个STM32的芯片上都有两个管脚BOOT0和BOOT1,这两个管脚在芯片复位时的电平状态决定了芯片复位后从哪个区域开始执行程序,见下表:BOOT1=x BOOT0=0 从用户闪存FLASH启动,这是正常的工作模式。BOOT1=0 BOOT0=1 从系统存储器启动,这种模式启动的程序功能由厂家设置。BOOT1=1 BOOT0=1 从内置SRAM启动,这种模式可以用于调试。
而Cortex-M3/4内核规定,起始地址必须存放堆顶指针,而第二个地址则必须存放复位中断入口向量地址,这样在Cortex-M3/4内核复位后,会自动从起始地址的下一个32位空间取出复位中断入口向量,跳转执行复位中断服务程序。而启动文件的编写正是按着这个规定来的,下面我将对启动文件的代码进行分析。
我挑选了我们常用的startup_stm32f40_41xxx.s进行源码分析,该文件可以分成以下五个典型部分:
1.堆栈空间定义;
2.存放中断向量表;
3.复位中断函数(Reset_Handler);
4.其它中断异常服务函数,以及弱[WEAK]声明;
5.将堆栈地址传递给库函数,利用库函数初始化堆栈,和库函数自身初始化。
01堆栈空间的定义
如下图所示,定义了栈大小Stack_Size = 0X400,即1024个字节;堆大小Heap_Size = 0X200, 512个字节。还定义了三个标号:__initial_sp(栈顶)、__heap_base(堆起始地址)和__heap_limit(堆终止地址),它们的空间由SPACE关键字来申请,并记作Stack_Mem和Heap_Mem。
02存放中断向量表
在启动代码中,会见到许多由DCD申请空间存放的一个个函数入口,即中断向量表,关键字DCD代表申请一个字的空间,后面的函数名即为中断服务函数入口地址。
03中断复位函数
程序上电后,首先加载SP和PC,ARM规定从0地址处加载堆顶指针,从偏移为4的地址(0x00000004)处加载复位函数,然后后将程序控制权交给程序。我们知道0地址处存放__initial_sp,0x00000004地址处存放Reset_Handler,符合ARM规定。
__main标号并不表示C程序中的main函数入口地址,因此第131行也并不是跳转至main函数开始执行C程序。__main标号表示C/C++标准实时库函数里的一个初始化子程序__main的入口地址。该程序的一个主要作用是初始化堆栈(对于程序清单一来说则是跳转__user_initial_stackheap标号进行初始化堆栈的),并初始化映像文件,最后跳转C程序中的main函数。这就解释了为何所有的C程序必须有一个main函数作为程序的起点——因为这是由C/C++标准实时库所规定的——并且不能更改,因为C/C++标准实时库并不对外界开发源代码.
04其他中断异常服务函数,以及弱声明
所谓弱声明,即:如果用户定义了相同的函数则此处函数失效而使用用户定义的中断服务函数,这样可以防止用户使能了中断二没有中断服务函数造成程序崩溃。
05将堆栈地址传递给库函数
第三步骤中,调用__main函数,然后__main调用库函数初始化堆栈,但库函数并不知道堆栈的大小,因此我们需要告诉它。
至此可以总结一下STM32的启动过程和启动文件。首先对堆栈大小进行定义,并建立中断向量表,其第一个表项是栈顶地址,第二个表项是复位中断服务入口地址,然后在复位中断服务程序中跳转到C/C++标准实时库的_main函数,完成用户堆栈初始化后,跳转到C文件的main函数开始执行C程序。假设STM32被设置为从内部FLASH启动(这也是最常见的一种情况),中断向量表起始地位为0x8000000,则栈顶地址存放于0x8000000处,而复位中断服务入口地址存放于0x8000004处。当STM32遇到复位信号后,则从0x80000004处取出复位中断服务入口地址,继而执行复位中断服务程序,然后跳转__main函数,最后进入mian函数,来到C的世界。