stm32系列启动文件解读(KEIL编译环境)
基于stm32f103c8t6芯片的啟動文件進行分析。
啟動文件在嵌入式芯片開發是必不可少的,其文件后綴是.s,通常需要加入工程參與編譯。它的的用包括一下幾點:
啟動文件使用匯編語言編寫,如果熟悉匯編語言,那么很容易理解它;如果不熟悉匯編語言,針對啟動文件里用到的匯編指令,下面會一一介紹。
1.啟動文件使用到的匯編指令
| 指令名稱 | 作用 |
| EQU | 給數據常量起一個符號名,相當于C語言的#define |
| AREA | 匯編一個新的代碼段或者數據段 |
| SPACE | 分配內存空間 |
| PRESERVE8 | 告訴編譯器,指定當前文件的堆按照8字節對齊 |
| THUMB | 表示后面指令兼容THUMB指令。 |
| EXPORT | 聲明一個標號具有外部屬性,可在外部文件使用 |
| DCD | 以字節為單位,按照4字節對齊,并要求初始化這些內存 |
| PROC | 定義子程序,與ENDP成對使用,表示子程序結束。 |
| WEAK | 編譯器指令(不同的編譯器有差異),弱定義一個標號,如果外部文件聲明了該標號,則優先使用外部文件的定義。避免出現重定義錯誤。 |
| IMPORT | 聲明標號來自外部文件,相當于C語言的extern |
| B | 跳轉到一個標號 |
| ALIGN | 編譯器指令(不同的編譯器有差異),編譯器對指令或數據的存放地址進行對齊,一般跟一個立即數,默認表示按照4字節對齊。 |
| END | 文件結束標志 |
| IF,ELSE,ENDIF | 匯編條件分支語句,相當于C語言的#if #else #endif |
| ; | 行注釋起始符號,相當于C語言的“//“ |
?
2.啟動文件代碼分析
Stack_Size????? EQU???? 0x00000400AREA??? STACK, NOINIT, READWRITE, ALIGN=3 Stack_Mem?????? SPACE?? Stack_Size __initial_sp上面代碼定義了棧的大小為0x00000400(1KB),名稱為STACK,NOINIT表示不初始化,REAWRITE表示可讀可下,ALIGN?= 3表示按照8(即2^3)字節對齊。還定義了標號__initial_sp(棧的結束地址)。利用SPACE關鍵字來申請空間,大小為Stack_Size,記作Stack_Mem。棧是由高向低生長的。
棧的空間用于局部變量、函數調用、函數形參等開銷,棧的大小不能超過芯片內部SRAM大小;如果編寫的程序局部變量比較多,占用內存比較大,會造成棧溢出,導致進入硬故障,這時候需要考慮增加棧空間大小。
Heap_Size?????? EQU???? 0x00000200AREA??? HEAP, NOINIT, READWRITE, ALIGN=3 __heap_baseHeap_Mem??????? SPACE?? Heap_Size __heap_limit? ? ? ?上面的代碼定義了堆大小為0x00000200(512字節),名稱為HEAP,NOINIT表示不初始化,READWRITE表示可讀可寫,ALIGN?= 3表示按照8(即2^3)字節對齊。還定義了標號:__heap_base(堆的起始地址)、__heap_limit(堆的結束地址)。利用SPACE關鍵字來申請空間,大小為Heap_Size,記作Heap_Mem。堆是由低向高生長的,與棧的生長方向相反。
?????? 堆的主要用于動態內存的分配,像malloc()函數申請的內存就在堆中。這個在STM32里面用得比較少。
??????????????? PRESERVE8THUMB上面兩個在前面的指令表格里已經說明。
; Vector Table Mapped to Address 0 at ResetAREA??? RESET, DATA, READONLYEXPORT? __VectorsEXPORT? __Vectors_EndEXPORT? __Vectors_Size上面代碼定義了一個數據段,名稱為RESET,僅可讀,還聲明了3個外部文件可以使用的標號:__Vectors(向量表起始地址)、__Vectors_End(向量表結束地址)、__Vectors_Size(向量表大小)。
向量表其實就是一個WORD(32bit整數)數組,每一個下標對應一種異常,該下標元素的值則是該ESR的入口地址。向量表在地址空間的位置是可以被設置的,同過NVIC中的一個重定位寄存器來指定向量表的地址。復位之后,該寄存器的值為0。因此,在地址0(即flash地址0)處必須包含一張向量表,用于初始化時的異常分配。
接下來的代碼就是分配向量表的內存
__Vectors?????? DCD???? __initial_sp?????????????? ; Top of Stack(棧頂地址)DCD???? Reset_Handler????????????? ; Reset Handler(復位程序地址)DCD???? NMI_Handler??????????????? ; NMI HandlerDCD???? HardFault_Handler????????? ; Hard Fault HandlerDCD???? MemManage_Handler????????? ; MPU Fault HandlerDCD???? BusFault_Handler?????????? ; Bus Fault HandlerDCD???? UsageFault_Handler???????? ; Usage Fault HandlerDCD???? 0??????????????? ??????????; Reserved(保留)DCD???? 0????????????????????????? ; ReservedDCD???? 0????????????????????????? ; ReservedDCD???? 0????????????????????????? ; ReservedDCD???? SVC_Handler????????? ??????; SVCall HandlerDCD???? DebugMon_Handler?????????? ; Debug Monitor HandlerDCD???? 0????????????????????????? ; ReservedDCD???? PendSV_Handler???????????? ; PendSV HandlerDCD???? SysTick_Handler??????????? ; SysTick Handler; External InterruptsDCD???? WWDG_IRQHandler??????????? ; Window WatchdogDCD???? PVD_IRQHandler???????????? ; PVD through EXTI Line detectDCD???? TAMPER_IRQHandler????????? ; Tamper;(中間代碼省略)DCD???? EXTI15_10_IRQHandler?????? ; EXTI Line 15..10DCD???? RTCAlarm_IRQHandler??????? ; RTC Alarm through EXTI LineDCD???? USBWakeUp_IRQHandler?????? ; USB Wakeup from suspend __Vectors_End__Vectors_Size? EQU? __Vectors_End - __Vectors?????? 上面代碼利用DCD關鍵字以4字節對齊,分配了一堆內存,類似之前SPACE關鍵字的作用。利用__Vectors_End(堆結束地址)減去__Vectors(堆的起始地址)得到堆的大小__Vectors_Size。
?????? 向量表從flash的0地址開始放置,以4字節為一個單位,地址0存放的時棧頂地址,接著0x04存放的時復位程序的地址,以此類推。向量表中存放的都是中斷函數的函數名,可我們知道C語言中函數名就是一個地址。
AREA??? |.text|, CODE, READONLY?????? 上面代碼表示是定義一個名稱為.text的代碼段,僅可讀。
?????? ; Reset handler Reset_Handler??? PROCEXPORT? Reset_Handler???????????? [WEAK]IMPORT? __mainIMPORT? SystemInitLDR???? R0, =SystemInitBLX???? R0LDR???? R0, =__mainBX????? R0ENDP?????? 復位子程序是系統上電第一個執行的程序,調用SystemInit函數(sysyem_stm32f4xx.c文件里定義的)初始化系統時鐘等,然后調用C庫函數_main(編譯器自帶的),在_main函數里最終會調用main函數轉到C的世界。
?????? LDR、BLX、BX是CM4內核的指令:
LDR:從儲存器中加載一個字到寄存器中。
BL:跳轉到由寄存器/標號給出的地址,并把跳轉前的下一條指令保存到LR中。
BLX:跳轉到由寄存器/標號給出的地址,并根據寄存器的LSE確定處理器的狀態,并把跳轉前的下一條指令保存到LR中。
BX:跳轉到有寄存器/標號給出的地址,不用返回。
接下來的代碼就是中斷服務程序的實現,一般來說,我們會在外部的c文件實現中斷函數,這里定義了只是備用,以防我們把某個中斷使能了,但忘了實現它的中斷函數或者函數名寫錯,那么系統就會執行下面的程序,并且下面的中斷函數會進入無限循環,程序也就死在這里。
NMI_Handler???? PROC ?? ;不可屏蔽的系統異常中斷EXPORT? NMI_Handler??????????????? [WEAK]B?????? .ENDPHardFault_Handler\???????????? ;所有類型錯誤的中斷PROCEXPORT? HardFault_Handler????????? [WEAK]B?????? .ENDP(省略一部分代碼)SysTick_Handler PROC??????? ;系統滴答定時器中斷EXPORT? SysTick_Handler??????????? [WEAK]B?????? .ENDPDefault_Handler PROC??????? ;外設中斷EXPORT? WWDG_IRQHandler??????????? [WEAK]EXPORT? PVD_IRQHandler???????????? [WEAK]EXPORT? TAMPER_IRQHandler????????? [WEAK] (省略一部分代碼)RTCAlarm_IRQHandlerUSBWakeUp_IRQHandlerB?????? .ENDPALIGNB:跳轉到一個標號,這里跳轉到一個‘.’,表示無限循環。
;*******************************************************************************; User Stack and Heap initialization;*******************************************************************************IF????? :DEF:__MICROLIB? ;這個宏在KEIL里面開啟? ???????????????????EXPORT? __initial_spEXPORT? __heap_baseEXPORT? __heap_limit?????????????ELSE???????????????IMPORT? __use_two_region_memory???? ;這個函數由用戶自己實現EXPORT? __user_initial_stackheap?????????? __user_initial_stackheapLDR???? R0, =? Heap_MemLDR???? R1, =(Stack_Mem + Stack_Size)LDR???? R2, = (Heap_Mem +? Heap_Size)LDR???? R3, = Stack_MemBX????? LRALIGNENDIFEND如果勾選【Options for Target】->【Target】->【Use MicroLIB】,那么就會定義宏__MICROLOB。
如果定義了__MICROLOB,那么把__initial_sp(棧起始地址)、__heap_base(堆開始地址)、__heap_limit(堆結束地址)標號賦予全局屬性,那么在外部文件也可以使用,然后堆和棧的初始化就由C庫函數__main來完成。
如果沒有定義__MICROLOB,采用雙段存儲器模式,且聲明標號__user_initial_stackheap具有全局性,讓用戶自己來初始化堆和棧。KEIL C庫函數會調用__user_initial_stackheap,通過R0~R3將堆棧以參數形式傳遞給KEIL C庫。
總結
以上是生活随笔為你收集整理的stm32系列启动文件解读(KEIL编译环境)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: IAR新建stm32工程,完美移植stm
- 下一篇: 基于IAR-stm32裸板工程,完美移植