Flash memory(快閃記憶體),在Arduino、AVR的世界裡,也稱為Program Memory,也就是負責存放程式與任何初始資料的地方,需使用特殊的燒錄工具與軟體將編譯後的程式碼燒錄進去,也可以存放bootloader以便進行ISP線上即時燒錄。可讀,但你不能在程式裡下指令寫入東西到Flash。Flash也是一般隨身碟與記憶卡裡所使用的儲存技術,非揮發性,拔掉電源後資料依舊存在不會消失。Flash有其寫入次數限制,若不斷地寫入,總有一天會壞掉,但正常使用下應很難看到那一天。
SRAM,可讀可寫,拔掉電源後裡頭的東西就會消失不見。負責存放靜態資料、堆積(heap)、堆疊(stack),所以是程式裡建立變數進行操作的地方。靜態資料,若不指定初始值則為0,若有則從Flash拷貝到SRAM。堆積,動態配置的記憶體空間,譬如malloc。堆疊,存放函式呼叫時所需東西、區域變數。堆積與堆疊會從SRAM的兩端逐漸成長,若兩方碰在一起就會出問題,可能根本無法執行或執行時呈現怪異行為。
EEPROM,非揮發性,可讀可寫,讀取時須以byte為單位,速度比SRAM慢,也有寫入次數限制。當你需要儲存長期性的資料,就是放在這裡。
那麼,你的程式需要使用多少記憶體空間呢?
Arduino IDE編譯後會顯示Binary sketch size,就是將佔用的Flash大小。bootloader約需0.5 KB。若草稿碼編譯後太大,就無法燒錄到微控制器晶片裡。
根據下圖,Blink範例草稿碼編譯後需要1084 bytes的Flash空間,而我的Uno板的Flash記憶體最大可容納32256 bytes(31.5 KB),因為其中0.5 KB已被bootloader佔用。
EEPROM的讀寫完全控制在你的掌心,所以也知道用了多少。
至於SRAM則為動態,在不同時間點,也就是程式執行到不同的地方時,會不一樣。可透過底下這支函式得知剩餘的SRAM:
int free_ram()
{
extern int __heap_start, *__brkval;
int v;
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}
free_ram回傳的是heap與stack之間剩餘的空間,但heap內可能充滿很多「洞」,這些洞的記憶體雖已釋放,但仍不能被stack所用,而且可能呈現非常分散、斷裂的情況,所以當你下次動態配置heap記憶體時,也有可能無法使用這些洞。總而言之,介於stack與heap之間的剩餘記憶體空間是你真正需要監控的地方,以避免程式當掉。
誰會耗用SRAM呢?當你動態配置heap記憶體時,例如呼叫malloc,就會佔用heap的部份,當你呼叫函式、定義宣告區域變數時,例如儲存整數、顏色值、像素的資料、等等,就會耗用stack的部份,以一塊OLED單色面板解析度為128x64,每個像素需要1位元,8個像素需要1 bytes,所以總共需要1 KB,好大啊!另外,靜態資料不僅會佔用Flash,執行時也會複製到SRAM裡。
如何減少記憶體用量呢?
想減少Flash用量的話,請分析程式碼,移除不需要的程式庫(#include)、移除無作用的函式、移除有定義卻從未存取的變數、移除絕對不會被執行的程式碼;如果真的很緊迫,可拿掉bootloader,但就需要一台燒錄機、而不能使用ISP線上即時燒錄了。
想減少SRAM用量的話,若你的Arduino板子會跟其他運算裝置溝通,例如電腦、手機、等等,那麼可將部分程式碼交由記憶體更多的裝置執行。移除有定義卻從未存取的變數。
當以底下這種寫法定義字串變數時,
char message[] = "I support the Cape Wind project.";
不僅在Flash裡會佔用33 bytes(一個char佔用1 byte,再加上結尾的NULL),執行時也會把這份資料拷貝到SRAM裡,成為以靜態變數的形式,供你修改、讀取。若你不需要修改它,可使用PROGMEM將它存放在Flash裡即可,有需要時再呼叫特殊函式複製到SRAM裡,用完後再放掉。因為使用前必須先下指令將資料從Flash複製到SRAM,然後才能運用,比較麻煩,但可節省SRAM用量。詳情請看參考資料的PROGMEM。
Arduino 1.0引進新的「F()」語法,如下:
Serial.println(F("This string will be stored in flash memory"));
這麼一來,該字串就只會存放於Flash裡,不會在SRAM裡,甚為方便。
程式裡常需要動態配置heap記憶體作為緩衝區,請配置適當的大小別浪費。有些程式庫也會配置緩衝區,譬如Serial序列傳輸介面會使用64 bytes的緩衝區,若你不需要高速傳輸,那麼可以此大小裁掉一半,這個緩衝區的大小定義在HardwareSerial.cpp裡#define SERIAL_BUFFER_SIZE 64。
各種型別的變數會佔用不同大小的記憶體,若能用byte儲存,就不該使用float。
盡量避免動態配置heap記憶體,因為會造成斷裂的情況,以致於即使釋放了也收縮heap佔用的範圍。
若能夠的話,應使用區域變數而非全域變數,區域變數只存在於stack內,呼叫完畢便會消失,但全域變數會一直佔用著記憶體。
參考資料:
- Adafruit Learning System的You know you have a memory problem when... | Memories of an Arduino。
- Arduino官方文件Memory與PROGMEM。
- Arduino PlayGround的Memory與Available Memory。
- March Madness的Arduino Memory Usage。
葉大您好 請問所謂的長期資料要怎麼存在EEPROM裡呢
ReplyDelete謝謝
請參閱
DeleteArduino:自訂整組資料讀寫EEPROM
http://yehnan.blogspot.tw/2014/03/arduinoeeprom.html
我想問 ardunino的gsm是什麼 和他函式庫是不是有imei的函式
ReplyDelete> gsm是什麼
Delete存取gsm sim卡的程式庫。
主要有語音通訊和簡訊sms的功能。
> 是不是有imei的函式
何不自己查閱?
https://www.arduino.cc/en/Reference/GSM
您好
ReplyDelete我在使用F()函式時,不知為何印出來的都是亂碼?
請問是否缺少哪個動作呢?
還是文字檔的編碼?
不用F()時,印字串時正常嗎?
Delete請問您有辦法可以把寫入板子的程式,再叫出來修改嗎?謝謝您
ReplyDelete