第九章STM32啟動過程分析
本章給大家分析STM32H7的啟動過程,這裡的啟動過程是指從STM32晶片上電複位執行的第一條指令開始,到執行使用者編寫的main函數這之間的過程。我們編寫程式,基本都是用C語言編寫,并且以main函數作為程式的入口。但是事實上,main函數并非最先執行的,在此之前需要做一些準備工作,準備工作通過啟動檔案的程式來完成。了解STM32啟動過程,對今後的學習和分析STM32程式有很大的幫助。
注意:學習本章内容之前,請大家最好先閱讀由正點原子團隊編寫的《STM32 啟動檔案淺析_V1.2.pdf》和《STM32 MAP檔案淺析_V1.1.pdf》這兩份文檔(路徑:A 盤→1,入門資料)。
本章将分為如下幾個小節:
9.1 啟動模式
9.2 啟動檔案分析
9.3 map檔案分析
9.1 啟動模式
我們知道的複位方式有三種:上電複位,硬體複位和軟體複位。當産生複位,并且離開複位狀态後,CM7核心做的第一件事就是讀取下列兩個32位整數的值:
(1)從位址 0x0000 0000 處取出堆棧指針MSP 的初始值,該值就是棧頂位址。
(2)從位址 0x0000 0004 處取出程式計數器指針PC 的初始值,該值指向複位後執行的第一條指令。下面用示意圖表示,如圖9.1.1所示。
圖9.1.1 複位序列
上述過程中,核心是從0x0000 0000和0x0000 0004兩個的位址擷取堆棧指針SP和程式計數器指針PC。事實上,0x0000 0000和0x0000 0004兩個的位址可以被重映射到其他的位址空間。例如:我們将0x0800 0000映射到 0x0000 0000,即從内部FLASH啟動,那麼核心會從位址0x0800 0000處取出堆棧指針MSP 的初始值,從位址0x0800 0004處取出程式計數器指針PC 的初始值。CPU 會從 PC 寄存器指向的位址空間取出的第1條指令開始執行程式,就是開始執行複位中斷服務程式 Reset_Handler。
将0x0000 0000和0x0000 0004兩個的位址重映射到其他位址空間,就是啟動模式選擇。
對于STM32H7的啟動模式(也稱自舉模式),我們看表9.1.1進行分析。
啟動模式選擇 | 啟動位址 | |
BOOT | 啟動位址選項位元組 | |
0 | BOOT_ADD0[15:0] | 由使用者選項位元組BOOT_ADD0[15:0]決定啟動位址,ST出廠預設的啟動位址為:0X0800 0000的Flash位址 |
1 | BOOT_ADD1[15:0] | 由使用者選項位元組BOOT_ADD1[15:0]決定啟動位址,ST出廠預設的啟動位址為:0X1FF0 0000的系統存儲器位址 |
表9.1.1啟動模式選擇表
注:啟動引腳(BOOT)的電平:0:低電平;1:高電平。
由表9.1.1可以看到,STM32H7的啟動模式選擇需要一個BOOT引腳,這個BOOT引腳對應了0和1兩個狀态,這兩個狀态都有自己對應的啟動位址選項位元組,用于選擇自己的啟動位址。
這裡的BOOT_ADD0[15:0]和BOOT_ADD1[15:0]對應32位位址的高16位,也就是說我們隻能設定高16位對應的位址(低16位必須是0),其設定範圍:0X0000 0000 ~ 0X3FFF 0000,涵蓋了所有FLASH位址空間,所有 RAM 位址空間:ITCM、DTCM RAM和 SRAM,還有TCM-RAM位址空間。基本上,可以設定任意位址啟動(低16位必須是0),通過FLASH_BOOT_PRGR寄存器設定。
在出廠的時候,ST預設給BOOT_ADD0和BOOT_ADD1程式設計為:0X0800 0000和0X1FF0 0000分别對應使用者FLASH的起始位址和系統存儲器位址,用于執行使用者代碼或者進入BOOTLOADER狀态。一般情況下我們設定B00T引腳為低電平即可,即從0X0800 0000的FLASH位址啟動,執行使用者代碼。
9.2 啟動檔案分析
STM32啟動檔案由ST官方提供,在官方的STM32Cube固件包裡,對于STM32H750系列晶片的啟動檔案,我們選用的是startup_stm32h750xx.s這個檔案。啟動檔案用彙編編寫,是系統上電複位後第一個執行的程式。
啟動檔案主要做了以下工作:
1、初始化堆棧指針 SP = _initial_sp
2、初始化程式計數器指針PC = Reset_Handler
3、設定堆和棧的大小
4、初始化中斷向量表
5、配置外部SRAM作為資料存儲器(可選)
6、配置系統時鐘,通過調用SystemInit函數(可選)
7、調用 C庫中的 _main 函數初始化使用者堆棧,最終調用 main 函數
9.2.1啟動檔案中的一些指令
指令名稱 | 作用 |
EQU | 給數字常量取一個符号名,相當于C語言中的define |
AREA | 彙編一個新的代碼段或者資料段 |
ALIGN | 編譯器對指令或者資料的存放位址進行對齊,一般需要跟一個立即數,預設表示4位元組對齊。要注意的是,這個不是ARM的指令,是編譯器的,這裡放到一起為了友善。 |
SPACE | 配置設定記憶體空間 |
PRESERVE8 | 目前檔案堆棧需要按照8位元組對齊 |
THUMB | 表示後面指令相容THUMB指令。在ARM以前的指令集中有16位的THUMBM指令,現在Cortex-M系列使用的都是THUMB-2指令集,THUMB-2是32位的,相容16位和32位的指令,是THUMB的超級版。 |
EXPORT | 聲明一個标号具有全局屬性,可被外部的檔案使用 |
DCD | 以位元組為機關配置設定記憶體,要求4位元組對齊,并要求初始化這些記憶體 |
PROC | 定義子程式,與ENDP成對使用,表示子程式結束 |
WEAK | 弱定義,如果外部檔案聲明了一個标号,則優先使用外部檔案定義的标号,如果外部檔案沒有定義也不會出錯。要注意的是,這個不是ARM的指令,是編譯器的,這裡放到一起為了友善。 |
IMPORT | 聲明标号來自外部檔案,跟C語言中的extern關鍵字類似 |
LDR | 從存儲器中加載字到一個存儲器中 |
BLX | 跳轉到由寄存器給出的位址,并根據寄存器的 LSE 确定處理器的狀态,還要把跳轉前的下條指令位址儲存到 LR |
BX | 跳轉到由寄存器/标号給出的位址,不用傳回 |
B | 跳轉到一個标号 |
IF,ELSE,ENDIF | 彙編條件分支語句,跟C語言的類似 |
END | 到達檔案的末尾,檔案結束 |
表9.2.1.1 啟動檔案的彙編指令
上表,列舉了STM32啟動檔案的一些彙編和編譯器指令,關于其他更多的ARM彙編指令,我們可以通過MDK的索引搜尋工具中搜尋找到。打開索引搜尋工具的方法:MDK->Help->uVision Help,如圖9.2.1.1所示。
圖9.2.1.1打開索引搜尋工具的方法
打開之後,我們以EQU為例,示範一下怎麼使用,如圖9.2.1.2所示。
圖9.2.1.2 搜尋EQU彙編指令
搜尋到的标題有很多,我們隻需要看Assembler User Guide 這部分即可。
9.2.2啟動檔案代碼講解
(1)棧空間的開辟
棧空間的開辟,源碼如圖9.2.2.1所示:
圖9.2.2.1 棧空間的開辟
源碼含義:開辟一段大小為0x0000 0400(1KB)的棧空間,段名為STACK,NOINIT表示不初始化;READWRITE表示可讀可寫;ALIGN=3,表示按照 2^3對齊,即 8 位元組對齊。
AREA彙編一個新的代碼段或者資料段。
SPACE配置設定記憶體指令,配置設定大小為Stack_Size位元組連續的存儲單元給棧空間。
__initial_sp緊挨着SPACE放置,表示棧的結束位址,棧是從高往低生長,是以結束位址就是棧頂位址。
棧主要用于存放局部變量,函數形參等,屬于編譯器自動配置設定和釋放的記憶體,棧的大小不能超過内部SRAM的大小。如果工程的程式量比較大,定義的局部變量比較多,那麼就需要在啟動代碼中修改棧的大小,即修改Stack_Size的值。如果程式出現了莫名其妙的錯誤,并進入了HardFault的時候,你就要考慮下是不是棧空間不夠大,溢出了的問題。
(2)堆空間的開辟
堆空間的開辟,源碼如圖9.2.2.2所示:
圖9.2.2.2堆空間的開辟
源碼含義:開辟一段大小為0x0000 0200(512位元組)的堆空間,段名為HEAP,不初始化,可讀可寫,8位元組對齊。
__heap_base表示堆的起始位址,__heap_limit表示堆的結束位址。堆和棧的生長方向相反的,堆是由低向高生長,而棧是從高往低生長。
堆主要用于動态記憶體的配置設定,像malloc()、calloc()和realloc()等函數申請的記憶體就在堆上面。堆中的記憶體一般由程式員配置設定和釋放,若程式員不釋放,程式結束時可能由作業系統回收。
接下來是PRESERVE8和THUMB指令兩行代碼。如圖9.2.2.3所示。
圖9.2.2.3 PRESERVE8和THUMB指令
PRESERVE8:訓示編譯器按照8位元組對齊。
THUMB:訓示編譯器之後的指令為THUMB指令。
注意:由于正點原子提供了獨立的記憶體管理實作方式(mymalloc,myfree等),并不需要使用C庫的malloc和free等函數,也就用不到堆空間,是以我們可以設定Heap_Size的大小為0,以節省記憶體空間。
(3)中斷向量表定義(簡稱:向量表)
為中斷向量表定義一個資料段,如圖9.2.2.4所示:
圖9.2.2.4 為中斷向量表定義一個資料段
源碼含義:定義一個資料段,名字為RESET, READONLY表示隻讀。EXPORT表示聲明一個标号具有全局屬性,可被外部的檔案使用。這裡是聲明了__Vectors、__Vectors_End和__Vectors_Size三個标号具有全局性,可被外部的檔案使用。
STM32H750的中斷向量表定義代碼,如圖9.2.2.5所示。
圖9.2.2.5中斷向量表定義代碼
__Vectors 為向量表起始位址, __Vectors_End 為向量表結束位址,__Vectors_Size為向量表大小,__Vectors_Size = __Vectors_End - __Vectors。
DCD:配置設定一個或者多個以字為機關的記憶體,以四位元組對齊,并要求初始化這些記憶體。
中斷向量表被放置在代碼段的最前面。例如:當我們的程式在FLASH運作時,那麼向量表的起始位址是:0x0800 0000。結合圖9.2.2.5可以知道,位址0x0800 0000存放的是棧頂位址。DCD:以四位元組對齊配置設定記憶體,也就是下個位址是0x0800 0004,存放的是Reset_Handler中斷函數入口位址。
從代碼上看,向量表中存放的都是中斷服務函數的函數名,是以 C 語言中的函數名對晶片來說實際上就是一個位址。
STM32H750的中斷向量表可以在《STM32H7xx參考手冊_V3(中文版).pdf》的第19章的19.1.2小節找到,與中斷向量表定義代碼是對應的。
(4)複位程式
接下來是定義隻讀代碼段,如圖9.2.2.6所示:
圖9.2.2.6 定義隻讀代碼段
定義一個段命為.text,隻讀的代碼段,在CODE區。
複位子程式代碼,如圖9.2.2.7所示:
圖9.2.2.7 複位子程式代碼
利用PROC、ENDP這一對僞指令把程式段分為若幹個過程,使程式的結構加清晰。
複位子程式是複位後第一個被執行的程式,主要是調用SystemInit函數配置系統時鐘、還有就是初始化FSMC/FMC總線上外挂的SRAM(可選)。然後在調用C 庫函數__main,最終調用 main函數去到 C 的世界。
EXPORT聲明複位中斷向量Reset_Handler為全局屬性,這樣外部檔案就可以調用此複位中斷服務。
WEAK:表示弱定義,如果外部檔案優先定義了該标号則首先引用外部定義的标号,如果外部檔案沒有聲明也不會出錯。這裡表示複位子程式可以由使用者在其他檔案重新實作,這裡并不是唯一的。
IMPORT表示該标号來自外部檔案。這裡表示SystemInit 和__main 這兩個函數均來自外部的檔案。
LDR、BLX、BX 是核心的指令,可在《CM3 權威指南 CnR2》第四章-指令集裡面查詢到。
LDR表示從存儲器中加載字到一個存儲器中。
BLX表示跳轉到由寄存器給出的位址,并根據寄存器的 LSE 确定處理器的狀态,還要把跳轉前的下條指令位址儲存到 LR。
BX表示跳轉到由寄存器/标号給出的位址,不用傳回。這裡表示切換到__main位址,最終調用main函數,不傳回,進入C的世界。
(5)中斷服務程式
接下來就是中斷服務程式了,如圖9.2.2.8所示:
圖9.2.2.8 中斷服務程式
可以看到這些中斷服務函數都被[WEAK]聲明為弱定義函數,如果外部檔案聲明了一個标号,則優先使用外部檔案定義的标号,如果外部檔案沒有定義也不會出錯。
這些中斷函數分為系統異常中斷和外部中斷,外部中斷根據不同晶片有所變化。B指令是跳轉到一個标号,這裡跳轉到一個‘.’,表示無限循環。
在啟動檔案代碼中,已經把我們所有中斷的中斷服務函數寫好了,但都是聲明為弱定義,是以真正的中斷服務函數需要我們在外部實作。
如果我們開啟了某個中斷,但是忘記寫對應的中斷服務程式函數又或者把中斷服務函數名寫錯,那麼中斷發生時,程式就會跳轉到啟動檔案預先寫好的弱定義的中斷服務程式中,并且在B指令作用下跳轉到一個‘.’中,無限循環。
這裡的系統異常中斷部分是核心的,外部中斷部分是外設的。
(6)使用者堆棧初始化
ALIGN指令,如圖9.2.2.9所示:
圖9.2.2.9 ALIGN指令
ALIGN表示對指令或者資料的存放位址進行對齊,一般需要跟一個立即數,預設表示4位元組對齊。要注意的是,這個不是ARM的指令,是編譯器的。
接下就是啟動檔案最後一部分代碼,使用者堆棧初始化代碼,如圖9.2.2.10所示:
圖9.2.2.10 使用者堆棧初始化代碼
IF, ELSE, ENDIF是彙編的條件分支語句。
588行判斷是否定義了__MICROLIB。關于__MICROLIB這個宏定義,我們是在KEIL裡面配置,具體方法如圖9.2.2.11所示。
圖9.2.2.11 __MICROLIB定義方法
勾選了Use MicroLIB就代表定義了__MICROLIB這個宏。
如果定義__MICROLIB,聲明__initial_sp、__heap_base和__heap_limit這三個标号具有全局屬性,可被外部的檔案使用。__initial_sp表示棧頂位址,__heap_base表示堆起始位址,__heap_limit表示堆結束位址。
如果沒有定義__MICROLIB,實際的情況就是我們沒有定義__MICROLIB,是以使用預設的C庫運作。那麼堆棧的初始化由C庫函數__main來完成。
IMPORT聲明__use_two_region_memory标号來自外部檔案。
EXPORT聲明__user_initial_stackheap具有全局屬性,可被外部的檔案使用。
599行标号__user_initial_stackheap,表示使用者堆棧初始化程式入口。
接下來進行堆棧空間初始化,堆是從低到高生長,棧是從高到低生長,是兩個互相獨立的資料段,并且不能交叉使用。
601行儲存堆起始位址。
602行儲存棧大小。
603行儲存堆大小。
604行儲存棧頂指針。
605行跳轉到LR标号給出的位址,不用傳回。
611行END表示到達檔案的末尾,檔案結束。
Use MicroLIB
MicroLIB是MDK自帶的微庫,是預設C庫的備選庫,MicroLIB進行了高度優化使得其代碼變得很小,功能比預設C庫少。MicroLIB是沒有源碼的,隻有庫。
關于MicroLIB更多知識可以看官方介紹http://www.keil.com/arm/microlib.asp 。
9.2.3系統啟動流程
我們知道啟動模式不同,啟動的起始位址是不一樣的,下面我們以代碼下載下傳到内部FLASH的情況舉例,即代碼從位址0x0800 0000開始被執行。
當産生複位,并且離開複位狀态後,CM7核心做的第一件事就是讀取下列兩個32位整數的值:
(1)從位址 0x0800 0000 處取出堆棧指針MSP 的初始值,該值就是棧頂位址。
(2)從位址 0x0800 0004 處取出程式計數器指針PC 的初始值,該值指向中斷服務程式 Reset_Handler。下面用示意圖表示,如圖9.2.3.1所示。
圖9.2.3.1 複位序列
我們看看MiniPRO STM32H750開發闆HAL庫例程的實驗1 跑馬燈實驗中,取出的MPS和PC的值是多少,方法如圖9.2.3.2所示。
圖9.2.3.2 取出的MPS和PC的值
由圖9.2.3.2可以知道位址0x0800 0000的值是0x2400 0BD8,位址0x0800 0004的值是0x0800 0339,即堆棧指針 =0x2400 0BD8,程式計數器指針PC = 0x0800 0339(即複位中斷服務程式Reset_Handler的入口位址),即。因為CM7核心是小端模式,是以倒着讀。
請注意,這與傳統的 ARM 架構不同——其實也和絕大多數的其它單片機不同。傳統的ARM 架構總是從 0 位址開始執行第一條指令。它們的 0 位址處總是一條跳轉指令。而在 CM3核心中,0 位址處提供 MSP 的初始值,然後就是向量表(向量表在以後還可以被移至其它位置)。向量表中的數值是 32 位的位址,而不是跳轉指令。向量表的第一個條目指向複位後應執行的第一條指令,就是Reset_Handler這個函數。下面繼續以正點原子MiniPRO STM32H750開發闆HAL庫例程的實驗1跑馬燈實驗為例,代碼從位址0x0800 0000開始被執行,講解一下系統啟動,初始化堆棧、MSP和PC後的記憶體情況。
圖9.2.3.3 初始化堆棧、MSP和PC後的記憶體情況
因為CM3使用的是向下生長的滿棧,是以MSP的初始值必須是堆棧記憶體的末位址加1。
舉例來說,如果你的棧區域在0x2400 03D8‐0x2400 0BD4(2KB大小)之間,那麼 MSP 的初始值就必須是0x2400 0BD8。
向量表跟随在 MSP 的初始值之後——也就是第 2 個表目。
R15是程式計數器,在彙編代碼中,可以使用名字“PC”來通路它。ARM規定:PC最低兩位并不表示真實位址,最低位LSB用于表示是ARM指令(0)還是Thumb指令(1),因為 CM3 主要執行 Thumb指令,是以這些指令的最低位都是1(都是奇數)。因為 CM3 内部使用了指令流水線,讀 PC 時傳回的值是目前指令的位址+4。比如說:
0x1000: MOV R0, PC ; R0 = 0x1004
如果向 PC 寫資料,就會引起一次程式的分支(但是不更新 LR 寄存器)。CM3 中的指令至少是半字對齊的,是以 PC 的 LSB 總是讀回 0。然而,在分支時,無論是直接寫 PC 的值還是使用分支指令,都必須保證加載到 PC 的數值是奇數(即 LSB=1),表明是在Thumb 狀态下執行。倘若寫了0,則視為轉入 ARM 模式,CM3 将産生一個fault異常。
正因為上述原因,圖9.2.3.3中使用0x0800 0339來表達位址0x0800 0338。當0x0800 0339處的指令得到執行後,就正式開始了程式的執行(即去到C的世界)。是以在此之前初始化 MSP 是必需的,因為可能第 1 條指令還沒執行就會被 NMI 或是其它 fault 打斷。MSP 初始化好後就已經為它們的服務例程準備好了堆棧。
STM32啟動檔案分析就給大家介紹到這裡,更多内容請看《STM32 啟動檔案淺析_V1.1》。
9.3 map檔案分析
9.3.1 MDK編譯生成檔案簡介
MDK 編譯工程,會生成一些中間檔案(如.o、.axf、.map 等),最終生成 hex 檔案,以便下載下傳到 MCU 上面執行,以MiniPRO STM32H750開發闆HAL庫例程的實驗1 跑馬燈實驗為例(其他開發闆類似),編譯過程産生的所有檔案,都存放在 OBJ 檔案夾下,如圖 9.3.1.1所示:
圖9.3.1.1 MDK編譯過程生成的檔案
可以看到,這裡總共生成了48個檔案,共 11 個類型,分别是:.axf、.crf、.d、.dep、
.hex、.lnp、.lst、.o、.htm、bulild_log.htm 和.map。48個檔案看着不是很多,但是随着工程的增大,這些檔案也會越來越多,大項目編譯一次,可以生成幾百甚至上千個這種檔案,不過檔案類型基本就是上面這些。
對于 MDK 工程來說,基本上任何工程在編譯過程中都會有這 11 類檔案,常見的 MDK編譯過程生産檔案類型如表 9.3.1.1所示:
檔案類型 | 說明 |
.o | 可重定向1 對象檔案,每個源檔案(.c/.s 等)編譯都會生成一個.o 檔案 |
.axf | 由 ARMCC 編譯生産的可執行對象檔案,不可重定向2 (絕對位址) 多個.o 檔案連結生成.axf 檔案,我們在仿真的時候,需要用到該檔案 |
.hex | Intel Hex 格式檔案,可用于下載下傳到 MCU,.hex 檔案由.axf 檔案轉換而來 |
.crf | 交叉引用檔案,包含浏覽資訊(定義、辨別符、引用) |
.d | 由 ARMCC/GCC 編譯生産的依賴檔案(.o 檔案所對應的依賴檔案) 每個.o 檔案,都有一個對應的.d 檔案 |
.dep | 整個工程的依賴檔案 |
.lnp | MDK 生成的連結輸入檔案,用于指令輸入 |
.lst | C 語言或彙編編譯器生成的清單檔案 |
.htm | 連結生成的清單檔案 |
.build_log.htm | 最近一次編譯工程時的日志記錄檔案 |
.map | 連接配接器生成的清單檔案/MAP 檔案, 該檔案對我們非常有用 |
表9.3.1.1 常見的中間檔案類型說明
注 1,可重定向是指該檔案包含資料/代碼,但是并沒有指定位址,它的位址可由後續連結的時候進行指定。
注 2,不可重定向是指該檔案所包含的資料/代碼都已經指定位址了,不能再改變。
9.3.2 map檔案分析
.map 檔案是編譯器連結時生成的一個檔案,它主要包含了交叉連結資訊。通過.map 檔案,我們可以知道整個工程的函數調用關系、FLASH 和 RAM 占用情況及其詳細彙總資訊,能具體到單個源檔案(.c/.s)的占用情況,根據這些資訊,我們可以對代碼進行優化。.map 檔案可以分為以下 5 個組成部分:
1, 程式段交叉引用關系(Section Cross References)
2, 删除映像未使用的程式段(Removing Unused input sections from the image)
3, 映像符号表(Image Symbol Table)
4, 映像記憶體分布圖(Memory Map of the image)
5, 映像元件大小(Image component sizes)
9.3.2.1 map 檔案的 MDK 設定
要生成 map 檔案,我們需要在 MDK 的魔術棒→Listing 頁籤裡面,進行相關設定,如圖 9.3.2.1.1所示:
圖9.3.2.1.1 .map 檔案生成設定
圖9.3.2.1.1中紅框框出的部分就是我們需要設定的,預設情況下,MDK 這部分設定就是全勾選的,如果我們想取消掉一些資訊的輸出,則取消相關勾選即可(一般不建議)。
如圖9.3.2.1.1設定好MDK以後,我全編譯目前工程,當編譯完成後(無錯誤),就會生成.map檔案。在 MDK 裡面打開.map 檔案的方法如圖 9.3.2.1.2所示:
圖9.3.2.1.2 打開.map 檔案
①,先確定工程編譯成功(無錯誤)。
②,輕按兩下 LED,打開.map 檔案。
③,map 檔案打開成功。
9.3.2.2 map 檔案的基礎概念
為了更好的分析 map 檔案,我們先對需要用到的一些基礎概念進行一個簡單介紹,相關概念如下:
- Section:描述映像檔案的代碼或資料塊,我們簡稱程式段
- RO:Read Only 的縮寫,包括隻讀資料(RO data)和代碼(RO code)兩部分内容,占用 FLASH 空間
- RW:Read Write 的縮寫,包含可讀寫資料(RW data,有初值,且不為 0),占用 FLASH(存儲初值)和 RAM(讀寫操作)
- ZI:Zero initialized 的縮寫,包含初始化為 0 的資料(ZI data),占用 RAM 空間
- .text:相當于 RO code
- .constdata:相當于 RO data
- .bss:相當于 ZI data
- .data:相當于 RW data
9.3.2.3 map 檔案的組成部分說明
我們前面說map 檔案分為 5 個部分組成,下面以MiniPRO STM32H750開發闆HAL庫例程的實驗1 跑馬燈實驗為例,簡要講解一下。
1. 程式段交叉引用關系(S S ection Cross References s )
這部分内容描述了各個檔案(.c/.s 等)之間函數(程式段)的調用關系,舉個例子如圖9.3.2.3.1所示:
圖9.3.2.3.1 程式段交叉引用關系圖
上圖中,框出部分:main.o(i.main) refers to sys.o(i.sys_stm32_clock_init) for sys_stm32_
clock_init表示:main.c 檔案中的 main 函數,調用了 sys.c 中的 sys_stm32_clock_init函數。其中:i.main表示 main 函數的入口位址,同理 i. sys_stm32_clock_init表示sys_stm32_
clock_init函數的入口位址。
2. 删除映像未使用的程式段(Removing Unused input sections from the image)
這部分内容描述了工程中由于未被調用而被删除的備援程式段(函數/資料),如圖
9.3.2.3.2所示:
圖9.3.2.3.2 删除未用到的程式段
上圖中,列出了所有被移除的程式段,比如usart.c 裡面的 usart_init 函數就被移除了,因為該例程沒用到usart_init函數。
另外,在最後還有一個統計資訊:361 unused section(s) (total 43234 bytes) removed from the image. 表示總共移除了361個程式段(函數/資料),大小為43234位元組。即給我們的 MCU 節省了 43234位元組的程式空間。
為了更好的節省空間,我們一般在 MDK→魔術棒→C/C++頁籤裡面勾選:One ELF
Section per Function,如圖 9.3.2.3.3所示:
圖9.3.2.3.3勾選 One ELF Section per Function
3. 映像符号表(Image Symbol Table)
映像符号表(Image Symbol Table)描述了被引用的各個符号(程式段/資料)在存儲器中的存儲位址、類型、大小等資訊。映像符号表分為兩類:本地符号(Local Symbols)和全局符号(Global Symbols)。
本地符号(Local Symbols)記錄了用 static 聲明的全局變量位址和大小,c 檔案中函數的位址和用 static 聲明的函數代碼大小,彙編檔案中的标号位址(作用域:限本檔案)。
全局符号(Global Symbols)記錄了全局變量的位址和大小,C 檔案中函數的位址及其代碼大小,彙編檔案中的标号位址(作用域:全工程)。
4. 映像記憶體分布圖(Memory Map of the image)
映像檔案分為加載域(Load Region)和運作域(Execution Region),一個加載域必須有至少一個運作域(可以有多個運作域),而一個程式又可以有多個加載域。加載域為映像程式的實際存儲區域,而運作域則是 MCU 上電後的運作狀态。加載域和運作域的簡化關系(這裡僅表示一個加載域的情況)圖,如圖9.3.2.3.4所示:
圖9.3.2.3.4 加載域運作域關系
由圖可知,RW 區也是存放在 ROM(FLASH)裡面的,在執行 main 函數之前,RW(有初值且不為 0 的變量)資料會被拷貝到 RAM 區,同時還會在 RAM 裡面建立 ZI 區(初始化為 0 的變量)。
5. 映像元件大小(Image component sizes)
映像元件大小(Image component sizes)給出了整個映像所有代碼(.o)占用空間的彙總資訊。