FSMC使用之外擴RAM的妙用以及啓動文件解析

地址: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的世界。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章