原文在此:http://www.cnblogs.com/mddblog/p/4920063.html
概述
在嵌入式系統中,啟動文件是整個系統非常關鍵的部分,它會進行一些底層的初始化,構建程序運行必要的環境,比如堆棧初始化,變量初始化等。如果啟動文件出現錯誤,則整個系統就跑不起來,因此研究啟動文件非常必要。
在keil中,啟動文件由匯編代碼編寫,一般命名為startup_xxx.s,xxx為支持的某種芯片,比如可以是lpc15xx(NXP的LPC15xx系列)、MK60D10(飛思卡爾)、stm32f10x(意法半導體stm32f10x系列)等Cortext-M0/M3/M4內核芯片。它們的代碼格式非常相近,根據啟動文件代碼由上到下的編寫順序.
可以將其分為以下5個典型部分:
1.堆棧空間定義;
2.存放中斷向量表;
3.復位中斷函數(Reset_Handler);
4.其它中斷異常服務函數,以及弱[WEAK]聲明;
5.將堆棧地址傳遞給庫函數,利用庫函數初始化堆棧,和庫函數自身初始化。
5個部分具體說明如下:
1.堆棧空間定義
如下圖所示,定義了棧大小Stack_Size = 0X200,即512字節;堆大小Heap_Size = 0X100,256字節。還定義了三個標號:__initial_sp(棧頂)、__heap_base(堆起始地址)和__heap_limit(堆終止地址),它們的空間由SPACE關鍵字來申請,並記作Stack_Mem和Heap_Mem。
通過這些我們可以很容易的知道堆棧的大小,但是它們的絕對地址或者說基地址僅僅從這里是得不到的。編譯器編譯完工程后,根據生成.bss段(比如未初始化的全局變量)和.data段(比如初始化的全局變量)的大小以及RAM的起始地址,來計算堆棧的基地址。
舉個例子:
一個芯片的RAM起始地址為0x0200_0000,RAM大小為0x500字節,程序編譯后.bss段為0x100個字節,.data段為0x100個字節。堆棧大小定義如上圖。則:
A:堆起始地址 __heap_base==Heap_Mem==0x0200_0200;
B:堆終止地址即棧底 __heap_limit==Stack_Mem==0x0200_0300;
C:棧頂地址 __initial_sp==0x0200_0500(棧是向下生長,棧頂處於RAM最大地址處)。
其實,我可以在.map文件中查看堆棧的大小和基地址,如下圖所示:
2.存放中斷向量表
在啟動代碼中,會見到許多由DCD申請空間存放的一個個函數入口,即中斷向量表,如下圖所示,只列出了部分。
關鍵字DCD代表申請一個字的空間,后面的函數名即為中斷服務函數入口地址。另外中斷向量表一般存放在Flash 0地址。
另外,對於NXP微控制器,均實現了芯片的加密,加密的設置在向量表的結尾處,具體地址為0x02FC處。通過在此地址存放不同的值實現是否加密或者加密的等級。加密分為三個等級,CRP1:0x12345678;CRP2:0x87654321;CRP3:0x43218765。至於每個等級的具體說明請參考芯片用戶手冊。下面說一下加密步驟,以CRP1為例:
首先將下圖中0xFFFFFFFF,修改為0x12345678。
其次,圖中IF :LNOT::DEF:NOCRP表示如果沒有定義宏NOCRP則執行下面的代碼,那么必須保證匯編中沒有定義NOCRP宏。即保證下圖中Define:一欄中沒有定義NOCRP即可。
3. 復位中斷函數(Reset_Handler)
程序上電后,首先加載SP和PC,ARM規定從0地址處加載SP,從偏移為4的地址(0x00000004)處加載PC。然后將程序控制權交給程序。我們知道0地址處存放__initial_sp,0x00000004地址處存放Reset_Handler,加載PC后,程序跳轉到Reset_Handler開始運行。Reset_Handler函數體如下圖所示:
首先調用SystemInit函數來初始化系統的各種時鍾,然后調用__main函數(由KEIL微庫或者C庫實現),在__main函數中:.data段數據的初始化->.bss段變量清零->設置堆棧指針->庫函數初始化(比如常用的malloc函數)->如果必要會設置main函數的argc和argv兩個參數->調用用戶main函數->退出。
4.其它中斷異常服務函數,以及弱[WEAK]聲明
如上圖所示,這里的中斷服務函數是弱聲明的(由[WEAK]關鍵字標注)。所謂弱聲明,即:如果用戶定義了相同的函數則此處函數失效而使用用戶定義的中斷服務函數。這樣是為了防止用戶使能了中斷而沒有中斷服務函數,從而造成程序崩潰。假設使能了中斷,而用戶又沒有定義中斷服務函數則會進入默認中斷,如下圖所示,默認中斷為死循環(死循環與程序崩潰不是一個概念)。
5.將堆棧地址傳遞給庫函數
第三步驟中,調用__main函數,然后__main調用庫函數初始化堆棧,但庫函數並不知道堆棧的大小,因此我們需要告訴它,具體做法就是傳遞參數或聲明標號。
下圖為具體做法,可以看到第一行為:
IF :DEF:__MICROLIB
是條件編譯選項,如果定義__MICROLIB,則編譯圖中紅線上面部分,否則編譯紅線下面部分。那么就分2種情況。
2種情況的選擇可以如下實現:
如果勾選【Options for Target】->【Target】->【Use MicroLIB】,如下圖所示。即使用微庫,則__MICROLIB會被定義,編譯器編譯紅線以上代碼。用EXPORT聲明 __initial_sp、__heap_base和__heap_limit。
如果不勾選【Use MicroLIB】,則缺省使用KEIL C庫,上圖紅線以下會參與編譯,KEIL C庫函數會調用__user_initial_stackheap,通過R0~R3將堆棧以參數形式傳遞給KEIL C庫。