天天看點

《MiniPRO H750開發指南》第九章 STM32啟動過程分析

第九章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所示。​

《MiniPRO H750開發指南》第九章 STM32啟動過程分析

圖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所示。​

《MiniPRO H750開發指南》第九章 STM32啟動過程分析

圖9.2.1.1打開索引搜尋工具的方法​

打開之後,我們以EQU為例,示範一下怎麼使用,如圖9.2.1.2所示。​

《MiniPRO H750開發指南》第九章 STM32啟動過程分析

圖9.2.1.2 搜尋EQU彙編指令​

搜尋到的标題有很多,我們隻需要看Assembler User Guide 這部分即可。​

9.2.2啟動檔案代碼講解​

(1)棧空間的開辟​

棧空間的開辟,源碼如圖9.2.2.1所示:​

《MiniPRO H750開發指南》第九章 STM32啟動過程分析

圖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所示:​

《MiniPRO H750開發指南》第九章 STM32啟動過程分析

圖9.2.2.2堆空間的開辟​

源碼含義:開辟一段大小為0x0000 0200(512位元組)的堆空間,段名為HEAP,不初始化,可讀可寫,8位元組對齊。​

__heap_base表示堆的起始位址,__heap_limit表示堆的結束位址。堆和棧的生長方向相反的,堆是由低向高生長,而棧是從高往低生長。​

堆主要用于動态記憶體的配置設定,像malloc()、calloc()和realloc()等函數申請的記憶體就在堆上面。堆中的記憶體一般由程式員配置設定和釋放,若程式員不釋放,程式結束時可能由作業系統回收。​

接下來是PRESERVE8和THUMB指令兩行代碼。如圖9.2.2.3所示。​

《MiniPRO H750開發指南》第九章 STM32啟動過程分析

圖9.2.2.3 PRESERVE8和THUMB指令​

PRESERVE8:訓示編譯器按照8位元組對齊。​

THUMB:訓示編譯器之後的指令為THUMB指令。​

注意:由于正點原子提供了獨立的記憶體管理實作方式(mymalloc,myfree等),并不需要使用C庫的malloc和free等函數,也就用不到堆空間,是以我們可以設定Heap_Size的大小為0,以節省記憶體空間。​

(3)中斷向量表定義(簡稱:向量表)​

為中斷向量表定義一個資料段,如圖9.2.2.4所示:​

《MiniPRO H750開發指南》第九章 STM32啟動過程分析

圖9.2.2.4 為中斷向量表定義一個資料段​

源碼含義:定義一個資料段,名字為RESET, READONLY表示隻讀。EXPORT表示聲明一個标号具有全局屬性,可被外部的檔案使用。這裡是聲明了__Vectors、__Vectors_End和__Vectors_Size三個标号具有全局性,可被外部的檔案使用。​

STM32H750的中斷向量表定義代碼,如圖9.2.2.5所示。​

《MiniPRO H750開發指南》第九章 STM32啟動過程分析

圖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所示:​

《MiniPRO H750開發指南》第九章 STM32啟動過程分析

圖9.2.2.6 定義隻讀代碼段​

定義一個段命為.text,隻讀的代碼段,在CODE區。​

複位子程式代碼,如圖9.2.2.7所示:​

《MiniPRO H750開發指南》第九章 STM32啟動過程分析

圖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所示:​

《MiniPRO H750開發指南》第九章 STM32啟動過程分析

圖9.2.2.8 中斷服務程式​

可以看到這些中斷服務函數都被[WEAK]聲明為弱定義函數,如果外部檔案聲明了一個标号,則優先使用外部檔案定義的标号,如果外部檔案沒有定義也不會出錯。​

這些中斷函數分為系統異常中斷和外部中斷,外部中斷根據不同晶片有所變化。B指令是跳轉到一個标号,這裡跳轉到一個‘.’,表示無限循環。​

在啟動檔案代碼中,已經把我們所有中斷的中斷服務函數寫好了,但都是聲明為弱定義,是以真正的中斷服務函數需要我們在外部實作。​

如果我們開啟了某個中斷,但是忘記寫對應的中斷服務程式函數又或者把中斷服務函數名寫錯,那麼中斷發生時,程式就會跳轉到啟動檔案預先寫好的弱定義的中斷服務程式中,并且在B指令作用下跳轉到一個‘.’中,無限循環。​

這裡的系統異常中斷部分是核心的,外部中斷部分是外設的。​

(6)使用者堆棧初始化​

ALIGN指令,如圖9.2.2.9所示:​

《MiniPRO H750開發指南》第九章 STM32啟動過程分析

圖9.2.2.9 ALIGN指令​

ALIGN表示對指令或者資料的存放位址進行對齊,一般需要跟一個立即數,預設表示4位元組對齊。要注意的是,這個不是ARM的指令,是編譯器的。​

接下就是啟動檔案最後一部分代碼,使用者堆棧初始化代碼,如圖9.2.2.10所示:​

《MiniPRO H750開發指南》第九章 STM32啟動過程分析

圖9.2.2.10 使用者堆棧初始化代碼​

IF, ELSE, ENDIF是彙編的條件分支語句。​

588行判斷是否定義了__MICROLIB。關于__MICROLIB這個宏定義,我們是在KEIL裡面配置,具體方法如圖9.2.2.11所示。​

《MiniPRO H750開發指南》第九章 STM32啟動過程分析

圖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所示。​

《MiniPRO H750開發指南》第九章 STM32啟動過程分析

圖9.2.3.1 複位序列​

我們看看MiniPRO STM32H750開發闆HAL庫例程的實驗1 跑馬燈實驗中,取出的MPS和PC的值是多少,方法如圖9.2.3.2所示。​

《MiniPRO H750開發指南》第九章 STM32啟動過程分析

圖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後的記憶體情況。​

《MiniPRO H750開發指南》第九章 STM32啟動過程分析

圖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所示:​

《MiniPRO H750開發指南》第九章 STM32啟動過程分析

圖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所示:​

《MiniPRO H750開發指南》第九章 STM32啟動過程分析

圖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所示:​

《MiniPRO H750開發指南》第九章 STM32啟動過程分析

圖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所示:​

《MiniPRO H750開發指南》第九章 STM32啟動過程分析

圖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所示:​

《MiniPRO H750開發指南》第九章 STM32啟動過程分析

圖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所示:​

《MiniPRO H750開發指南》第九章 STM32啟動過程分析

圖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所示:​

《MiniPRO H750開發指南》第九章 STM32啟動過程分析

圖9.3.2.3.4 加載域運作域關系​

由圖可知,RW 區也是存放在 ROM(FLASH)裡面的,在執行 main 函數之前,RW(有初值且不為 0 的變量)資料會被拷貝到 RAM 區,同時還會在 RAM 裡面建立 ZI 區(初始化為 0 的變量)。​

5. 映像元件大小(Image component sizes)​

映像元件大小(Image component sizes)給出了整個映像所有代碼(.o)占用空間的彙總資訊。​