2013/08/16

Arduino:關於記憶體之二三事

這一篇要介紹Arduino微控制器開發板裡可供使用的記憶體,若以Arduino Uno板為例,其微控制器晶片是Atmel公司的ATmega328P,含有32KB Flash memory、2KB SRAM、1KB EEPROM,至於其他板子請到維基百科查看,或查閱設計製造商提供的產品規格資料表,若是Arduino Leonardo(Atmega32u4)則分別是32、2.5、1,。

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內,呼叫完畢便會消失,但全域變數會一直佔用著記憶體。


參考資料


7 comments:

  1. 葉大您好 請問所謂的長期資料要怎麼存在EEPROM裡呢
    謝謝

    ReplyDelete
    Replies
    1. 請參閱
      Arduino:自訂整組資料讀寫EEPROM
      http://yehnan.blogspot.tw/2014/03/arduinoeeprom.html

      Delete
  2. 我想問 ardunino的gsm是什麼 和他函式庫是不是有imei的函式

    ReplyDelete
    Replies
    1. > gsm是什麼
      存取gsm sim卡的程式庫。
      主要有語音通訊和簡訊sms的功能。

      > 是不是有imei的函式
      何不自己查閱?
      https://www.arduino.cc/en/Reference/GSM

      Delete
  3. 您好
    我在使用F()函式時,不知為何印出來的都是亂碼?
    請問是否缺少哪個動作呢?
    還是文字檔的編碼?

    ReplyDelete
    Replies
    1. 不用F()時,印字串時正常嗎?

      Delete
  4. 請問您有辦法可以把寫入板子的程式,再叫出來修改嗎?謝謝您

    ReplyDelete