????本文對STM32啟動文件startup_stm32f10x_hd.s的代碼進行講解,此文件的代碼在任何一個STM32F10x工程中都可以找到。
Stack——棧
?
Stack_Size EQU 0x00000400 AREA STACK, NOINIT, READWRITE, ALIGN= Stack_Mem SPACE Stack_Size __initial_sp
?
????開辟棧的大小為 0X00000400(1KB),名字為 STACK, NOINIT 即不初始化,可讀可寫, 8(2^3)字節(jié)對齊。
????棧的作用是用于局部變量,函數(shù)調(diào)用,函數(shù)形參等的開銷,棧的大小不能超過內(nèi)部SRAM 的大小。如果編寫的程序比較大,定義的局部變量很多,那么就需要修改棧的大小。如果某一天,你寫的程序出現(xiàn)了莫名奇怪的錯誤,并進入了硬 fault 的時候,這時你就要考慮下是不是棧不夠大,溢出了。
??? EQU:宏定義的偽指令,相當于等于,類似于C 中的 define。
??? AREA:告訴匯編器匯編一個新的代碼段或者數(shù)據(jù)段。STACK 表示段名,這個可以任意命名;NOINIT 表示不初始化;READWRITE 表示可讀可寫, ALIGN=3,表示按照 2^3對齊,即 8 字節(jié)對齊。
??? SPACE:用于分配一定大小的內(nèi)存空間,單位為字節(jié)。這里指定大小等于 Stack_Size。
????標號__initial_sp 緊挨著 SPACE 語句放置,表示棧的結(jié)束地址,即棧頂?shù)刂?,棧是由高向低生長的。
Heap——堆
?
Heap_Size EQU 0x00000200 AREA HEAP, NOINIT, READWRITE, ALIGN=3 __heap_base Heap_Mem SPACE Heap_Size heap_limit
?
????開辟堆的大小為 0X00000200(512 字節(jié)),名字為 HEAP, NOINIT 即不初始化,可讀可寫, 8(2^3)字節(jié)對齊。__heap_base 表示對的起始地址, __heap_limit 表示堆的結(jié)束地址。堆是由低向高生長的,跟棧的生長方向相反。
????堆主要用來動態(tài)內(nèi)存的分配,像 malloc()函數(shù)申請的內(nèi)存就在堆上面。這個在 STM32里面用的比較少。
?
PRESERVE8 THUMB
?
??? PRESERVE8:指定當前文件的堆棧按照 8 字節(jié)對齊。
??? THUMB:表示后面指令兼容 THUMB 指令。THUBM 是 ARM 以前的指令集, 16bit,現(xiàn)在 Cortex-M 系列的都使用 THUMB-2 指令集, THUMB-2 是 32 位的,兼容 16 位和 32 位的指令,是 THUMB 的超集。
向量表
?
AREA RESET, DATA, READONLY EXPORT __Vectors EXPORT __Vectors_End EXPORT __Vectors_Size
?
????定義一個數(shù)據(jù)段,名字為 RESET,可讀。并聲明 __Vectors、 __Vectors_End 和__Vectors_Size 這三個標號具有全局屬性,可供外部的文件調(diào)用。
??? EXPORT:聲明一個標號可被外部的文件使用,使標號具有全局屬性。如果是 IAR 編譯器,則使用的是 GLOBAL 這個指令。
????當內(nèi)核響應了一個發(fā)生的異常后,對應的異常服務例程(ESR)就會執(zhí)行。為了決定 ESR的入口地址, 內(nèi)核使用了―向量表查表機制‖。這里使用一張向量表。向量表其實是一個WORD(32 位整數(shù))數(shù)組,每個下標對應一種異常,該下標元素的值則是該 ESR 的入口地址。向量表在地址空間中的位置是可以設置的,通過 NVIC 中的一個重定位寄存器來指出向量表的地址。在復位后,該寄存器的值為 0。因此,在地址 0 (即 FLASH 地址 0) 處必須包含一張向量表,用于初始時的異常分配。要注意的是這里有個另類:0 號類型并不是什么入口地址,而是給出了復位后 MSP 的初值。下圖是F103的向量表。
?
__Vectors DCD __initial_sp ;棧頂?shù)刂?DCD Reset_Handler ;復位程序地址 DCD NMI_Handler DCD HardFault_Handler DCD MemManage_Handler DCD BusFault_Handler DCD UsageFault_Handler DCD 0 ; 0 表示保留 DCD 0 DCD 0 DCD 0 DCD SVC_Handler DCD DebugMon_Handler DCD 0 DCD PendSV_Handler DCD SysTick_Handler ;外部中斷開始 DCD WWDG_IRQHandler DCD PVD_IRQHandler DCD TAMPER_IRQHandler ;限于篇幅,中間代碼省略 DCD DMA2_Channel2_IRQHandler DCD DMA2_Channel3_IRQHandler DCD DMA2_Channel4_5_IRQHandler __Vectors_End __Vectors_Size EQU __Vectors_End - __Vectors
?
????__Vectors 為向量表起始地址, __Vectors_End 為向量表結(jié)束地址,兩個相減即可算出向量表大小。
????向量表從 FLASH 的 0 地址開始放置,以 4 個字節(jié)為一個單位,地址 0 存放的是棧頂?shù)刂罚?0X04 存放的是復位程序的地址,以此類推。從代碼上看,向量表中存放的都是中斷服務函數(shù)的函數(shù)名,可我們知道 C 語言中的函數(shù)名就是一個地址。
??? DCD:分配一個或者多個以字為單位的內(nèi)存,以四字節(jié)對齊,并要求初始化這些內(nèi)存。在向量表中, DCD 分配了一堆內(nèi)存,并且以 ESR 的入口地址初始化它們。
復位程序
?
AREA |.text|, CODE, READONLY
?
????定義一個名稱為.text 的代碼段,可讀。
?
Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT SystemInit IMPORT __main LDR R0, =SystemInit BLX R0 LDR R0, =__main BX R0 ENDP
?
????復位子程序是系統(tǒng)上電后第一個執(zhí)行的程序,調(diào)用 SystemInit 函數(shù)初始化系統(tǒng)時鐘,然后調(diào)用 C 庫函數(shù)_mian,最終調(diào)用 main 函數(shù)去到 C 的世界。
??? WEAK:表示弱定義,如果外部文件優(yōu)先定義了該標號則首先引用該標號,如果外部文件沒有聲明也不會出錯。這里表示復位子程序可以由用戶在其他文件重新實現(xiàn),這里并不是唯一的。
??? IMPORT:表示該標號來自外部文件,跟 C 語言中的 EXTERN 關鍵字類似。這里表示 SystemInit 和__main 這兩個函數(shù)均來自外部的文件。
??? SystemInit()是一個標準的庫函數(shù),在 system_stm32f10x.c 這個庫文件中定義。主要作用是配置系統(tǒng)時鐘,這里調(diào)用這個函數(shù)之后,單片機的系統(tǒng)時鐘配被配置為 72M。__main 是一個標準的 C 庫函數(shù),主要作用是初始化用戶堆棧,并在函數(shù)的最后調(diào)用main 函數(shù)去到 C 的世界。這就是為什么我們寫的程序都有一個 main 函數(shù)的原因。
?????LDR、 BLX、 BX 是 CM4 內(nèi)核的指令,可在《CM3 權威指南 CnR2》第四章-指令集里面查詢到,具體作用見下表:
?中斷服務程序
????在啟動文件里面已經(jīng)幫我們寫好所有中斷的中斷服務函數(shù),跟我們平時寫的中斷服務函數(shù)不一樣的就是這些函數(shù)都是空的,真正的中斷服務程序需要我們在外部的 C 文件里面重新實現(xiàn),這里只是提前占了一個位置而已。
????如果我們在使用某個外設的時候,開啟了某個中斷,但是又忘記編寫配套的中斷服務程序或者函數(shù)名寫錯,那當中斷來臨的時,程序就會跳轉(zhuǎn)到啟動文件預先寫好的空的中斷服務程序中,并且在這個空函數(shù)中無線循環(huán),即程序就死在這里。
?
NMI_Handler PROC ;系統(tǒng)異常 EXPORT NMI_Handler [WEAK] B . ENDP ;限于篇幅,中間代碼省略 SysTick_Handler PROC EXPORT SysTick_Handler [WEAK] B . ENDP Default_Handler PROC ;外部中斷 EXPORT WWDG_IRQHandler [WEAK] EXPORT PVD_IRQHandler [WEAK] EXPORT TAMP_STAMP_IRQHandler [WEAK] ;限于篇幅,中間代碼省略 LTDC_IRQHandler LTDC_ER_IRQHandler DMA2D_IRQHandler B . ENDP
?
??? B:跳轉(zhuǎn)到一個標號。這里跳轉(zhuǎn)到一個‘.’,即表示無線循環(huán)
用戶堆棧初始化
?
ALIGN
?
??? ALIGN:對指令或者數(shù)據(jù)存放的地址進行對齊,后面會跟一個立即數(shù)。缺省表示 4 字節(jié)對齊。
?
;用戶棧和堆初始化,由 C 庫函數(shù)_main 來完成 IF :DEF:__MICROLIB ;這個宏在 KEIL 里面開啟 EXPORT __initial_sp EXPORT __heap_base EXPORT __heap_limit ELSE IMPORT __use_two_region_memory ; 這個函數(shù)由用戶自己實現(xiàn) EXPORT __user_initial_stackheap __user_initial_stackheap LDR R0, = Heap_Mem LDR R1, =(Stack_Mem + Stack_Size) LDR R2, = (Heap_Mem + Heap_Size) LDR R3, = Stack_Mem BX LR ALIGN ENDIF END
?
????首先判斷是否定義了__MICROLIB ,如果定義了這個宏則賦予標號__initial_sp(棧頂?shù)刂罚?__heap_base(堆起始地址)、 __heap_limit(堆結(jié)束地址)全局屬性,可供外部文件調(diào)用。有關這個宏我們在 KEIL 里面配置,具體見圖 15-2。然后堆棧的初始化就由 C 庫函數(shù)_main 來完成。
????如果沒有定義__MICROLIB,則才用雙段存儲器模式,且聲明標號__user_initial_stackheap 具有全局屬性,讓用戶自己來初始化堆棧。
??? IF,ELSE,ENDIF:匯編的條件分支語句,跟 C 語言的 if ,else 類似
??? END:文件結(jié)束
審核編輯:湯梓紅
評論
查看更多