地址:http://www.eepw.com.cn/article/182411.htm
相對於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”,其中註釋爲行號
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
|
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地址後。而“READWRITE”表示該段爲“可讀寫”屬性,可知“可讀寫”段保存於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的世界。