解析STM32启动过程

时间:2021-09-30 16:47:33

相对于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的启动过程做一个简要而全面的解析。

  
 
 
  1. DATA_IN_ExtSRAM EQU 0   
  2. Stack_Size EQU 0x00000400   
  3. AREA STACK, NOINIT, READWRITE, ALIGN = 3   
  4. Stack_Mem SPACE Stack_Size   
  5. __initial_sp   
  6. Heap_Size EQU 0x00000400   
  7. AREA HEAP, NOINIT, READWRITE, ALIGN = 3   
  8. __heap_base   
  9. Heap_Mem SPACE Heap_Size   
  10. __heap_limit   
  11. THUMB   
  12. PRESERVE8   
  13. IMPORT NMIException  
  14. IMPORT HardFaultException   
  15. IMPORT MemManageException   
  16. IMPORT BusFaultException   
  17. IMPORT UsageFaultException   
  18. IMPORT SVCHandler   
  19. IMPORT DebugMonitor   
  20. IMPORT PendSVC   
  21. IMPORT SysTickHandler   
  22. IMPORT WWDG_IRQHandler   
  23. IMPORT PVD_IRQHandler   
  24. IMPORT TAMPER_IRQHandler   
  25. IMPORT RTC_IRQHandler   
  26. IMPORT FLASH_IRQHandler   
  27. IMPORT RCC_IRQHandler   
  28. IMPORT EXTI0_IRQHandler   
  29. IMPORT EXTI1_IRQHandler   
  30. IMPORT EXTI2_IRQHandler  
  31. IMPORT EXTI3_IRQHandler   
  32. IMPORT EXTI4_IRQHandler   
  33. IMPORT DMA1_Channel1_IRQHandler   
  34. IMPORT DMA1_Channel2_IRQHandler   
  35. IMPORT DMA1_Channel3_IRQHandler   
  36. IMPORT DMA1_Channel4_IRQHandler   
  37. IMPORT DMA1_Channel5_IRQHandler   
  38. IMPORT DMA1_Channel6_IRQHandler   
  39. IMPORT DMA1_Channel7_IRQHandler   
  40. IMPORT ADC1_2_IRQHandler   
  41. IMPORT USB_HP_CAN_TX_IRQHandler   
  42. IMPORT USB_LP_CAN_RX0_IRQHandler   
  43. IMPORT CAN_RX1_IRQHandler   
  44. IMPORT CAN_SCE_IRQHandler   
  45. IMPORT EXTI9_5_IRQHandler   
  46. IMPORT TIM1_BRK_IRQHandler   
  47. IMPORT TIM1_UP_IRQHandler   
  48. IMPORT TIM1_TRG_COM_IRQHandler   
  49. IMPORT TIM1_CC_IRQHandler   
  50. IMPORT TIM2_IRQHandler   
  51. IMPORT TIM3_IRQHandler   
  52. IMPORT TIM4_IRQHandler   
  53. IMPORT I2C1_EV_IRQHandler   
  54. IMPORT I2C1_ER_IRQHandler   
  55. IMPORT I2C2_EV_IRQHandler   
  56. IMPORT I2C2_ER_IRQHandler   
  57. IMPORT SPI1_IRQHandler   
  58. IMPORT SPI2_IRQHandler   
  59. IMPORT USART1_IRQHandler   
  60. IMPORT USART2_IRQHandler   
  61. IMPORT USART3_IRQHandler   
  62. IMPORT EXTI15_10_IRQHandler   
  63. IMPORT RTCAlarm_IRQHandler   
  64. IMPORT USBWakeUp_IRQHandler   
  65. IMPORT TIM8_BRK_IRQHandler   
  66. IMPORT TIM8_UP_IRQHandler   
  67. IMPORT TIM8_TRG_COM_IRQHandler   
  68. IMPORT TIM8_CC_IRQHandler   
  69. IMPORT ADC3_IRQHandler   
  70. IMPORT FSMC_IRQHandler   
  71. IMPORT SDIO_IRQHandler   
  72. IMPORT TIM5_IRQHandler   
  73. IMPORT SPI3_IRQHandler   
  74. IMPORT UART4_IRQHandler   
  75. IMPORT UART5_IRQHandler   
  76. IMPORT TIM6_IRQHandler   
  77. IMPORT TIM7_IRQHandler   
  78. IMPORT DMA2_Channel1_IRQHandler  
  79. IMPORT DMA2_Channel2_IRQHandler  
  80. IMPORT DMA2_Channel3_IRQHandler   
  81. IMPORT DMA2_Channel4_5_IRQHandler   
  82. AREA RESET, DATA, READONLY   
  83. EXPORT __Vectors   
  84. __Vectors   
  85. DCD __initial_sp   
  86. DCD Reset_Handler   
  87. DCD NMIException   
  88. DCD HardFaultException   
  89. DCD MemManageException   
  90. DCD BusFaultException   
  91. DCD UsageFaultException   
  92. DCD 0   
  93. DCD 0   
  94. DCD 0   
  95. DCD 0   
  96. DCD SVCHandler   
  97. DCD DebugMonitor   
  98. DCD 0   
  99. DCD PendSVC   
  100. DCD SysTickHandler   
  101. DCD WWDG_IRQHandler   
  102. DCD PVD_IRQHandler   
  103. DCD TAMPER_IRQHandler   
  104. DCD RTC_IRQHandler   
  105. DCD FLASH_IRQHandler   
  106. DCD RCC_IRQHandler   
  107. DCD EXTI0_IRQHandler   
  108. DCD EXTI1_IRQHandler   
  109. DCD EXTI2_IRQHandler   
  110. DCD EXTI3_IRQHandler   
  111. DCD EXTI4_IRQHandler   
  112. DCD DMA1_Channel1_IRQHandler   
  113. DCD DMA1_Channel2_IRQHandler   
  114. DCD DMA1_Channel3_IRQHandler   
  115. DCD DMA1_Channel4_IRQHandler   
  116. DCD DMA1_Channel5_IRQHandler   
  117. DCD DMA1_Channel6_IRQHandler   
  118. DCD DMA1_Channel7_IRQHandler   
  119. DCD ADC1_2_IRQHandler   
  120. DCD USB_HP_CAN_TX_IRQHandler   
  121. DCD USB_LP_CAN_RX0_IRQHandler   
  122. DCD CAN_RX1_IRQHandler   
  123. DCD CAN_SCE_IRQHandler   
  124. DCD EXTI9_5_IRQHandler   
  125. DCD TIM1_BRK_IRQHandler   
  126. DCD TIM1_UP_IRQHandler   
  127. DCD TIM1_TRG_COM_IRQHandler   
  128. DCD TIM1_CC_IRQHandler   
  129. DCD TIM2_IRQHandler   
  130. DCD TIM3_IRQHandler   
  131. DCD TIM4_IRQHandler  
  132. DCD I2C1_EV_IRQHandler   
  133. DCD I2C1_ER_IRQHandler   
  134. DCD I2C2_EV_IRQHandler   
  135. DCD I2C2_ER_IRQHandler   
  136. DCD SPI1_IRQHandler   
  137. DCD SPI2_IRQHandler   
  138. DCD USART1_IRQHandler   
  139. DCD USART2_IRQHandler   
  140. DCD USART3_IRQHandler   
  141. DCD EXTI15_10_IRQHandler   
  142. DCD RTCAlarm_IRQHandler   
  143. DCD USBWakeUp_IRQHandler   
  144. DCD TIM8_BRK_IRQHandler   
  145. DCD TIM8_UP_IRQHandler   
  146. DCD TIM8_TRG_COM_IRQHandler   
  147. DCD TIM8_CC_IRQHandler   
  148. DCD ADC3_IRQHandler   
  149. DCD FSMC_IRQHandler   
  150. DCD SDIO_IRQHandler   
  151. DCD TIM5_IRQHandler   
  152. DCD SPI3_IRQHandler   
  153. DCD UART4_IRQHandler   
  154. DCD UART5_IRQHandler  
  155. DCD TIM6_IRQHandler   
  156. DCD TIM7_IRQHandler   
  157. DCD DMA2_Channel1_IRQHandler   
  158. DCD DMA2_Channel2_IRQHandler   
  159. DCD DMA2_Channel3_IRQHandler   
  160. DCD DMA2_Channel4_5_IRQHandler   
  161. AREA |.text|, CODE, READONLY   
  162. Reset_Handler PROC   
  163. EXPORT Reset_Handler   
  164. IF DATA_IN_ExtSRAM == 1  
  165. LDR R0,= 0x00000114  
  166. LDR R1,= 0x40021014  
  167. STR R0,[R1]   
  168. LDR R0,= 0x000001E0   
  169. LDR R1,= 0x40021018   
  170. STR R0,[R1]   
  171. LDR R0,= 0x44BB44BB   
  172. LDR R1,= 0x40011400   
  173. STR R0,[R1]   
  174. LDR R0,= 0xBBBBBBBB   
  175. LDR R1,= 0x40011404  
  176. STR R0,[R1]  
  177. LDR R0,= 0xB44444BB   
  178. LDR R1,= 0x40011800   
  179. STR R0,[R1]   
  180. LDR R0,= 0xBBBBBBBB   
  181. LDR R1,= 0x40011804   
  182. STR R0,[R1]   
  183. LDR R0,= 0x44BBBBBB   
  184. LDR R1,= 0x40011C00   
  185. STR R0,[R1]   
  186. LDR R0,= 0xBBBB4444   
  187. LDR R1,= 0x40011C04   
  188. STR R0,[R1]   
  189. LDR R0,= 0x44BBBBBB   
  190. LDR R1,= 0x40012000   
  191. STR R0,[R1]   
  192. LDR R0,= 0x44444B44   
  193. LDR R1,= 0x40012004   
  194. STR R0,[R1]   
  195. LDR R0,= 0x00001011   
  196. LDR R1,= 0xA0000010   
  197. STR R0,[R1]   
  198. LDR R0,= 0x00000200   
  199. LDR R1,= 0xA0000014   
  200. STR R0,[R1]   
  201. ENDIF   
  202. IMPORT __main   
  203. LDR R0, =__main   
  204. BX R0   
  205. ENDP   
  206. ALIGN   
  207. IF :DEF:__MICROLIB   
  208. EXPORT __initial_sp   
  209. EXPORT __heap_base   
  210. EXPORT __heap_limit   
  211. ELSE   
  212. IMPORT __use_two_region_memory   
  213. EXPORT __user_initial_stackheap   
  214. __user_initial_stackheap   
  215. LDR R0, = Heap_Mem   
  216. LDR R1, = (Stack_Mem + Stack_Size)   
  217. LDR R2, = (Heap_Mem + Heap_Size)   
  218. LDR R3, = Stack_Mem   
  219. BX LR   
  220. ALIGN   
  221. ENDIF   
  222. END   
  223. ENDIF   
  224. END   

如程序清单一,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的世界。