STM32記憶體知識

2022-06-09 21:01:41

在瞭解STM32記憶體之前需要了解 MCU 的型號和MDK 中的.map 檔案,很多剛學習 stm32 時都不會過多的去了解 MCU 的選型,是在太枯燥了。這裡在從新瞭解一下,久了就熟悉了。

一、STM32命令規則

二、MDK下生成.map檔案

  1. 在MDK中勾選.map檔案的生成,確認後編譯一下工程即可生成,map檔案。

  2. 開啟.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檔案

  1. 不同檔案中函數呼叫的關係

    ==============================================================================
    
    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函數

  2. 被刪除的冗餘函數

    ==============================================================================
    
    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位元組,如下圖所以:

  3. 區域性標號和全域性標號

    • 區域性標號
      主要是在檔案中用static宣告的全域性變數和函數。組合檔案中的標號地址(作用域限本檔案)

    • 全域性標號
      非static宣告的變數和函數,組合檔案中的標號地址(作用域全域性工程)

    注意:

    • Number 不站地址空間,大小為0。
    • DATA 唯讀資料
    • 檔案中的標號再次用i.宣告,說明在c檔案中用static宣告了的,如下圖所示:

  4. 映像檔案
    映像檔案可以分為載入域和執行域
    載入域反應了ARM可執行映像檔案各個段存放在暫存器中的位置關係。

  5. 元件大小

  6. 映像的真實大小

六、.htm檔案

檔案中做大的作用就是基本統計了所有被呼叫函數的棧stack使用的情況(不考慮中斷巢狀)

  1. 棧的最大深度,呼叫路勁是main ⇒ LED_GPIO_Config ⇒ GPIO_Init

  2. 遞迴呼叫函數

  3. 函數指標

  4. 全域性標號

    比如復位中斷函數,使用的是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