您的位置:首页 > 其它

解析 STM32 的启动过程

2016-09-19 10:25 232 查看
(原文件下载pdf下载在http://www.openedv.com/posts/list/5563.htm的14楼)

当前的嵌入式应用程序开发过程里,并且
C 语言成为了绝大部分场合的最佳选择。如此

一来 main
函数似乎成为了理所当然的起点——因为 C
程序往往从 main
函数开始执行。但

一个经常会被忽略的问题是:微控制器(单片机)上电后,是如何寻找到并执行
main 函数

的呢? 很显然微控制器无法从硬件上定位
main 函数的入口地址,因为使用
C 语言作为开发

语言后,变量/函数的地址便由编译器在编译时自行分配,这样一来
main 函数的入口地址在

微控制器的内部存储空间中不再是绝对不变的。 相信读者都可以回答这个问题, 答案也许大
同小异,但肯定都有个关键词,叫“启动文件”,用英文单词来描述是“
Bootloader”。

无论性能高下,结构简繁,价格贵贱,每一种微控制器(处理器)都必须有启动文件,
启动文件的作用便是负责执行微控制器从“复位”到“开始执行
main 函数”中间这段时间

(称为启动过程)所必须进行的工作。最为常见的
51, AVR
或 MSP430 等微控制器当然也有

对应启动文件,但开发环境往往自动完整地提供了这个启动文件,不需要开发人员再行干预
启动过程,只需要从 main
函数开始进行应用程序的设计即可。

话题转到 STM32
微控制器,无论是 keil uvision4
还是 IAR EWARM
开发环境, ST 公司都

提供了现成的直接可用的启动文件,程序开发人员可以直接引用启动文件后直接进行
C 应用

程序的开发。这样能大大减小开发人员从其它微控制器平台跳转至
STM32 平台,也降低了

适应 STM32
微控制器的难度(对于上一代 ARM
的当家花旦 ARM9,启动文件往往是第一道

难啃却又无法逾越的坎)。
相对于 ARM
上一代的主流 ARM7/ARM9
内核架构,新一代 Cortex 内核架构的启动方式

有了比较大的变化。 ARM7/ARM9
内核的控制器在复位后, CPU
会从存储空间的绝对地址

0x000000 取出第一条指令执行复位中断服务程序的方式启动,即固定了复位后的起始地址

为 0x000000(
PC = 0x000000)同时中断向量表的位置并不是固定的。而
Cortex-M3 内核则正

好相反,有 3
种情况:

1、 通过
boot 引脚设置可以将中断向量表定位于
SRAM 区,即起始地址为
0x2000000,同时

复位后 PC
指针位于 0x2000000
处;

2、 通过
boot 引脚设置可以将中断向量表定位于
FLASH 区,即起始地址为
0x8000000,同时

复位后 PC
指针位于 0x8000000
处;

3、 通过
boot 引脚设置可以将中断向量表定位于内置
Bootloader 区,本文不对这种情况做

论述;
而 Cortex-M3
内核规定,起始地址必须存放堆顶指针,而第二个地址则必须存放复位中断入

口向量地址,这样在 Cortex-M3
内核复位后,会自动从起始地址的下一个 32
位空间取出复

位中断入口向量,跳转执行复位中断服务程序。对比
ARM7/ARM9 内核, Cortex-M3
内核则

是固定了中断向量表的位置而起始地址是可变化的。
有了上述准备只是后,下面以 STM32的
2.02固件库提供的启动文件“
stm32f10x_vector.s”

为模板,对 STM32
的启动过程做一个简要而全面的解析。

程序清单一:
;文件“ stm32f10x_vector.s”,其中注释为行号

DATA_IN_ExtSRAM EQU 0
; 1

Stack_Size EQU 0x00000400
; 2

AREA STACK, NOINIT, READWRITE, ALIGN = 3
; 3

Stack_Mem SPACE Stack_Size
; 4

__initial_sp ;
5

Heap_Size EQU 0x00000400
; 6

AREA HEAP, NOINIT, READWRITE, ALIGN = 3
; 7

__heap_base ;
8

Heap_Mem SPACE Heap_Size
; 9

__heap_limit ;
10

THUMB ;
11

PRESERVE8 ;
12

IMPORT NMIException
; 13

IMPORT HardFaultException
; 14

IMPORT MemManageException
; 15

IMPORT BusFaultException
; 16

IMPORT UsageFaultException
; 17

IMPORT SVCHandler
; 18

IMPORT DebugMonitor
; 19

IMPORT PendSVC ;
20

IMPORT SysTickHandler
; 21

IMPORT WWDG_IRQHandler
; 22

IMPORT PVD_IRQHandler
; 23

IMPORT TAMPER_IRQHandler
; 24

IMPORT RTC_IRQHandler
; 25

IMPORT FLASH_IRQHandler
; 26

IMPORT RCC_IRQHandler
; 27

IMPORT EXTI0_IRQHandler
; 28

IMPORT EXTI1_IRQHandler
; 29

IMPORT EXTI2_IRQHandler
; 30

IMPORT EXTI3_IRQHandler
; 31

IMPORT EXTI4_IRQHandler
; 32

IMPORT DMA1_Channel1_IRQHandler
; 33

IMPORT DMA1_Channel2_IRQHandler
; 34

IMPORT DMA1_Channel3_IRQHandler
; 35

IMPORT DMA1_Channel4_IRQHandler
; 36

IMPORT DMA1_Channel5_IRQHandler
; 37

IMPORT DMA1_Channel6_IRQHandler
; 38

IMPORT DMA1_Channel7_IRQHandler
; 39

IMPORT ADC1_2_IRQHandler
; 40

IMPORT USB_HP_CAN_TX_IRQHandler
; 41

IMPORT USB_LP_CAN_RX0_IRQHandler
; 42

IMPORT CAN_RX1_IRQHandler
; 43

IMPORT CAN_SCE_IRQHandler
; 44

IMPORT EXTI9_5_IRQHandler
; 45

IMPORT TIM1_BRK_IRQHandler
; 46

IMPORT TIM1_UP_IRQHandler
; 47

IMPORT TIM1_TRG_COM_IRQHandler
; 48

IMPORT TIM1_CC_IRQHandler
; 49

IMPORT TIM2_IRQHandler
; 50

IMPORT TIM3_IRQHandler
; 51

IMPORT TIM4_IRQHandler
; 52

IMPORT I2C1_EV_IRQHandler
; 53

IMPORT I2C1_ER_IRQHandler
; 54

IMPORT I2C2_EV_IRQHandler
; 55

IMPORT I2C2_ER_IRQHandler
; 56

IMPORT SPI1_IRQHandler
; 57

IMPORT SPI2_IRQHandler
; 58

IMPORT USART1_IRQHandler
; 59

IMPORT USART2_IRQHandler
; 60

IMPORT USART3_IRQHandler
; 61

IMPORT EXTI15_10_IRQHandler
; 62

IMPORT RTCAlarm_IRQHandler
; 63

IMPORT USBWakeUp_IRQHandler
; 64

IMPORT TIM8_BRK_IRQHandler
; 65

IMPORT TIM8_UP_IRQHandler
; 66

IMPORT TIM8_TRG_COM_IRQHandler
; 67

IMPORT TIM8_CC_IRQHandler
; 68

IMPORT ADC3_IRQHandler
; 69

IMPORT FSMC_IRQHandler
; 70

IMPORT SDIO_IRQHandler
; 71

IMPORT TIM5_IRQHandler
; 72

IMPORT SPI3_IRQHandler
; 73

IMPORT UART4_IRQHandler
; 74

IMPORT UART5_IRQHandler
; 75

IMPORT TIM6_IRQHandler
; 76

IMPORT TIM7_IRQHandler
; 77

IMPORT DMA2_Channel1_IRQHandler
; 78

IMPORT DMA2_Channel2_IRQHandler
; 79

IMPORT DMA2_Channel3_IRQHandler
; 80

IMPORT DMA2_Channel4_5_IRQHandler
; 81

AREA RESET, DATA, READONLY
; 82

EXPORT __Vectors
; 83

__Vectors ;
84

DCD __initial_sp
; 85

DCD Reset_Handler
; 86

DCD NMIException
; 87

DCD HardFaultException
; 88

DCD MemManageException
; 89

DCD BusFaultException
; 90

DCD UsageFaultException
; 91

DCD 0 ;
92

DCD 0 ;
93

DCD 0 ;
94

DCD 0 ;
95

DCD SVCHandler ;
96

DCD DebugMonitor
; 97

DCD 0 ;
98

DCD PendSVC ;
99

DCD SysTickHandler
; 100

DCD WWDG_IRQHandler
; 101

DCD PVD_IRQHandler
; 102

DCD TAMPER_IRQHandler
; 103

DCD RTC_IRQHandler
; 104

DCD FLASH_IRQHandler
; 105

DCD RCC_IRQHandler
; 106

DCD EXTI0_IRQHandler
; 107

DCD EXTI1_IRQHandler
; 108

DCD EXTI2_IRQHandler
; 109

DCD EXTI3_IRQHandler
; 110

DCD EXTI4_IRQHandler
; 111

DCD DMA1_Channel1_IRQHandler
; 112

DCD DMA1_Channel2_IRQHandler
; 113

DCD DMA1_Channel3_IRQHandler
; 114

DCD DMA1_Channel4_IRQHandler
; 115

DCD DMA1_Channel5_IRQHandler
; 116

DCD DMA1_Channel6_IRQHandler
; 117

DCD DMA1_Channel7_IRQHandler
; 118

DCD ADC1_2_IRQHandler
; 119

DCD USB_HP_CAN_TX_IRQHandler
; 120

DCD USB_LP_CAN_RX0_IRQHandler
; 121

DCD CAN_RX1_IRQHandler
; 122

DCD CAN_SCE_IRQHandler
; 123

DCD EXTI9_5_IRQHandler
; 124

DCD TIM1_BRK_IRQHandler
; 125

DCD TIM1_UP_IRQHandler
; 126

DCD TIM1_TRG_COM_IRQHandler
; 127

DCD TIM1_CC_IRQHandler
; 128

DCD TIM2_IRQHandler
; 129

DCD TIM3_IRQHandler
; 130

DCD TIM4_IRQHandler
; 131

DCD I2C1_EV_IRQHandler
; 132

DCD I2C1_ER_IRQHandler
; 133

DCD I2C2_EV_IRQHandler
; 134

DCD I2C2_ER_IRQHandler
; 135

DCD SPI1_IRQHandler
; 136

DCD SPI2_IRQHandler
; 137

DCD USART1_IRQHandler
; 138

DCD USART2_IRQHandler
; 139

DCD USART3_IRQHandler
; 140

DCD EXTI15_10_IRQHandler
; 141

DCD RTCAlarm_IRQHandler
; 142

DCD USBWakeUp_IRQHandler
; 143

DCD TIM8_BRK_IRQHandler
; 144

DCD TIM8_UP_IRQHandler
; 145

DCD TIM8_TRG_COM_IRQHandler
; 146

DCD TIM8_CC_IRQHandler
; 147

DCD ADC3_IRQHandler
; 148

DCD FSMC_IRQHandler
; 149

DCD SDIO_IRQHandler
; 150

DCD TIM5_IRQHandler
; 151

DCD SPI3_IRQHandler
; 152

DCD UART4_IRQHandler
; 153

DCD UART5_IRQHandler
; 154

DCD TIM6_IRQHandler
; 155

DCD TIM7_IRQHandler
; 156

DCD DMA2_Channel1_IRQHandler
; 157

DCD DMA2_Channel2_IRQHandler
; 158

DCD DMA2_Channel3_IRQHandler
; 159

DCD DMA2_Channel4_5_IRQHandler
; 160

AREA |.text|, CODE, READONLY
; 161

Reset_Handler PROC
; 162

EXPORT Reset_Handler
; 163

IF DATA_IN_ExtSRAM == 1
; 164

LDR R0,= 0x00000114
; 165

LDR R1,= 0x40021014
; 166

STR R0,[R1] ;
167

LDR R0,= 0x000001E0
; 168

LDR R1,= 0x40021018
; 169

STR R0,[R1] ;
170

LDR R0,= 0x44BB44BB
; 171

LDR R1,= 0x40011400
; 172

STR R0,[R1] ;
173

LDR R0,= 0xBBBBBBBB
; 174

LDR R1,= 0x40011404
; 175

STR R0,[R1] ;
176

LDR R0,= 0xB44444BB
; 177

LDR R1,= 0x40011800
; 178

STR R0,[R1] ;
179

LDR R0,= 0xBBBBBBBB
; 180

LDR R1,= 0x40011804
; 181

STR R0,[R1] ;
182

LDR R0,= 0x44BBBBBB
; 183

LDR R1,= 0x40011C00
; 184

STR R0,[R1] ;
185

LDR R0,= 0xBBBB4444
; 186

LDR R1,= 0x40011C04
; 187

STR R0,[R1] ;
188

LDR R0,= 0x44BBBBBB
; 189

LDR R1,= 0x40012000
; 190

STR R0,[R1] ;
191

LDR R0,= 0x44444B44
; 192

LDR R1,= 0x40012004
; 193

STR R0,[R1] ;
194

LDR R0,= 0x00001011
; 195

LDR R1,= 0xA0000010
; 196

STR R0,[R1] ;
197

LDR R0,= 0x00000200
; 198

LDR R1,= 0xA0000014
; 199

STR R0,[R1] ;
200

ENDIF ;
201

IMPORT __main ;
202

LDR R0, =__main ;
203

BX R0 ;
204

ENDP ;
205

ALIGN ;
206

IF :DEF:__MICROLIB
; 207

EXPORT __initial_sp
; 208

EXPORT __heap_base
; 209

EXPORT __heap_limit
; 210

ELSE ;
211

IMPORT __use_two_region_memory
; 212

EXPORT __user_initial_stackheap
; 213

__user_initial_stackheap
; 214

LDR R0, = Heap_Mem
; 215

LDR R1, = (Stack_Mem + Stack_Size)
; 216

LDR R2, = (Heap_Mem + Heap_Size)
; 217

LDR R3, = Stack_Mem
; 218

BX LR ;
219

ALIGN ;
220

ENDIF ;
221

END ;
222

ENDIF ;
223

END ;
224

如程序清单一, STM32
的启动代码一共 224
行,使用了汇编语言编写,这其中的主要

原因下文将会给出交代。现在从第一行开始分析:

 第
1 行:定义是否使用外部
SRAM,为 1
则使用,为 0 则表示不使用。此语行若用
C 语

言表达则等价于:

#define DATA_IN_ExtSRAM 0

 第
2 行:定义栈空间大小为
0x00000400 个字节,即
1Kbyte。此语行亦等价于:

#define Stack_Size 0x00000400

 第
3 行:伪指令 AREA,表示

 第
4 行:开辟一段大小为
Stack_Size 的内存空间作为栈。

 第
5 行:标号__initial_sp,表示栈空间顶地址。

 第
6 行:定义堆空间大小为
0x00000400 个字节,也为
1Kbyte。

 第
7 行:伪指令 AREA,表示

 第
8 行:标号__heap_base,表示堆空间起始地址。

 第
9 行:开辟一段大小为
Heap_Size 的内存空间作为堆。

 第
10 行:标号__heap_limit,表示堆空间结束地址。

 第
11 行:告诉编译器使用
THUMB 指令集。

 第
12 行:告诉编译器以
8 字节对齐。

 第
13—81
行: IMPORT
指令,指示后续符号是在外部文件定义的(类似 C
语言中的全

局变量声明),而下文可能会使用到这些符号。

 第
82 行: 定义只读数据段,实际上是在
CODE 区(假设
STM32 从 FLASH
启动,则此中

断向量表起始地址即为 0x8000000)

 第
83 行:将标号__Vectors
声明为全局标号,这样外部文件就可以使用这个标号。

 第
84 行:标号__Vectors,表示中断向量表入口地址。

 第
85—160
行:建立中断向量表。

 第
161 行:

 第
162 行:复位中断服务程序,
PROC…ENDP 结构表示程序的开始和结束。

 第
163 行:声明复位中断向量
Reset_Handler 为全局属性,这样外部文件就可以调用此

复位中断服务。

 第
164 行: IF…ENDIF
为预编译结构,判断是否使用外部 SRAM,在第
1 行中已定义为“不

使用”。

 第
165—201
行:此部分代码的作用是设置 FSMC
总线以支持 SRAM,因不使用外部
SRAM

因此此部分代码不会被编译。

 第
202 行:声明__main
标号。

 第
203—204
行:跳转__main
地址执行。

 第
207 行: IF…ELSE…ENDIF
结构,判断是否使用 DEF:__MICROLIB(此处为不使用)。

 第
208—210
行:若使用 DEF:__MICROLIB,则将__initial_sp,
__heap_base,
__heap_limit

亦即栈顶地址,堆始末地址赋予全局属性,使外部程序可以使用。

 第
212 行:定义全局标号__use_two_region_memory。

 第
213 行:声明全局标号__user_initial_stackheap,这样外程序也可调用此标号。

 第
214 行:标号__user_initial_stackheap,表示用户堆栈初始化程序入口。

 第
215—218
行:分别保存栈顶指针和栈大小,堆始地址和堆大小至 R0,
R1, R2,
R3

寄存器。

 第
224 行:程序完毕。

以上便是 STM32
的启动代码的完整解析,接下来对几个小地方做解释:

1、
AREA 指令:伪指令,用于定义代码段或数据段,后跟属性标号。其中比较重要的一个

标号为“ READONLY”或者“
READWRITE”,其中“
READONLY”表示该段为只读属性,联

系到 STM32
的内部存储介质,可知具有只读属性的段保存于 FLASH
区,即 0x8000000

地址后。而“ READONLY”表示该段为“可读写”属性,可知“可读写”段保存于
SRAM

区,即 0x2000000
地址后。由此可以从第 3、
7 行代码知道,堆栈段位于
SRAM 空间。

从第 82行可知,中断向量表放置与
FLASH区,而这也是整片启动代码中最先被放进
FLASH

区的数据。因此可以得到一条重要的信息:
0x8000000 地址存放的是栈顶地址__initial_sp,

0x8000004 地址存放的是复位中断向量
Reset_Handler(
STM32 使用 32
位总线,因此存

储空间为 4
字节对齐)。

2、
DCD 指令:作用是开辟一段空间,其意义等价于
C 语言中的地址符“
&”。因此从第 84

行开始建立的中断向量表则类似于使用
C 语言定义了一个指针数组,其每一个成员都是

一个函数指针,分别指向各个中断服务函数。

3、 标号:前文多处使用了“标号”一词。标号主要用于表示一片内存空间的某个位置,等

价于 C
语言中的“地址”概念。地址仅仅表示存储空间的一个位置,从 C
语言的角度来

看,变量的地址,数组的地址或是函数的入口地址在本质上并无区别。

4、 第
202 行中的__main
标号并不表示 C 程序中的
main 函数入口地址,因此第
204 行也并

不是跳转至 main
函数开始执行 C 程序。
__main 标号表示
C/C++标准实时库函数里的一

个初始化子程序__main
的入口地址。该程序的一个主要作用是初始化堆栈(对于程序清

单一来说则是跳转__user_initial_stackheap
标号进行初始化堆栈的),并初始化映像文件,

最后跳转 C
程序中的 main
函数。这就解释了为何所有的 C 程序必须有一个
main 函数作

为程序的起点——因为这是由 C/C++标准实时库所规定的——并且不能更改,因为
C/C++

标准实时库并不对外界开发源代码。 因此,实际上在用户可见的前提下,程序在第
204

行后就跳转至.c
文件中的 main
函数,开始执行 C 程序了。

至此可以总结一下 STM32
的启动文件和启动过程。首先对栈和堆的大小进行定义,并在

代码区的起始处建立中断向量表,其第一个表项是栈顶地址,第二个表项是复位中断服务入
口地址。然后在复位中断服务程序中跳转
C/C++标准实时库的__main
函数,完成用户堆栈等

的初始化后,跳转.c
文件中的 main
函数开始执行 C 程序。假设
STM32 被设置为从内部
FLASH

启动( 这也是最常见的一种情况),中断向量表起始地位为
0x8000000,则栈顶地址存放于

0x8000000 处,而复位中断服务入口地址存放于
0x8000004 处。当
STM32 遇到复位信号后,

则从 0x80000004
处取出复位中断服务入口地址, 继而执行复位中断服务程序, 然后跳转

__main 函数,最后进入
mian 函数,来到
C 的世界。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息