Arduinoのドキュメントによれば、文字列などの定数や、実行時に変更したくないものはプログラムメモリに保持することが可能です。
すべての定数は、最初はプログラムメモリにあります。電源がオフのとき、彼らは他にどこにいますか?
私はそれをコードセグメントのどこかに埋め込んでいると考えています。これは、フォンノイマンアーキテクチャ内でかなり可能でなければなりません。
これは実際にはハーバードアーキテクチャです。
アクセスする前に、どうしてコンテンツをRAMにコピーする必要があるのですか?
あなたはしません。実際、ハードウェア命令(LPM-プログラムメモリのロード)があり、プログラムメモリからレジスタにデータを直接移動します。
Arduino UnoのVGAモニターへの出力でこのテクニックの例を持っています。そのコードでは、プログラムメモリにビットマップフォントが格納されています。その場で読み込まれ、次のように出力にコピーされます。
// blit pixel data to screen
while (i--)
UDR0 = pgm_read_byte (linePtr + (* messagePtr++));
これらの行の分解は、(部分的に)示しています。
f1a: e4 91 lpm r30, Z+
f1c: e0 93 c6 00 sts 0x00C6, r30
プログラムメモリの1バイトがR30にコピーされ、すぐにUSARTレジスタUDR0に格納されていることがわかります。RAMは必要ありません。
ただし、複雑さがあります。通常の文字列の場合、コンパイラはPROGMEMではなくRAMでデータを検索することを期待しています。それらは異なるアドレス空間であるため、RAMの0x200はPROGMEMの0x200とは異なります。したがって、コンパイラはプログラムの起動時に定数(文字列など)をRAMにコピーするという問題を抱えているため、後でその違いを知る必要はありません。
コード(32kiB)は2kiB RAMだけでどのように処理されますか?
良い質問。それらをすべてコピーする余地がないため、2 KBを超える定数文字列があっても問題はありません。
そのため、メニューやその他の冗長なものを書いている人は、文字列にPROGMEM属性を追加するために追加の手順を実行します。これにより、RAMへのコピーが無効になります。
しかし、プログラムメモリからデータを読み取って出力するだけの命令には戸惑っています。
PROGMEM属性を追加する場合は、これらの文字列が別のアドレス空間にあることをコンパイラーに知らせる手順を実行する必要があります。完全な(一時的な)コピーを作成するのは1つの方法です。または、一度に1バイトずつ、PROGMEMから直接印刷します。その例は次のとおりです。
// Print a string from Program Memory directly to save RAM
void printProgStr (const char * str)
{
char c;
if (!str)
return;
while ((c = pgm_read_byte(str++)))
Serial.print (c);
} // end of printProgStr
この関数にPROGMEMの文字列へのポインターを渡すと、RAMからではなくPROGMEMからデータをプルするために「特別な読み取り」(pgm_read_byte)が行われ、印刷されます。これには、バイトごとに1クロックサイクルかかることに注意してください。
そしてさらに興味深い:この式のようなリテラル定数は、a = 5*(10+7)
レジスタにロードする前に実際にRAMにコピーされる5、10、7はどうなりますか?信じられません。
いいえ、そうである必要はありません。これは、「リテラルをレジスターにロードする」命令にコンパイルされます。その命令はすでにPROGMEMにあるため、リテラルが処理されます。それをRAMにコピーしてから読み戻す必要はありません。
定数データをプログラムメモリに格納する(PROGMEM)のページに、これらの詳細な説明があります。これには、文字列と文字列の配列を簡単に設定するためのサンプルコードがあります。
また、PROGMEMから単に印刷する簡単な方法であるF()マクロについても説明します。
Serial.println (F("Hello, world"));
プリプロセッサの複雑さが少しあるため、PROGMEMから文字列のバイトを一度に1バイトずつプルするヘルパー関数にコンパイルできます。RAMを途中で使用する必要はありません。
PrintクラスからLCD印刷を派生させることで、Serial以外(LCDなど)にその手法を使用するのは簡単です。
例として、私が書いたLCDライブラリの1つで、まさにそれを行いました。
class I2C_graphical_LCD_display : public Print
{
...
size_t write(uint8_t c);
};
ここでの重要なポイントは、Printから派生し、「書き込み」機能をオーバーライドすることです。これで、オーバーライドされた関数は、文字を出力するために必要なことをすべて実行します。Printから派生しているため、F()マクロを使用できます。例えば。
lcd.println (F("Hello, world"));
string_table
配列から1バイトのみを読み取ることに注意してください。その配列は20KBで、メモリに収まらない場合があります(一時的にでも)。ただし、上記の方法を使用して1つのインデックスのみをロードできます。