赞
踩
在讲解 STM32 启动过程之前,我们先来了解一下 STM32 的程序和数据在 Flash 和 SRAM 上到底是如何存储的,因为有了这方面的知识之后,非常有助于我们理解 STM32 启动过程中还做了哪些隐藏的工作。关于 STM32 详细的程序和数据存储分布信息,我们可以从Keil生成的 .map 文件中得到,下面讨论的 STM32 程序和数据的存储结构就是从这份文件和自己积累的一些相关知识总结出来的。要生成 .map 文件操作如下:
STM32 的程序在 Flash 上的存储结构如下图所示:
补充:Flash上是不会保存没有初始化或初始化为0的数据的,这部分数据要使用之前,把对应的内存空间清零即可,也就是清除 bss 段。另外 Flash 上可能还会存放 Keil 添加的一些代码或者参数,这些参数可能会用于数据段的重定位、清除 bss 段等任务,我对这些参数具体的存放位置还研究得不深,所以没有把这部分内容写上去。
当程序完成了数据段的重定位和清除bss段的任务之后(对于 STM32 会在启动文件中会调用 __main 函数来完成这部分工作),STM32 的数据在 SRAM 上的存储结构如下图所示:
STM32 启动过程主要指的是,系统上电后 CPU 执行的第一条指令,到调用用户写的 main 函数的这一段过程。具体启动过程的步骤和所完成的工作是:
(1) 上电复位,硬件设置 SP、PC 的值
(2) 找到了 Reset_Handler 的地址后,CPU 就从这里开始取指令运行程序;
(3) 调用 SystemInit 函数,设置系统时钟;
(4) 调用 __main 函数,软件对 SP 寄存器赋值,完成数据段的重定位、清除 bss 段,初始化栈空间等工作;
(5) 最终 __main 函数会调用用户的 main 函数,进入到用户程序。
启动过程分析中所涉及到源码主要来自启动文件和 Keil 生成的反汇编文件提取出来的。其中,Keil 生成反汇编文件的设置步骤如下:
具体输入的命令如下:
fromelf --bin --output=YH_STM32F103VET6.bin Objects\YH_STM32F103VET6.axf
fromelf --text -a -c --output=YH_STM32F103VET6.dis Objects\YH_STM32F103VET6.axf
第一条命令是生成 bin 文件的,如果不需要 bin 文件那么可以不输入这条命令。注意 YH_STM32F103VET6.axf 这个 axf 文件要根据自己工程的实际文件名输入。另外,生成反汇编文件时,最好把 Output 选项下的 Debug Information 这个选项勾上,这样生成的反汇编文件的信息比较详细。
下面是向量表的最前面两条指令:
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
......
对于 Cortex-M3 内核,ARM 规定向量表的起始位置存放的是栈顶指针 MSP 的地址值,紧接着存放的是复位中断入口函数的地址。当刚上电的时候,硬件会根据向量表的地址找到向量表的具体位置(对于向量表的地址是可以通过 NVIC 中的一个重定位寄存器来设置的,复位时该寄存器的值为0),然后会根据向量表中的这两个数据,设置 SP、PC 的值,这时 CPU 就会从复位中断的入口函数开始取指令运行程序。
硬件设置好了 SP、PC 的值后,这时CPU 会从 Reset_Handler 处开始取指令运行,首先会调用 SystemInit 函数来初始化系统时钟,该函数是官方固件库提供的一个库函数,在 system_stm32f10x.c 这个文件中有该函数的定义。下面是 Reset_Handler 的代码。
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0 ; 调用SystemInit函数
LDR R0, =__main
BX R0
ENDP
__main 函数是系统自带的库函数,根据反汇编的文件,得到的该函数的部分代码如下:
__main
_main_stk
0x08000130: f8dfd010 .... LDR sp,__lit__00000000 ; [0x8000144] = 0x200007a0
.ARM.Collect$$$$00000004
_main_scatterload
0x08000134: f000f81a .... BL __scatterload ; 0x800016c
从上面的代码可以看到,__main 函数首先软件设置了 SP 寄存器的值等于 0x200007a0,也就是设置了栈顶指针的指向。
接下来就跳转到了 __scatterload 函数,从函数名可以看出这是一个分散加载函数,该函数主要实现的功能就是数据段的重定位和清除 bss 段,初始化栈空间。该函数的反汇编源码如下:
__scatterload
__scatterload_rt2
0x0800016c: 4c06 .L LDR r4,[pc,#24] ; [0x8000188] = 0x80005d4
0x0800016e: 4d07 .M LDR r5,[pc,#28] ; [0x800018c] = 0x80005f4
0x08000170: e006 .. B 0x8000180 ; __scatterload + 20
0x08000172: 68e0 .h LDR r0,[r4,#0xc]
0x08000174: f0400301 @... ORR r3,r0,#1
0x08000178: e8940007 .... LDM r4,{r0-r2}
0x0800017c: 4798 .G BLX r3
0x0800017e: 3410 .4 ADDS r4,r4,#0x10
0x08000180: 42ac .B CMP r4,r5
0x08000182: d3f6 .. BCC 0x8000172 ; __scatterload + 6
0x08000184: f7ffffd8 .... BL __main_after_scatterload ; 0x8000138
__scatterload 函数实际上会去调用 __scatterload_copy 和 __scatterload_zeroinit 函数来完成上述的功能。
从上面的汇编代码可以看出,__scatterload 函数最后调用了 __main_after_scatterload 函数,这个函数实现的功能就是跳转到了用户的 main 函数,进入到用户程序。__main_after_scatterload 函数的代码如下:
__main_after_scatterload
_main_clock
_main_cpp_init
_main_init
0x08000138: 4800 .H LDR r0,[pc,#0] ; [0x800013c] = 0x80004c1
0x0800013a: 4700 .G BX r0
可以看出,代码最后跳转到了 r0 寄存器所指示的地址中去了,实际上这个地址就是用户的 main 函数地址。到这里为止 STM32 的启动过程就完成了,接下来运行的就是用户写的代码了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。