2017年3月13日 星期一

[Arduino] 淺談記憶體2 -- PROGRAM

在前篇文中( http://pizgchen.blogspot.tw/2017/03/arduino-1.html )我們說明了 Arduino 有哪些類型的記憶體,其中有提到一個關鍵字「PROGRAM」,本文旨在說明如何使用 PROGRAM 這個變數修飾符。


使用 PROGRAM 的時機

Arduno UNO 只有 2k bytes 的 SRAM 空間讓您存放及運作變數,若是您要將大量的訊息送到序列監控視窗顯示出來,或是您要設置一個供給查表法使用的大資料量表格,這時您有可能會使用超過 2k bytes 的空間。

在您使用超過 2k bytes 的 SRAM 空間時,也許編譯時沒有異狀,但這並不表示您的程式能夠正常運作。

要解決 SRAM 空間不足的問題,我們可以將這些大量資料從 SRAM "搬到" Flash (程式碼就是儲存在 Flash),在需要運作變數時再將這些資料從 Flash "搬回" SRAM 內。那麼如何讓 Arduino 知道哪些資料將會被這樣使用,答案就是使用 PROGRAM 這個變數修飾符來宣告。


如何使用 PROGRAM

PROGRAM 變數修飾符是被定義在 pgmspace.h 之中,它是 AVR 架構中 pgmspace.h 函式庫眾多函式的其中之一。所以您要使用 PROGRAM 之前,必須在程式的開頭包含入這個函式庫,如下:

#include <avr/pgmspace.h>

然後,使用下列句法給值

const dataType variableName [ ] PROGRAM = {data0, data1, data2...};

dataType - 表資料型態。
variableName - 表變數名稱。


PROGRAM 只是一個變數修飾符,它沒有被規定應該放在哪個位置,所以下列三種宣告方式都可以通過編譯器的解析:

const dataType variableName [ ] PROGRAM = {data0, data1, data2...};
const PROGRAM dataType variableName [ ] = {data0, data1, data2...};
const dataType PROGRAM variableName [ ] = {data0, data1, data2...};

雖然 PROGRAM 可以使用在單一變數上,但大多情況我們會用它來處理大量的資料,尤其是大量的陣列資料。下列表格讓您瞭解與比較不同的資料型態會佔用多少記憶體:



再提醒您一次,我們需要兩個步驟來使用 PROGRAM,首先用 PROGRAM 宣告一個變數並給值,然後要使用這些變數的值時再將它從 Flash 讀回到 SRAM。


範例

下列程式片段在示範如何讀寫 char (1 byte) 和 int (2 bytes) 到 PROGRAM。

#include <avr/pgmspace.h>


// save some unsigned ints
const PROGMEM  uint16_t charSet[]  = { 65000, 32796, 16843, 10, 11234};

// save some chars
const char signMessage[] PROGMEM  = {"I AM PREDATOR,  UNSEEN COMBATANT. CREATED BY THE UNITED STATES DEPART"};

unsigned int displayInt;
int k;    // counter variable
char myChar;


void setup() {
  Serial.begin(9600);
  while (!Serial);

  // put your setup code here, to run once:
  // read back a 2-byte int
  for (= 0; k < 5; k++)
  {
    displayInt = pgm_read_word_near(charSet + k);
    Serial.println(displayInt);
  }
  Serial.println();

  // read back a char
  int len = strlen_P(signMessage);
  for (= 0; k < len; k++)
  {
    myChar =  pgm_read_byte_near(signMessage + k);
    Serial.print(myChar);
  }

  Serial.println();
}

void loop() {
  // put your main code here, to run repeatedly:

}


字串陣列


將資料設置為字串陣列通常是最方便使用的,尤其是要將大量的文字顯示到 LCD 之中時。因為字串本身就是一個字元陣列,而字串陣列實際上就是一個二維的字元陣列。

傾向於將大型的結構化資料放到 Flash 之中,往往是比較好的方式。下列程式碼就在說明這樣的概念:

#include <avr/pgmspace.h>


// save some unsigned ints
const PROGMEM  uint16_t charSet[]  = { 65000, 32796, 16843, 10, 11234};

// save some chars
const char signMessage[] PROGMEM  = {"I AM PREDATOR,  UNSEEN COMBATANT. CREATED BY THE UNITED STATES DEPART"};

unsigned int displayInt;
int k;    // counter variable
char myChar;


void setup() {
  Serial.begin(9600);
  while (!Serial);

  // put your setup code here, to run once:
  // read back a 2-byte int
  for (= 0; k < 5; k++)
  {
    displayInt = pgm_read_word_near(charSet + k);
    Serial.println(displayInt);
  }
  Serial.println();

  // read back a char
  int len = strlen_P(signMessage);
  for (= 0; k < len; k++)
  {
    myChar =  pgm_read_byte_near(signMessage + k);
    Serial.print(myChar);
  }

  Serial.println();
}

void loop() {
  // put your main code here, to run repeatedly:

}


注意

請注意,為了要使用 PROGRAM,您必須將變數指定為全域變數(Global)或靜態變數(Static)。

下列程式碼若是被放在函式裏面,它將不會運作

const char long_str[] PROGMEM = "Hi, I would like to tell you a bit about myself.\n";

下列程式碼若是被放在函式裏面,它也會運作

const static char long_str[] PROGMEM = "Hi, I would like to tell you a bit about myself.\n"


F() 函式

如果您在程式中有使用這樣的句法

Serial.print("Write something on  the Serial Monitor");

這串要印出的文字通常是被存放在記憶體之中,如果您印出非常大量這樣的文字到 Serial Monitor,將會很容易把記憶體耗光。如果您有閒置的 Flash 記憶體空間,您可以很輕易地將要印出的文字存放到 Flash 裏,如下:

Serial.print(F("Write something on the Serial Monitor that is stored in FLASH"));


相關連結

淺談記憶體1 -- Memory http://pizgchen.blogspot.tw/2017/03/arduino-1.html

Arduino PROGRAM https://www.arduino.cc/en/Reference/PROGMEM



2 則留言:

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

    回覆刪除