在瞭解STM32記憶體之前需要了解 MCU 的型號和MDK 中的.map 檔案,很多剛學習 stm32 時都不會過多的去了解 MCU 的選型,是在太枯燥了。這裡在從新瞭解一下,久了就熟悉了。
一、STM32命令規則
二、MDK下生成.map檔案
-
在MDK中勾選.map檔案的生成,確認後編譯一下工程即可生成,map檔案。
-
開啟.map檔案
三、MDK下檔案基本概念
在.map檔案的最後可以看到檔案資訊的統計,如下圖所示:
當然每次編譯完成後也可以看到統計資訊,如下圖所示:
瞭解MDK下的一些常用變數名:
變數 | 作用 |
---|---|
code | 程式碼儲存區,存放函式體的二進位制程式碼 |
Ro-data | 只讀資料儲存區,存放字常量資料型別(如const型別)程式結束後有系統自動釋放 |
RW-data | 初始化可讀寫變數的大小,程式結束後由系統自動釋放。 |
ZI-data | 沒有初始化的可讀寫變數大小,程式結束後由系統自動釋放。 |
heap | 堆區,一般由程式設計師分配釋放,若程式設計師不釋放,程式結束時可能由OS釋放。 |
stack | 棧區,由編譯器自動分配釋放,存放函式的引數值,區域性變數的值等。 |
.text | 與RO-code同義 |
.constdata | 與RO-data同義 |
.bss | 未初始化的全域性和靜態變數,編譯器自動初始化為0 |
.data | 初始化的全域性和靜態變數(與RW-data同義) |
PAD | 地址空間對齊 |
RO Size | 包含Code及RO Data,表示只讀資料佔用Flash空間的大小。 |
RW Size | 包含RW Data及ZI Data,表示執行時佔用的RAM的大小。 |
ROM Size | 包含Code,RO Data及RW Data,表示燒寫程式所佔用的Flash的大小。 |
注意:棧向下生長,記憶體地址由高至低;堆向上,記憶體地址由低至高
通過上面表格可知STM32在程式設計時所用RAM和ROM的大小:
Flash(ROM)=Code+Ro-data
Sram(RAM)=Rw-data+ZI-data
四、STM32記憶體
這裡我找了一位大佬總結的部落格“STM32記憶體知識你真的瞭解嗎?”,感覺挺好的,所以我直接引用一下啟動的圖片,如下圖所示:
- STM32程式執行的流程
程式在執行之前,需要可執行將映象檔案(一般是bin或hex檔案),通過燒寫工具寫入STM32的Flash中。STM32上電啟動(從Flash啟動時)後會將RW段中的RW-data(初始化的全域性變數)拷貝到RAM中,然後根據編譯器給出的ZI地址和大小分配出ZI段,並將這塊RAM區域清零。如下圖所示:左邊是每上電flash+ram的狀態,右邊是上電後執行時flash+ram的狀態。
注意:
- 可執行映像檔案燒錄到 STM32 後的記憶體分佈包含 RO 段和 RW 段兩個部分,其中其中 RO 段中儲存了 Code、RO-data 的資料,RW 段儲存了 RW-data 的資料,由於 ZI-data 都是 0,所以未包含在映像檔案中。
- STM32執行時不會拷貝RO段,因為CPU的可執行程式碼是直接從Flash中讀取的。
STM32程式設計時需要注意的事項
-
堆疊的大小在編譯器編譯之後是不知道的,只有在執行時才知道,所以容易造成堆疊溢位(發生hardfault錯誤),那怎麼知道自己的記憶體大小了,通過選型手冊就知道了,比如我使用的是STM32F103C8T6,ROM是64k,RAM是20k,如下圖所示:
-
程式中的常量,如果沒加const也會編譯到SRAM裡,加了const會被編譯到flash中。
-
棧向下生長,記憶體地址由高至低;堆向上,記憶體地址由低至高,堆疊之間沒有固定的界限,堆疊衝突時會導致系統崩潰,如下圖所示:
五、.map檔案
-
不同檔案中函式呼叫的關係
============================================================================== Section Cross References startup_stm32f10x_hd.o(RESET) refers to startup_stm32f10x_hd.o(.text) for Reset_Handler startup_stm32f10x_hd.o(.text) refers to system_stm32f10x.o(.text) for SystemInit startup_stm32f10x_hd.o(.text) refers to entry.o(.ARM.Collect$$$$00000000) for __main stm32f10x_rcc.o(.text) refers to stm32f10x_rcc.o(.data) for APBAHBPrescTable stm32f10x_gpio.o(.text) refers to stm32f10x_rcc.o(.text) for RCC_APB2PeriphResetCmd stm32f10x_usart.o(.text) refers to stm32f10x_rcc.o(.text) for RCC_APB2PeriphResetCmd led.o(.text) refers to stm32f10x_rcc.o(.text) for RCC_APB2PeriphClockCmd led.o(.text) refers to stm32f10x_gpio.o(.text) for GPIO_Init main.o(.text) refers to led.o(.text) for LED_GPIO_Config main.o(.text) refers to stm32f10x_gpio.o(.text) for GPIO_ResetBits entry.o(.ARM.Collect$$$$00000000) refers (Special) to entry10a.o(.ARM.Collect$$$$0000000D) for __rt_final_cpp entry.o(.ARM.Collect$$$$00000000) refers (Special) to entry11a.o(.ARM.Collect$$$$0000000F) for __rt_final_exit entry.o(.ARM.Collect$$$$00000000) refers (Special) to entry7b.o(.ARM.Collect$$$$00000008) for _main_clock entry.o(.ARM.Collect$$$$00000000) refers (Special) to entry8b.o(.ARM.Collect$$$$0000000A) for _main_cpp_init entry.o(.ARM.Collect$$$$00000000) refers (Special) to entry9a.o(.ARM.Collect$$$$0000000B) for _main_init entry.o(.ARM.Collect$$$$00000000) refers (Special) to entry5.o(.ARM.Collect$$$$00000004) for _main_scatterload entry.o(.ARM.Collect$$$$00000000) refers (Special) to entry2.o(.ARM.Collect$$$$00000001) for _main_stk entry2.o(.ARM.Collect$$$$00000001) refers to entry2.o(.ARM.Collect$$$$00002712) for __lit__00000000 entry2.o(.ARM.Collect$$$$00002712) refers to startup_stm32f10x_hd.o(STACK) for __initial_sp entry2.o(__vectab_stack_and_reset_area) refers to startup_stm32f10x_hd.o(STACK) for __initial_sp entry2.o(__vectab_stack_and_reset_area) refers to entry.o(.ARM.Collect$$$$00000000) for __main entry5.o(.ARM.Collect$$$$00000004) refers to init.o(.text) for __scatterload entry9a.o(.ARM.Collect$$$$0000000B) refers to main.o(.text) for main entry9b.o(.ARM.Collect$$$$0000000C) refers to main.o(.text) for main init.o(.text) refers to entry5.o(.ARM.Collect$$$$00000004) for __main_after_scatterload ==============================================================================
如
main.o(.text) refers to led.o(.text) for LED_GPIO_Config
,是main.c檔案中呼叫了led.c檔案中的LED_GPIO_Config函式 -
被刪除的冗餘函式
============================================================================== Removing Unused input sections from the image. Removing startup_stm32f10x_hd.o(HEAP), (0 bytes). Removing core_cm3.o(.emb_text), (32 bytes). Removing system_stm32f10x.o(.constdata), (20 bytes). Removing misc.o(.text), (220 bytes). Removing stm32f10x_usart.o(.text), (880 bytes). 5 unused section(s) (total 1152 bytes) removed from the image. ==============================================================================
刪除冗餘的函式,有效降低程式的程式碼量,MDK自動優化,可以通過“One ELF Section per Function”選項開啟,開啟後可以大大優化程式程式碼量,開啟方式是如下圖所示:
開啟後再次編譯,看看.map檔案中刪除了81個函式,優化了2762位元組,如下圖所以:
-
區域性標號和全域性標號
-
區域性標號
主要是在檔案中用static宣告的全域性變數和函式。彙編檔案中的標號地址(作用域限本檔案)
-
全域性標號
非static宣告的變數和函式,彙編檔案中的標號地址(作用域全域性工程)
注意:
- Number 不站地址空間,大小為0。
- DATA 只讀資料
- 檔案中的標號再次用i.宣告,說明在c檔案中用static宣告瞭的,如下圖所示:
-
-
映像檔案
映像檔案可以分為載入域和執行域
載入域反應了ARM可執行映像檔案各個段存放在暫存器中的位置關係。
-
元件大小
-
映像的真實大小
六、.htm檔案
檔案中做大的作用就是基本統計了所有被呼叫函式的棧stack使用的情況(不考慮中斷巢狀)
-
棧的最大深度,呼叫路勁是main ⇒ LED_GPIO_Config ⇒ GPIO_Init
-
遞迴呼叫函式
-
函式指標
-
全域性標號
比如復位中斷函式,使用的是Thumb指令,佔用0位元組棧空間,函式程式碼大小2位元組
比如系統時鐘初始化函式SystemInit ,使用的是Thumb指令,函式程式碼大小68位元組,佔用棧空間8位元組,程式碼深度28位元組,函式呼叫路徑是Call Chain = SetSysClock ⇒ SetSysClockTo72
參考文獻
stm32的記憶體分佈:https://blog.csdn.net/BooleanWater/article/details/119278723
STM32微控制器的記憶體分佈詳解(1):https://www.bilibili.com/read/cv13912565
STM32記憶體知識你真的瞭解嗎?:https://blog.csdn.net/qq_49864684/article/details/119887704
MDK生成的map和htm檔案分析:https://www.bilibili.com/video/BV1t3411C7Pu/?spm_id_from=autoNext