本章參考資料《STM32F4xx 中文參考手冊》第十章-中斷和事件:表 46. STM32F42xxx 和 STM32F43xxx 的向量表;MDK中的幫助手冊—ARM Development Tools:用來查詢ARM的匯編指令和編譯器相關(guān)的指令。
14.1 啟動文件簡介啟動文件由匯編編寫,是系統(tǒng)上電復(fù)位后第一個執(zhí)行的程序。主要做了以下工作: 1、初始化堆棧指針SP=_initial_sp 2、初始化PC指針=Reset_Handler 3、初始化中斷向量表 4、配置系統(tǒng)時鐘 5、調(diào)用C庫函數(shù)_main初始化用戶堆棧,從而最終調(diào)用main函數(shù)去到C的世界 14.2 查找ARM匯編指令在講解啟動代碼的時候,會涉及到ARM的匯編指令和Cortex內(nèi)核的指令,有關(guān)Cortex內(nèi)核的指令我們可以參考《CM3權(quán)威指南CnR2》第四章:指令集。剩下的ARM的匯編指令我們可以在MDK->Help->Uvision Help中搜索到,以EQU為例,檢索如下:
圖 141 ARM 匯編指令索引 檢索出來的結(jié)果會有很多,我們只需要看Assembler User Guide 這部分即可。下面列出了啟動文件中使用到的ARM匯編指令,該列表的指令全部從ARM Development Tools這個幫助文檔里面檢索而來。其中編譯器相關(guān)的指令WEAK和ALIGN為了方便也放在同一個表格了。 表格 10 啟動文件使用的ARM匯編指令匯總
14.3 啟動文件代碼講解1. Stack—棧
開辟棧的大小為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ù)刂罚瑮J怯筛呦虻蜕L的。 2. Heap堆
開辟堆的大小為0X00000200(512字節(jié)),名字為HEAP,NOINIT即不初始化,可讀可寫,8(2^3)字節(jié)對齊。__heap_base表示對的起始地址,__heap_limit表示堆的結(jié)束地址。堆是由低向高生長的,跟棧的生長方向相反。 堆主要用來動態(tài)內(nèi)存的分配,像malloc()函數(shù)申請的內(nèi)存就在堆上面。這個在STM32里面用的比較少。
PRESERVE8:指定當前文件的堆棧按照8字節(jié)對齊。 THUMB:表示后面指令兼容THUMB指令。THUBM是ARM以前的指令集,16bit,現(xiàn)在Cortex-M系列的都使用THUMB-2指令集,THUMB-2是32位的,兼容16位和32位的指令,是THUMB的超級。 3. 向量表
定義一個數(shù)據(jù)段,名字為RESET,可讀。并聲明__Vectors、__Vectors_End和__Vectors_Size這三個標號具有全局屬性,可供外部的文件調(diào)用。 EXPORT:聲明一個標號可被外部的文件使用,使標號具有全局屬性。如果是IAR編譯器,則使用的是GLOBAL這個指令。 當內(nèi)核響應(yīng)了一個發(fā)生的異常后,對應(yīng)的異常服務(wù)例程(ESR)就會執(zhí)行。為了決定ESR 的入口地址,內(nèi)核使用了"向量表查表機制"。這里使用一張向量表。向量表其實是一個WORD(32 位整數(shù))數(shù)組,每個下標對應(yīng)一種異常,該下標元素的值則是該ESR 的入口地址。向量表在地址空間中的位置是可以設(shè)置的,通過NVIC 中的一個重定位寄存器來指出向量表的地址。在復(fù)位后,該寄存器的值為0。因此,在地址0 (即FLASH 地址0)處必須包含一張向量表,用于初始時的異常分配。要注意的是這里有個另類:0 號類型并不是什么入口地址,而是給出了復(fù)位后MSP 的初值。 表格 11 F429向量表
代碼 12 向量表
1 __Vectors_Size EQU __Vectors_End - __Vectors __Vectors為向量表起始地址,__Vectors_End 為向量表結(jié)束地址,兩個相減即可算出向量表大小。 向量表從FLASH的0地址開始放置,以4個字節(jié)為一個單位,地址0存放的是棧頂?shù)刂罚?X04存放的是復(fù)位程序的地址,以此類推。從代碼上看,向量表中存放的都是中斷服務(wù)函數(shù)的函數(shù)名,可我們知道C語言中的函數(shù)名就是一個地址。 DCD:分配一個或者多個以字為單位的內(nèi)存,以四字節(jié)對齊,并要求初始化這些內(nèi)存。在向量表中,DCD分配了一堆內(nèi)存,并且以ESR的入口地址初始化它們。 4. 復(fù)位程序
復(fù)位子程序是系統(tǒng)上電后第一個執(zhí)行的程序,調(diào)用SystemInit函數(shù)初始化系統(tǒng)時鐘,然后調(diào)用C庫函數(shù)_mian,最終調(diào)用main函數(shù)去到C的世界。 WEAK:表示弱定義,如果外部文件優(yōu)先定義了該標號則首先引用該標號,如果外部文件沒有聲明也不會出錯。這里表示復(fù)位子程序可以由用戶在其他文件重新實現(xiàn),這里并不是唯一的。 IMPORT:表示該標號來自外部文件,跟C語言中的EXTERN關(guān)鍵字類似。這里表示SystemInit和__main這兩個函數(shù)均來自外部的文件。 SystemInit()是一個標準的庫函數(shù),在system_stm32f4xx.c這個庫文件總定義。主要作用是配置系統(tǒng)時鐘,這里調(diào)用這個函數(shù)之后,F429的系統(tǒng)時鐘配被配置為180M。 __main是一個標準的C庫函數(shù),主要作用是初始化用戶堆棧,最終調(diào)用main函數(shù)去到C的世界。這就是為什么我們寫的程序都有一個main函數(shù)的原因。如果我們在這里不調(diào)用__main,那么程序最終就不會調(diào)用我們C文件里面的main,如果是調(diào)皮的用戶就可以修改主函數(shù)的名稱,然后在這里面IMPORT你寫的主函數(shù)名稱即可。
這個時候你在C文件里面寫的主函數(shù)名稱就不是main了,而是user_main了。 LDR、BLX、BX是CM4內(nèi)核的指令,可在《CM3權(quán)威指南CnR2》第四章-指令集里面查詢到,具體作用見下表:
5. 中斷服務(wù)程序在啟動文件里面已經(jīng)幫我們寫好所有中斷的中斷服務(wù)函數(shù),跟我們平時寫的中斷服務(wù)函數(shù)不一樣的就是這些函數(shù)都是空的,真正的中斷復(fù)服務(wù)程序需要我們在外部的C文件里面重新實現(xiàn),這里只是提前占了一個位置而已。 如果我們在使用某個外設(shè)的時候,開啟了某個中斷,但是又忘記編寫配套的中斷服務(wù)程序或者函數(shù)名寫錯,那當中斷來臨的時,程序就會跳轉(zhuǎn)到啟動文件預(yù)先寫好的空的中斷服務(wù)程序中,并且在這個空函數(shù)中無線循環(huán),即程序就死在這里。
B:跳轉(zhuǎn)到一個標號。這里跳轉(zhuǎn)到一個'.',即表示無線循環(huán)。 6. 用戶堆棧初始化
ALIGN:對指令或者數(shù)據(jù)存放的地址進行對齊,后面會跟一個立即數(shù)。缺省表示4字節(jié)對齊。 1 ;用戶棧和堆初始化
判斷是否定義了__MICROLIB ,如果定義了則賦予標號__initial_sp(棧頂?shù)刂罚?/span>__heap_base(堆起始地址)、__heap_limit(堆結(jié)束地址)全局屬性,可供外部文件調(diào)用。如果沒有定義(實際的情況就是我們沒定義__MICROLIB)則使用默認的C庫,然后初始化用戶堆棧大小,這部分有C庫函數(shù)__main來完成,當初始化完堆棧之后,就調(diào)用main函數(shù)去到C的世界。 IF,ELSE,ENDIF:匯編的條件分支語句,跟C語言的if ,else類似 END:文件結(jié)束 14.4 系統(tǒng)啟動流程下面這段話引用自《CM3權(quán)威指南CnR2》3.8—復(fù)位序列,CM4的復(fù)位序列跟CM3一樣?!鹱?。 在離開復(fù)位狀態(tài)后, CM3 做的第一件事就是讀取下列兩個 32 位整數(shù)的值: 1、從地址 0x0000,0000 處取出 MSP 的初始值。 2、從地址 0x0000,0004 處取出 PC 的初始值——這個值是復(fù)位向量, LSB 必須是 1。 然后從這個值所對應(yīng)的地址處取指。
圖 142 復(fù)位序列 請注意,這與傳統(tǒng)的 ARM 架構(gòu)不同——其實也和絕大多數(shù)的其它單片機不同。傳統(tǒng)的 ARM 架構(gòu)總是從 0 地址開始執(zhí)行第一條指令。它們的 0 地址處總是一條跳轉(zhuǎn)指令。在 CM3 中,在 0 地址處提供 MSP 的初始值,然后緊跟著就是向量表。向量表中的數(shù)值是 32 位的地址,而不是跳轉(zhuǎn)指令。向量表的第一個條目指向復(fù)位后應(yīng)執(zhí)行的第一條指令,就是我們剛剛分析的Reset_Handler這個函數(shù)。
圖 143 初始化MSP和PC的一個范例 因為 CM3 使用的是向下生長的滿棧,所以 MSP 的初始值必須是堆棧內(nèi)存的末地址加 1。舉例來說,如果我們的堆棧區(qū)域在 0x20007C00-0x20007FFF 之間,那么 MSP 的初始值就必須是 0x20008000。 向量表跟隨在 MSP 的初始值之后——也就是第 2 個表目。要注意因為 CM3 是在 Thumb 態(tài)下執(zhí)行,所以向量表中的每個數(shù)值都必須把 LSB 置 1(也就是奇數(shù))。正是因為這個原因,圖 143中使用0x101 來表達地址 0x100。當 0x100 處的指令得到執(zhí)行后,就正式開始了程序的執(zhí)行(即去到C的世界)。在此之前初始化 MSP 是必需的,因為可能第 1 條指令還沒來得及執(zhí)行,就發(fā)生了 NMI 或是其它 fault。 MSP 初始化好后就已經(jīng)為它們的服務(wù)例程準備好了堆棧。 現(xiàn)在,程序就進入了我們熟悉的C世界,現(xiàn)在我們也應(yīng)該明白main并不是系統(tǒng)執(zhí)行的第一個程序了。 14.5 每課一問1、啟動文件的主要作用是什么? 2、FLASH地址0存放的是什么? 3、熟悉啟動文件里面的ARM匯編指令 |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|