解析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的世界。 |