解析STM32启动过程

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

相对于ARM上一代的主流ARM7/ARM9内核架构新一代Cortex内核架构的启动方式有了比较大的变化。ARM7/ARM9内核的控制器在复位后CPU会从存储空间的绝对地址0x000000取出第一条指令执行复位中断服务程序的方式启动即固定了复位后的起始地址为0x000000PC = 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行分别保存栈顶指针和栈大小堆始地址和堆大小至R0R1R2R3寄存器。
 第224行程序完毕。
以上便是STM32的启动代码的完整解析接下来对几个小地方做解释
1、 AREA指令伪指令用于定义代码段或数据段后跟属性标号。其中比较重要的一个标号为“READONLY”或者“READWRITE”其中“READONLY”表示该段为只读属性联系到STM32的内部存储介质可知具有只读属性的段保存于FLASH区即0x8000000地址后。而“READONLY”表示该段为“可读写”属性可知“可读写”段保存于SRAM区即0x2000000地址后。由此可以从第3、7行代码知道堆栈段位于SRAM空间。从第82行可知中断向量表放置与FLASH区而这也是整片启动代码中最先被放进FLASH区的数据。因此可以得到一条重要的信息0x8000000地址存放的是栈顶地址__initial_sp0x8000004地址存放的是复位中断向量Reset_HandlerSTM32使用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的世界。
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6