在啟動文件內部使用的都是匯編語言,這個文件的作用是負責執行微控制器從“復位”到“開始執行 main 函數”中間這段啟動時間所必須進行的工作。它完成的具體工作有:
-
初始化堆棧指針SP=_initial_sp
-
初始化PC指針=Reset_Handler
-
初始化中斷向量表
-
配置系統時鍾
-
調用C庫函數_main初始化用戶堆棧,從而轉向我們用戶應用程序的main。
匯編指令
打開STM32的啟動文件會發現,里面全部都是匯編語句,對於匯編指令不了解的朋友來說可能一頭霧水。下面我們按照啟動文件內指令出現的順序來介紹,相信可以了解到大概情況。
EQU:給數字常量取一個符號名, 相當於C語言中的預處理命令define。其常用格式如下:
Stack_Size EQU 0x00000400
表示將0x00000400這個數值,用Stack_Size名代替。
AREA:匯編一個新的代碼段或者數據段。常用格式如下:
AREA STACK, NOINIT, READWRITE, ALIGN=3
表示匯編一個數據段,名字是STACK,NOINIT表示不初始化,READWRITE表示可讀可寫,ALIGN表示字節對齊,通常后面會賦一個立即數,比如ALIGN=3表示的就是2^3字節對齊,即8字節對齊。
SPACE:分配一定大小的內存空間,單位為字節。常用格式如下:
Stack_Mem SPACE Stack_Size
表示給Stack_Mem分配一個Stack_Size大小的內存空間。通常它后面還會跟隨一個__initial_sp語句,表示棧的結束地址,即棧頂地址,因為棧是由高向低生長的。
PRESERVE8:當前文件堆棧需按照8字節對齊。格式:直接寫此指令即可。
THUMB:表示后面指令兼容THUMB指令。在ARM以前的指令集中有16位的THUMBM指令,現在Cortex-M系列使用的都是THUMB-2指令集,THUMB-2是32位的,兼容16位和32位的指令,是THUMB的超級版。格式:直接寫此指令即可。
EXPORT:聲明一個具有全局屬性的標號,可被外部文件使用。常用格式如下:
EXPORT __Vectors
表示__Vectors標號具有全局屬性,外部文件可以調用它。
DCD:以字為單位分配內存, 要求4字節對齊, 並要求初始化這些內存。常用格式如下:
DCD Reset_Handler ; Reset Handler
表示給Reset_Handler名稱的地址分配內存並初始化, 這個名稱地址可以在“STM32F4xx中文參考手冊”-“中斷和事件”-“中斷和異常向量”章節中找到。在那個函數名后面還有一個“;”, 在匯編程序中“;”即注釋,和C語言中的//類似效果。在后面的學習中會接觸很多的中斷函數,這些中斷函數名都可在DCD這部分找到。
PROC:定義子程序。常用格式:
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
表示定義一個全局的子程序Reset_Handler,需與ENDP成對使用,表示子程序結束。在
EXPORT Reset_Handler [WEAK]
后面有一個[WEAK],這個是弱定義,如果外部文件聲明了一個標號,則優先使用外部文件定義的標號,如果外部文件沒有定義也不出錯。要注意的是:這個並不是ARM的指令,是編譯器的。
LDR:從存儲器中加載字到一個寄存器中。常用格式:
LDR R0, =SystemInit
BLX:跳轉到由寄存器給出的地址,並根據寄存器的LSE確定處理器的狀態,還要把跳轉前的下條指令地址保存到LR。常用格式:
BLX R0
BX:跳轉到由寄存器/標號給出的地址,不用返回。常用格式:
BX R0
IMPORT:聲明標號來自外部文件,和C語言中的extern關鍵字類似。
IMPORT SystemInit
IMPORT __main
上述代碼中表示聲明SystemInit和main為外部文件,所以 在創建寄存器模板的時候寫一個SystemInit()空函數。如果想修改main.c文件中的main函數名,在這個地方就可以改動,然后后面
LDR R0, =__main
中的main也需要改動,對匯編不了解的不建議改動。
B:跳轉到一個標號。常用格式如下:
B .
在B后面有一個“.”,在匯編語言中表示循環。這句話的意思就是說跳進了循環。
IF,ELSE,ENDIF:匯編條件分支語句,與C語言的if else類似。其常用格式:
IF :DEF:__MICROLIB
EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit
ELSE
IMPORT __use_two_region_memory
EXPORT __user_initial_stackheap
ENDIF
END:到達文件的末尾,文件結束。
到這里啟動文件指令就介紹完了, 有的朋友在學習過程中還會遇到其他的匯編指令,那么怎么查找它們的功能和用法呢?其實很簡單,KEIL5軟件內已經給我們提供了幫組文檔,打開Help選項就會彈出幫助文檔,如下圖所示。
幫助文檔的界面如下圖所示,如果要查找AREA指令,只需要選擇搜索,輸入要搜索的指令,選擇列出主題即可。選擇對應的指令,右側就會顯示指令的具體介紹和格式說明。
堆棧
學習過C語言的朋友對堆棧這一詞很熟悉。在啟動文件開始處就定義了一堆棧的大小,代碼如下:
;棧空間的開辟
Stack_Size EQU 0x00000400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp ;棧的結束地址
;堆空間的開辟
Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit ;堆的結束地址
在程序開頭開辟了一個0x00000400即1KB的Stack_Size棧空間。 棧主要用於存放函數的參數值、局部變量的值等,其操作方式類似於數據結構中的棧。棧的大小不能超過內部RAM的大小。假如開發的程序占用的RAM比較大,局部變量使用的比較多,那么可以在啟動文件內修改這個Stack_Size值。
如果程序出現了莫名其妙的錯誤,並進入了硬fault的時候,你就要考慮下是不是棧不夠大,溢出了的問題。通常我們修改最多的還是棧值。
程序緊接着又開辟了一個0x00000200即512字節的Heap_Size堆空間。堆中的內存一般由程序員分配和釋放,若程序員不釋放,程序結束時可能由操作系統回收。分配方式類似於數據結構中的鏈表。堆和棧生長方式是相反的,堆是由低向高生長的,而棧是由高向低生長的。