文章目錄
簡介我們?cè)谧鰡纹瑱C(jī)編程的時(shí)候,大部分都是用KEIL自帶的啟動(dòng)文件來使程序進(jìn)入C語言main函數(shù),然后進(jìn)行C語言編程開發(fā)的工作。那么這個(gè)啟動(dòng)文件到底做了什么呢?相信朋友們肯定和我一樣好奇,想弄明白啟動(dòng)文件到底都干了些什么。那么本文就來介紹下,本文介紹stm32啟動(dòng)文件匯編代碼,對(duì)應(yīng)文件名startup_stm32f10x_hd.s。其他Cortex-M3內(nèi)核的單片機(jī)都是大同小異的。 其實(shí)啟動(dòng)文件存在的目的就是構(gòu)建可以供C語言代碼運(yùn)行的工作環(huán)境,比如傳遞參數(shù)時(shí)需要的??臻g初始化,動(dòng)態(tài)分配內(nèi)存時(shí)的堆初始化,一些初始化為0的變量空間的初始化等等。如果這些沒有配置好,無法達(dá)到C語言代碼運(yùn)行的工作環(huán)境,那么后面的C語言代碼執(zhí)行的結(jié)果就是不對(duì)的,也會(huì)導(dǎo)致總個(gè)系統(tǒng)無法工作。所以啟動(dòng)文件很重要,也正是因?yàn)槲覀冇X得它重要,所以才想搞懂它。 startup_stm32f10x_hd.s啟動(dòng)文件中的匯編代碼主要做了下面5個(gè)工作。 1.堆??臻g的定義; 2.初始化中斷向量表; 3.復(fù)位中斷函數(shù)(Reset_Handler){系統(tǒng)初始化,然后進(jìn)入main函數(shù)}; 4.中斷函數(shù)的弱(WEAK)聲明 5.用戶棧和堆初始化 再介紹這5個(gè)部分的詳細(xì)代碼前,這里已經(jīng)先總結(jié)了啟動(dòng)文件中用到的匯編代碼與編譯器相關(guān)的指令,下面就先來介紹下這些指令。 啟動(dòng)文件匯編代碼相關(guān)指令啟動(dòng)文件代碼主要由ARM指令代碼和與編譯器相關(guān)的匯編指令組成,下表羅列了啟動(dòng)文件中用到的相關(guān)匯編指令。 上面只是對(duì)指令做了簡要的說明,后面代碼用到時(shí)我們?cè)僖灰恢v解。我們也可以通過在代碼中選中指令,按F1按鍵調(diào)出幫助說明,查看具體指令的相關(guān)介紹。 接下來我們就對(duì)代碼做詳細(xì)分析吧。 一、堆棧空間的定義首先這里使用指令EQU定義了一個(gè)數(shù)值常量符號(hào)Stack_Size指明棧大小為0x00000400即1K,這個(gè)值可以根據(jù)實(shí)際需求更改。使用QEU定義常數(shù),類似與C語言的#define定義常數(shù)。 然后這里又使用指令A(yù)REA定義了一個(gè)未初始化,可以讀寫并要求8字節(jié)邊界對(duì)齊的段,段名,為STACK。這里可以為段選擇任何段名。但是,以一個(gè)數(shù)字開始的名稱必須包含在豎杠號(hào)內(nèi),否則會(huì)產(chǎn)生一個(gè)缺失段名錯(cuò)誤。例如, |1_DataArea|。有些名稱是習(xí)慣性的名稱。例如,|.text| 用于表示由 C 編譯程序產(chǎn)生的代碼段,或用于以某種方式與 C 庫關(guān)聯(lián)的代碼段。這里的NOINIT表示數(shù)據(jù)段是未初始化的或初始化為零。其只包含零初始化的空間保留命令 SPACE 或 DCB, DCD, DCDU, DCQ, DCQU, DCW 或 DCWU 。可以決定在鏈接時(shí) AREA 是未初始化的,還是零初始化的,后面一條指令是SPACE,所以這里要初始化為0,READWRITE指明段可以讀寫,ALIGN=3指明段要在2^3=8字節(jié)邊界上對(duì)齊。 再然后使用SPACE定義了一個(gè)初始化為0的存儲(chǔ)塊Stack_Mem,可以理解為存儲(chǔ)塊是歸別到段里的。標(biāo)號(hào)__initial_sp緊挨著SPACE語句放置,表示棧的結(jié)束地址,即棧頂?shù)刂?,棧是由高向低生長的。 同樣的堆也是這樣定義的,只是這里先是指明了堆開始__heap_base(堆起始地址),再指明堆存儲(chǔ)塊,最后指明__heap_limit(堆終止地址)。堆是由低向高生長的,跟棧的生長方向相反。堆主要用來動(dòng)態(tài)內(nèi)存的分配,像malloc()函數(shù)申請(qǐng)的內(nèi)存就在堆上面。這里堆默認(rèn)大小為0x00000200即512字節(jié),一般的程序中我們很少用到malloc函數(shù),所以這里也就不做過多更改,如果要使用malloc函數(shù),需要將此處堆大小定義的值根據(jù)需求改大。 后面的PRESERVE8,指明當(dāng)前文件的堆棧按照8字節(jié)對(duì)齊。 二、初始化中斷向量表THUMB指示匯編器將隨后的指令解釋為16位的Thumb指令。Cortex-M3使用的是Thumb-2指令集,是一種介于Thumb指令集和ARM指令集。ARM指令集全部是32位的,Thumb指令集全部是16位的,Thumb-2指令集是即有部分16位Thumb指令的也有部分32位的ARM指令。 后面定義一個(gè)只讀數(shù)據(jù)段RESET,用于保存中斷向量表,和三個(gè)標(biāo)號(hào)__Vectors(向量表開始)、__Vectors_End(向量表結(jié)束)和__Vectors_Size(向量表大?。┎⑹褂肊XPORT指明其具有全局性。這樣可以使在其他文件中訪問此文件中的這三標(biāo)號(hào)。 DCD 命令分配一個(gè)或多個(gè)字的存儲(chǔ)器,在四個(gè)字節(jié)的邊界上對(duì)齊,并定義存儲(chǔ)器的運(yùn)行時(shí)初值。 __Vectors DCD __initial_sp ; Top of Stack 這里就指示了段的開始為向量表的開始,標(biāo)號(hào)__Vectors(向量表開始)編譯器會(huì)根據(jù)不同單片機(jī)為其指定值,比如stm32單片機(jī)就是0x08000000,然后我們定義的RESET段就被分在了0x08000000開始的地址處,其結(jié)束位置就是從0x08000000開始依次加4個(gè)字節(jié),因?yàn)檫@里每個(gè)DCD命令占存儲(chǔ)器4個(gè)字節(jié),這樣一直到__Vectors_End(向量表結(jié)束),__Vectors_Size(向量表大小)就是這個(gè)RESET段所占大小。比如復(fù)位的時(shí)候,復(fù)位中斷來了,就從這個(gè)段的第二個(gè)存儲(chǔ)地址0x08000004處對(duì)應(yīng)的值0x08000144作為復(fù)位函數(shù)Reset_Handler的地址。 三、復(fù)位中斷函數(shù)這里先使用AREA定義了一個(gè)只讀代碼段。這里的標(biāo)號(hào)Reset_Handler就代表了復(fù)位函數(shù)的入口地址(函數(shù)名),使用PROC標(biāo)記函數(shù)入口,使用ENDP標(biāo)記函數(shù)結(jié)束。 EXPORT Reset_Handler [WEAK] 這里EXPORT聲明Reset_Handler是一個(gè)全局性的。WEAK表示其他地方?jīng)]有定義Reset_Handler函數(shù)時(shí),就將此處作為Reset_Handler函數(shù)的實(shí)例。IMPORT用于指示如果在當(dāng)前匯編代碼中未找到其引用,則不導(dǎo)入該名稱,很顯然,下面有用到__main和SystemInit。 從上那些代碼我就就知道,程序上電后,從0x08000000地址處加載SP,上電復(fù)位從0x08000004處加載PC,0x08000004處的地址就是復(fù)位函數(shù)的地址,然后復(fù)位函數(shù)里面先調(diào)用SystemInit函數(shù)來初始化系統(tǒng)的各種時(shí)鐘,再調(diào)用__main函數(shù)(由編譯器實(shí)現(xiàn))。 復(fù)位函數(shù)中用到的Thumb-2指令介紹如下: LDR從存儲(chǔ)器中加載字到一個(gè)寄存器中 BL跳轉(zhuǎn)到由寄存器/標(biāo)號(hào)給出的地址,并把跳轉(zhuǎn)前的下條指令地址保存到LR BLX跳轉(zhuǎn)到由寄存器給出的地址,并根據(jù)寄存器的LSE確定處理器的狀態(tài),還要把跳轉(zhuǎn)前的下條指令地址保存到LR BX跳轉(zhuǎn)到由寄存器/標(biāo)號(hào)給出的地址,不用返回 四、中斷函數(shù)的弱(WEAK)聲明這里定義了各種中斷函數(shù),使用PROC表示函數(shù)開始,ENDP表示函數(shù)結(jié)束,EXPORT說明函數(shù)的全局性,WEAK說明如果其他地方?jīng)]有定義這個(gè)函數(shù),那么就把此處作為函數(shù)的實(shí)例。這里函數(shù)的代碼都是B . 。這里的B表示跳轉(zhuǎn)到一個(gè)標(biāo)號(hào),這里跳轉(zhuǎn)到一個(gè)'.',即表示無限循環(huán),所以我們?cè)趯慍語言程序時(shí)如果沒有寫中斷函數(shù),那么對(duì)應(yīng)的中斷來了會(huì)運(yùn)行這里到中斷函數(shù),即B .那么將無限循環(huán)在此。 ALIGN命令通過用零或空指令NOP填充,來使當(dāng)前位置與一個(gè)指定的邊界對(duì)齊。使用ALIGN來確保數(shù)據(jù)和代碼對(duì)齊到適當(dāng)?shù)倪吔缟?。這里使用了默認(rèn)為字對(duì)齊方式。 五、用戶棧和堆初始化棧和堆初始化部分,這里IF、ELSE、ENDIF是條件編譯。 先判斷是否定義了__MICROLIB ,如果定義了則賦予標(biāo)號(hào)__initial_sp(棧頂?shù)刂罚?、__heap_base(堆起始地址)、__heap_limit(堆結(jié)束地址)全局屬性,可供外部文件調(diào)用,這樣我們使用到molloc函數(shù)申請(qǐng)的空間就是從這里有關(guān)堆的兩個(gè)標(biāo)號(hào)之間的內(nèi)存中申請(qǐng)的。如果沒有定義(實(shí)際的默認(rèn)情況就是我們沒定義__MICROLIB)則通過 IMPORT __use_two_region_memory 表明使用雙段模式,即一部分儲(chǔ)存區(qū)用于??臻g,其他的存儲(chǔ)區(qū)用于堆空間,堆區(qū)空間可以為0,但是,這樣就不能調(diào)用malloc()內(nèi)存分配函數(shù)。然后使用__user_initial_stackheap標(biāo)號(hào)處的代碼用于初始化用戶堆棧,這部分由編譯器提供的__main來調(diào)用。 END 命令指示匯編器,已到達(dá)一個(gè)源文件的末尾。 關(guān)于STM32單片機(jī)的Keil啟動(dòng)文件匯編代碼就講解完了,大家有沒有看明白呢,歡迎評(píng)論交流,如果覺得我這篇文章寫到很好到話,就轉(zhuǎn)發(fā)出去分享給更多到朋友吧。最后歡迎大家點(diǎn)贊評(píng)論轉(zhuǎn)發(fā)收藏,跟多好文章歡迎關(guān)注我——單片機(jī)嵌入式愛好者。 |
|