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