概述
在嵌入式系統(tǒng)中,啟動文件是整個系統(tǒng)非常關(guān)鍵的部分世分,它會進行一些底層的初始化,構(gòu)建程序運行必要的環(huán)境缀辩,比如堆棧初始化臭埋,變量初始化等。如果啟動文件出現(xiàn)錯誤臀玄,則整個系統(tǒng)就跑不起來瓢阴,因此研究啟動文件非常必要。
在keil中健无,啟動文件由匯編代碼編寫荣恐,一般命名為startup_xxx.s,xxx為支持的某種芯片,比如可以是lpc15xx(NXP的LPC15xx系列)叠穆、MK60D10(飛思卡爾)少漆、stm32f10x(意法半導(dǎo)體stm32f10x系列)等Cortext-M0/M3/M4內(nèi)核芯片。它們的代碼格式非常相近硼被,根據(jù)啟動文件代碼由上到下的編寫順序.
可以將其分為以下5個典型部分:
- 堆検舅穑空間定義;
- 存放中斷向量表嚷硫;
- 復(fù)位中斷函數(shù)(Reset_Handler)检访;
- 其它中斷異常服務(wù)函數(shù),以及弱[WEAK]聲明仔掸;
- 將堆棧地址傳遞給庫函數(shù)脆贵,利用庫函數(shù)初始化堆棧,和庫函數(shù)自身初始化嘉汰。
5個部分具體說明如下:
1 堆椀べ鳎空間定義
如下圖所示,定義了棧大小Stack_Size = 0X200
鞋怀,即512字節(jié)双泪;堆大小Heap_Size = 0X100
,256字節(jié)。還定義了三個標號:__initial_sp(棧頂)
密似、__heap_base(堆起始地址)
和__heap_limit(堆終止地址)
焙矛,它們的空間由SPACE
關(guān)鍵字來申請,并記作Stack_Mem
和Heap_Mem
残腌。
通過這些我們可以很容易的知道堆棧的大小村斟,但是它們的絕對地址或者說基地址僅僅從這里是得不到的。編譯器編譯完工程后抛猫,根據(jù)生成.bss段(比如未初始化的全局變量)和.data段(比如初始化的全局變量)的大小以及RAM的起始地址蟆盹,來計算堆棧的基地址。
舉個例子:
一個芯片的RAM起始地址為0x0200_0000闺金,RAM大小為0x500字節(jié)逾滥,程序編譯后.bss段為0x100個字節(jié),.data段為0x100個字節(jié)败匹。堆棧大小定義如上圖寨昙。則:
A:堆起始地址
__heap_base==Heap_Mem==0x0200_0200;
B:堆終止地址即棧底
__heap_limit==Stack_Mem==0x0200_0300掀亩;
C:棧頂?shù)刂?code>__initial_sp==0x0200_0500(棧是向下生長舔哪,棧頂處于RAM最大地址處)。
其實槽棍,我可以在.map文件中查看堆棧的大小和基地址捉蚤,如下圖所示:
2 存放中斷向量表
在啟動代碼中抬驴,會見到許多由DCD申請空間存放的一個個函數(shù)入口,即中斷向量表缆巧,如下圖所示怎爵,只列出了部分。
關(guān)鍵字DCD代表申請一個字的空間盅蝗,后面的函數(shù)名即為中斷服務(wù)函數(shù)入口地址。另外中斷向量表一般存放在Flash
0地址姆蘸。
另外墩莫,對于NXP微控制器,均實現(xiàn)了芯片的加密逞敷,加密的設(shè)置在向量表的結(jié)尾處狂秦,具體地址為0x02FC處。通過在此地址存放不同的值實現(xiàn)是否加密或者加密的等級推捐。加密分為三個等級裂问,CRP1:0x12345678;CRP2:0x87654321牛柒;CRP3:0x43218765堪簿。
至于每個等級的具體說明請參考芯片用戶手冊。下面說一下加密步驟皮壁,以CRP1為例:
首先將上圖中0xFFFFFFFF椭更,修改為0x12345678。
其次蛾魄,圖中IF :LNOT::DEF:NOCRP
表示如果沒有定義宏NOCRP
則執(zhí)行下面的代碼虑瀑,那么必須保證匯編中沒有定義NOCRP
宏。即保證下圖中Define:
一欄中沒有定義NOCRP
即可滴须。
3 復(fù)位中斷函數(shù)(Reset_Handler)
程序上電后舌狗,首先加載SP和PC,ARM規(guī)定從0地址處加載SP扔水,從偏移為4的地址(0x00000004)處加載PC痛侍。然后將程序控制權(quán)交給程序。我們知道0地址處存放__initial_sp
铭污,0x00000004地址處存放Reset_Handler
恋日,加載PC后,程序跳轉(zhuǎn)到Reset_Handler
開始運行嘹狞。Reset_Handler
函數(shù)體如下圖所示:
首先調(diào)用SystemInit
函數(shù)來初始化系統(tǒng)的各種時鐘岂膳,然后調(diào)用__main
函數(shù)(由KEIL微庫或者C庫實現(xiàn)),在__main
函數(shù)中:.data
段數(shù)據(jù)的初始化->.bss
段變量清零->設(shè)置堆棧指針->庫函數(shù)初始化(比如常用的malloc
函數(shù))->如果必要會設(shè)置main函數(shù)的argc和argv兩個參數(shù)->調(diào)用用戶main函數(shù)->退出磅网。
4 其它中斷異常服務(wù)函數(shù)谈截,以及弱[WEAK]聲明
如上圖所示,這里的中斷服務(wù)函數(shù)是弱聲明的(由[WEAK]關(guān)鍵字標注)。所謂弱聲明簸喂,即:如果用戶定義了相同的函數(shù)則此處函數(shù)失效而使用用戶定義的中斷服務(wù)函數(shù)毙死。這樣是為了防止用戶使能了中斷而沒有中斷服務(wù)函數(shù),從而造成程序崩潰喻鳄。假設(shè)使能了中斷扼倘,而用戶又沒有定義中斷服務(wù)函數(shù)則會進入默認中斷,如下圖所示除呵,默認中斷為死循環(huán)(死循環(huán)與程序崩潰不是一個概念)再菊。
5 將堆棧地址傳遞給庫函數(shù)
第三步驟中,調(diào)用__main
函數(shù)颜曾,然后__main
調(diào)用庫函數(shù)初始化堆棧纠拔,但庫函數(shù)并不知道堆棧的大小,因此我們需要告訴它泛豪,具體做法就是傳遞參數(shù)或聲明標號稠诲。
下圖為具體做法,可以看到第一行為:
IF :DEF:__MICROLIB
是條件編譯選項诡曙,如果定義__MICROLIB
臀叙,則編譯圖中紅線上面部分,否則編譯紅線下面部分岗仑。那么就分2種情況匹耕。
2種情況的選擇可以如下實現(xiàn):
如果勾選【Options for Target】
->【Target】
->【Use MicroLIB】
,如下圖所示。即使用微庫荠雕,則__MICROLIB
會被定義稳其,編譯器編譯紅線以上代碼。用EXPORT聲明 __initial_sp
炸卑、__heap_base
和__heap_limit
既鞠。
如果不勾選【Use MicroLIB】,則缺省使用KEIL C庫盖文,上圖紅線以下會參與編譯嘱蛋,KEIL C庫函數(shù)會調(diào)用__user_initial_stackheap
,通過R0~R3將堆棧以參數(shù)形式傳遞給KEIL C庫五续。