2018年1月31日 星期三

技術筆記 STM32 memory usage

STM32F100的電路圖,裡面包含著16k的flash memory

印表玩具

   STM32技術

自從使用C#或JAVA後,就不再關心記憶體配置了(這所謂的記憶體,不是pointer和leak的問題)。為了製作印表玩具使用的STM32,遇到許多記憶體操作問題。分別整理如下。

FLASH RAM原理


【動態記憶體(RAM)】與【唯讀記憶體(ROM or Flash)】是截然不同的。彼此關係,包括STACK和HEAP資料如下。



FLASH:
特性是拔電時,資料還存在著。資料也是區塊寫入讀出。
以前PC程式有硬碟可以存放程式,Keil編譯好的程式,就是直接放在這區。這裡面包括程式本體、resource、變數的宣告值等。資料讀出很方便,但是寫入很"麻煩"。
討論變數與flash的關係
1. int i = 100;  後面的100是要寫在 flash 中。程式啟動後會在RAM中設定 i ,然後從flash中拿出100放在一起。是compiler根據整個程式大小、最佳化等,東算西算決定放在這個位置。
2. const int i = 100; compiler就會將這個變數也會放在flash中(而不是RAM)

RAM:
特性是拔電時,資料就消失。資料可以某個位置直接寫入讀出。
這裡區分成三種資料,是程式運作中需要的項目,可以讀也可以寫。
1. global 變數
2. function 中宣告的變數,這會放在stack中
3. new malloc 的變數,這會放在heap中

這篇文章對記憶體使用有很清楚介紹。下面內容大多是從連結取得,當成筆記。

CODE、RO、RW、ZI Data域及堆棧空間
在工程的編譯提示輸出信息中有一個語句"Program Size:Code=xx RO-data=xx RW-data=xx ZI-data=xx",它說明了程序各個域的大小,編譯後,應用程序中所有具有同一性質的數據(包括代碼)被歸到一個域,程序在存儲或運行的時候,不同的域會呈現不同的狀態,這些域的意義如下:

    Code:即代碼域,它指的是編譯器生成的機器指令,這些內容被存儲到ROM區。

    RO-data:Read Only data,即只讀數據域,它指程序中用到的只讀數據,這些數據被存儲在ROM區,因而程序不能修改其內容。例如C語言中const關鍵字定義的變數就是典型的RO-data。

    RW-data:Read Write data,即可讀寫數據域,它指初始化為"非0值"的可讀寫數據,程序剛運行時,這些數據具有非0的初始值,且運行的時候它們會常駐在RAM區,因而應用程序可以修改其內容。例如C語言中使用定義的全局變數,且定義時賦予"非0值"給該變數進行初始化。

    ZI-data:Zero Initialie data,即0初始化數據,它指初始化為"0值"的可讀寫數據域,它與RW-data的區別是程序剛運行時這些數據初始值全都為0,而後續運行過程與RW-data的性質一樣,它們也常駐在RAM區,因而應用程序可以更改其內容。例如C語言中使用定義的全局變數,且定義時賦予"0值"給該變數進行初始化(若定義該變數時沒有賦予初始值,編譯器會把它當ZI-data來對待,初始化為0);

    ZI-data的棧空間(Stack)及堆空間(Heap):在C語言中,函數內部定義的局部變數屬於棧空間,進入函數的時候從向棧空間申請內存給局部變數,退出時釋放局部變數,歸還內存空間。而使用malloc動態分配的變數屬於堆空間。在程序中的棧空間和堆空間都是屬於ZI-data區域的,這些空間都會被初始值化為0值。編譯器給出的ZI-data占用的空間值中包含了堆棧的大小(經實際測試,若程序中完全沒有使用malloc動態申請堆空間,編譯器會優化,不把堆空間計算在內)。這裡提到如何看編譯後的檔案大小

FLASH RAM起動過程


程式起動後的動作如下

以int i = 100;這個例子來說,i是RAM,但是100是FLASH。此時又有一個問題出現了,上面的描述在使用時,100早就已經放在var裡面了,很明顯的,一定有一個東西把100放進去var那個RAM的位置。C-Language程式的進入點是main(),所以在main()之前,一定有一個東西做了很多事,讓C的程式能夠從main()開始順利運行,那個東西,叫做boot loader,換句話說,因為刻意的安排,CPU會先執行boot loader,等所有環境建立完成後,再進入main()。

當CPU通電開始執行時,依照CPU不同種類,一定固定由某個位置開始抓第一行指令開始執行,boot loader的第一行就放在這裡。依據CPU的複雜度,如ARM7,9就有專門的boot loader,可以做出很複雜的功能,例如可以用TFTP載入,單純的如8051的compiler:IAR51,因為事情很固定,它會自動幫你加上已經寫好的boot loader。

RW-data和ZI-data它們僅僅是初始值不一樣而已,為什麼編譯器非要把它們區分開?這就涉及到程序的存儲狀態了,應用程序具有靜止狀態和運行狀態。靜止態的程序被存儲在非易失存儲器中,如STM32的內部FLASH,因而系統掉電後也能正常保存。但是當程序在運行狀態的時候,程序常常需要修改一些暫存數據,由於運行速度的要求,這些數據往往存放在內存中(RAM),掉電後這些數據會丟失。因此,程序在靜止與運行的時候它在存儲器中的表現是不一樣的



圖中的左側是應用程序的存儲狀態,右側是運行狀態,而上方是RAM存儲器區域,下方是ROM存儲器區域。

程序在存儲狀態時,RO節(RO section)及RW節都被保存在ROM區。當程序開始運行時,內核直接從ROM中讀取代碼,並且在執行主體代碼前,會先執行一段加載代碼,它把RW節數據從ROM複製到RAM,並且在RAM加入ZI節,ZI節的數據都被初始化為0。加載完後RAM區準備完畢,正式開始執行主體程序。

編譯生成的RW-data的數據屬於圖中的RW節,ZI-data的數據屬於圖中的ZI節。是否需要掉電保存,這就是把RW-data與ZI-data區別開來的原因,因為在RAM創建數據的時候,默認值為0,但如果有的數據要求初值非0,那就需要使用ROM記錄該初始值,運行時再複製到RAM。

STM32的RO區域不需要加載到SRAM,內核直接從FLASH讀取指令運行。計算機系統的應用程序運行過程很類似,不過計算機系統的程序在存儲狀態時位於硬盤,執行的時候甚至會把上述的RO區域(代碼、只讀數據)加載到內存,加快運行速度,還有虛擬內存管理單元(MMU)輔助加載數據,使得可以運行比物理內存還大的應用程序。而STM32沒有MMU,所以無法支持Linux和Windows系統。

當程序存儲到STM32晶片的內部FLASH時(即ROM區),它占用的空間是Code、RO-data及RW-data的總和,所以如果這些內容比STM32晶片的FLASH空間大,程序就無法被正常保存了。當程序在執行的時候,需要占用內部SRAM空間(即RAM區),占用的空間包括RW-data和ZI-data。應用程序在各個狀態時各區域的組成見表 482。

宣告的變數太大無法編譯


在MDK中,我們建立的工程一般會選擇晶片型號,選擇後就有確定的FLASH及SRAM大小,若代碼超出了晶片的存儲器的極限,編譯器會提示錯誤,這時就需要裁剪程序了,裁剪時可針對超出的區域來優化。

如果宣告變數太大,編譯時就會有錯誤,
.\Targets\STM32F429_Discovery\project.axf: Error: L6406E: No space in execution regions with .ANY selector matching xxx.o(.bss).
.\Targets\STM32F429_Discovery\project.axf: Error: L6407E: Sections of aggregate size 0x98fa8 bytes could not fit into .ANY selector(s).
換成const有幫助,但這樣一來原本能動的程式就無法執行。

HEAP設定方式



能通過編譯,不代表程式運作時不會有問題。在SD CARD的範例中就會發現,malloc記憶體時會有錯誤。在網頁中提到,Dynamic allocation is used when new file should be created. You need at least 512 bytes of free memory in HEAP region for malloc to allocate data, otherwise, f_open function will return FR_NOT_ENOUGH_CORE result.

default Keil uVision project, HEAP memory is by default set to 0x200 Bytes = 512kB which can be quite small for something like strings can be in size. open .s (startup file) inside your project and edit variable “Heap_Size” to a value you want to reserve for HEAP

這個網頁中,提到如何設定HEAP SIZE,Heap_Size可以從200改成800。檔案位置在D:\stmlib\git\trunk\00-STM32F4xx_STANDARD_PERIPHERAL_DRIVERS\CMSIS\Device\ST\STM32F4xx\Source\Templates\arm\startup_stm32f429_439xx.s。

TM範例  STACKOVERFLOW討論

外部SD RAM原理


STM32控制器芯片內部有一定大小的SRAM及FLASH作為內存和程序存儲空間,但當程序較大,內存和程序空間不足時,就需要在STM32芯片的外部擴展存儲器了。

STM32F429 Discovery board has external 64Mbits or 8MBytes SDRAM chip ISSI IS42S16400. It has a FMC (Flexible Memory Control) peripheral to driving external SDRAM with hardware. 

FMC hardware is able to store up to 32bits variables at same time.
STM32F429使用FMC外設來管理擴展的存儲器,FMC是Flexible Memory Controller的縮寫,譯為可變存儲控制器。它可以用於驅動包括SRAM、SDRAM、NOR FLASH以及NAND FLSAH類型的存儲器。STM32F429的FMC外設才支持該功能,且只支持普通的SDRAM,不支持DDR類型的SDRAM。FMC是如何運作,這篇有非常非常非常深刻的說明。

外部SD RAM:簡單操作


包括TM方式和SPORTS方式,容易瞭解但沒有實用性。
TM的範例
透過API呼叫 FMC Flexible Memory Control (只有429之後才有 407沒有 )
包成 宣告 例如 TM_SDRAM_Write8(location, 8bitvalue); 8bitvalue = TM_SDRAM_Read8(location); 可以動態在SDRAM某個位置進行讀取。
SPORTS範例
取用記憶體,還是使用FMC的指令

外部SD RAM:整合入malloc


讀寫SDRAM存儲的內容時,需要使用指針或者__attribute__((at(具體地址)))來指定變數的位置,修改sct文件,讓連結器自動分配全局變數到SDRAM的地址併進行管理,使得利用SDRAM的空間就跟內部SRAM一樣簡單。

摘要內容,連結


人體自動化 字型文章

網路上找到一些連結似乎有更簡單方法。OPTREC似乎也有使用這個技巧。

連結1
連結2
連結3

沒有留言:

張貼留言