説明
SRAMの代わりにフラッシュ(プログラム)メモリにデータを保存します。Arduinoボードで使用できるさまざまな種類のメモリについての説明があります。
PROGMEMキーワードは変数修飾子であり、pgmspace.hで定義されたデータ型でのみ使用されます。これは、コンパイラに「この情報を通常のSRAMではなく、フラッシュメモリに入れてください」と指示します。
PROGMEMはpgmspace.hライブラリの一部です。最近のバージョンのIDEには自動的に含まれています。ただし、IDEのバージョンが1.0(2011)以下の場合は、まずスケッチの先頭で、次のようにライブラリをインクルードする必要があります。
#PROGMEMは1つの変数に使用することもできますが、実際に使用する価値があるのは、より大きなデータブロックを保存する必要がある場合だけです。これは通常、配列(または今回の議論の対象外である他のC++データ構造)に保存するのが最も簡単です。
PROGMEMの使用方法も2つのステップに分かれています。データをフラッシュ・メモリに格納した後、プログラム・メモリからSRAMにデータを読み出して、それを使って何かをするために、pgmspace.hライブラリで定義されている特別なメソッド(関数)が必要になります。
構文
const dataType variableName[] PROGMEM = {data0, data1, data3…};
PROGMEMは変数修飾子なので、どこに置くべきかという明確なルールはなく、Arduinoのコンパイラは以下の定義のすべてを受け入れます。しかし、実験によると、様々なバージョンのArduino(GCCのバージョンと関係があります)では、PROGMEMがある場所では動作し、別の場所では動作しないことがあります。以下の「文字列テーブル」の例は、Arduino 13で動作することが確認されています。それ以前のバージョンのIDEでは、PROGMEMが変数名の後に含まれているとうまく動作するかもしれません。
const dataType variableName[] PROGMEM = {}; // この形式を使用します。
const PROGMEM dataType variableName[] = {}; // またはこの形式を使用します。
const dataType PROGMEM variableName[] = {}; // この形式ではありません。
dataType: 許可されるデータ型:任意の変数型。
variableName: データの配列の名前です。
プログラム例
以下のコードは、PROGMEMに対するunsigned chars(バイト)およびints(2バイト)の読み取りおよび書き込み方法を示しています。
// 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; char myChar; void setup() { Serial.begin(9600); while (!Serial); // wait for serial port to connect. Needed for native USB // put your setup code here, to run once: // read back a 2-byte int for (byte k = 0; k < 5; k++) { displayInt = pgm_read_word_near(charSet + k); Serial.println(displayInt); } Serial.println(); // read back a char for (byte k = 0; k < strlen_P(signMessage); k++) { myChar = pgm_read_byte_near(signMessage + k); Serial.print(myChar); } Serial.println(); } void loop() { // put your main code here, to run repeatedly: }
文字列の配列
LCDを使ったプロジェクトのように、大量のテキストを扱う場合、文字列の配列を設定すると便利なことが多い。文字列自体が配列なので、これは2次元配列の一例です。
このような配列は構造が大きくなりがちなので、プログラムのメモリ上に置くことが望ましい場合があります。以下のコードはその例です。
/* PROGMEM string demo How to store a table of strings in program memory (flash), and retrieve them. Information summarized from: http://www.nongnu.org/avr-libc/user-manual/pgmspace.html Setting up a table (array) of strings in program memory is slightly complicated, but here is a good template to follow. Setting up the strings is a two-step process. First, define the strings. */ #include <avr/pgmspace.h> const char string_0[] PROGMEM = "String 0"; // "String 0" etc are strings to store - change to suit. const char string_1[] PROGMEM = "String 1"; const char string_2[] PROGMEM = "String 2"; const char string_3[] PROGMEM = "String 3"; const char string_4[] PROGMEM = "String 4"; const char string_5[] PROGMEM = "String 5"; // Then set up a table to refer to your strings. const char *const string_table[] PROGMEM = {string_0, string_1, string_2, string_3, string_4, string_5}; char buffer[30]; // make sure this is large enough for the largest string it must hold void setup() { Serial.begin(9600); while (!Serial); // wait for serial port to connect. Needed for native USB Serial.println("OK"); } void loop() { /* Using the string table in program memory requires the use of special functions to retrieve the data. The strcpy_P function copies a string from program space to a string in RAM ("buffer"). Make sure your receiving string in RAM is large enough to hold whatever you are retrieving from program space. */ for (int i = 0; i < 6; i++) { strcpy_P(buffer, (char *)pgm_read_word(&(string_table[i]))); // Necessary casts and dereferencing, just copy. Serial.println(buffer); delay(500); } }
注意と警告
PROGMEMで動作させるためには、変数はグローバルに定義されているか、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()のような命令があった場合
Serial.print(“Write something on the Serial Monitor”);
のような命令が使われると、通常、印刷される文字列はRAMに保存されます。もし、あなたのスケッチがシリアルモニターにたくさんのものを印刷するなら、簡単にRAMをいっぱいにすることができます。また、FLASHメモリの空き容量がある場合は、以下の構文を使って、文字列をFLASHに保存する必要があることを簡単に示すことができます。
Serial.print(F(“Write something on the Serial Monitor that is stored in FLASH”));