はじめに
Arduino Unoのメモリについての説明です。
メモリの種類
Arduino Unoには以下の種類のメモリが搭載されています。Arduino Unoが採用しているATmega328Pは、ハーバードアーキテクチャを採用しており、プログラムとデータが物理的に分離された領域に配置されます。
項番 | 種類 | 用途 | 容量 | 揮発性 | その他 |
---|---|---|---|---|---|
1 | フラッシュメモリ | プログラム、ブートローダ、読み取り専用ユーザデータ | 32キロバイト(内ブートローダが0.5キロバイト | 不揮発性 | スケッチのアップロード後はユーザデータの変更不可。 |
2 | SRAM | ユーザデータ | 2キロバイト(ユーザデータ) | 揮発性 | |
3 | EEPROM | ユーザデータ | 1キロバイト | 不揮発性 |
プログラム内で利用する変数は、通常、SRAM上に配置されます。読み取り専用のデータ(つまり、変数定義時に初期化できる変数)は、フラッシュメモリにおいてSRAMを節約することができます。また、EEPROMにデータを格納することもできます。フラッシュメモリとEEPROMに配置したデータは、スケッチの中では「直接」利用することはできず(変数によるアクセスができません)、API(関数)を利用して一旦SRAM上の変数に値をコピーする必要があります。
フラッシュメモリのデータはスケッチ内で初期化できるだけで、スケッチの実行中の書き換えはできません。EEPROMに初期値を書き込むには、avr-gccの機能を利用する必要があります。
フラッシュメモリ
フラッシュメモリには、スケッチとブートローダが配置されます。プログラム内で利用する変数も格納することもできます。
フラッシュメモリにデータを格納には、スケッチ上でフラッシュメモリに格納する旨を宣言する必要があります。フラッシュメモリに配置したデータは、API(関数)を利用してSRAM上の変数に読み込む必要があります。このため、基本的には大量のデータ(配列など)をフラッシュメモリに配置しておき、必要な部分をSRAMに読み込むということになります。同時に使う必要のあるデータをフラッシュメモリに配置しても効果は限られます。
利用方法
フラッシュメモリにデータを配置するには、変数を定義するときに、PROGMEMというキーワードをつけて宣言します。PROGMEMは、avr/pgmspace.hに定義されているため、このヘッダファイルをインクルードする必要があります。リファレンスはこちら。PROGMEMをつけるとその変数は読み出し専用となり、プログラム内での修正はできないので、変数を定義する際に必ず初期化する必要があります。constを付け定義する必要があります。また、グローバル変数もしくはstatic変数として宣言する必要があります。関数内のauto変数として宣言した場合は、コンパイルは正常終了しましたが、定義した値とは異なる値が読みだされました。
|
|
フラッシュメモリからデータを読み出すには、以下の関数を利用します。関数の引数は、フラッシュメモリ上の「アドレス」です。
- pgm_read_byte()
- pgm_read_word()
- pgm_read_dword()
- pgm_read_float()
上記を利用した例を以下に示します。
|
|
上記のプログラムをコンパイルした後、各変数がどのように割付けられているかを、avr-nmコマンドを利用して確認してみます。
C:\> nm -n -C progmem_test.cpp.elf
~省略~
00000068 t f_flash
00000074 t l_flash
00000080 t i_flash
00000088 t s_flash
~省略~
00800100 D __data_start
00800100 d s_data
~省略~
0080014c B __bss_start
0080014c D __data_end
0080014c D _edata
0080014c b s_bss
~省略~
00800207 B __bss_end
00800207 N _end
00810000 N __eeprom_end
t_flashなどのアドレスは、00000068番地以降にあり、テキストセクションであることがわかります。一方、s_dataは、00800100番地にあり、(初期化された)データであることがわかります。また、s_bssは0080014c番地にあり、(初期化されない)データであることがわかります。nmコマンドでは、RAM上のアドレスは、00800000のオフセット付きで表示されるようです。
次に、最もよく使われるパターンと思われる、文字列の配列をフラッシュメモリに配置して利用する例を以下に示します。
- 文字列の変数を定義する。
- 文字列の変数の配列を定義する。
という2段階でプログラミングします。文字列の配列を直接定義することはできないようです。
|
|
strcpy_P()は、フラッシュメモリ上の文字列をRAM上の変数にコピーする関数です。pgmspace.hには、他にも以下の関数が定義されていました。
|
|
また、F()というマクロを利用する方法もあります。Serial.print()などで利用できます。
|
|
この場合、Printクラスで、フラッシュメモリから1バイトずつ読み取って表示するので、2048バイト以上の文字列も表示することができます(もちろん、フラッシュメモリに入りきる範囲でですが)。
SRAM
SRAMは、ユーザデータが配置される2キロバイトの領域です。通常、プログラムで定義した変数はSRAMに配置されます。
ユーザデータが配置される領域は、以下の領域に分かれています。
領域 | 意味 | プログラムとの関係 | |
---|---|---|---|
1 | data | 初期化済みデータ | 静的記憶域期間を持つ変数で、初期値を与えたもtの。初期化したグローバル変数やstatic変数。 |
2 | bss | 非初期化済みデータ | 静的記憶域期間を持つ変数で、初期値を与えていないもの。初期化していないグローバル変数やstatic変数。 |
3 | ヒープ | プログラム実行時に動的に確保するメモリ領域。 | malloc()などで確保する領域。 |
4 | スタック | 実行中の関数に関するデータを格納する領域。 | 自動記憶域期間を持つ変数。関数内で定義したauto変数、関数の引数、関数終了後に戻るアドレスなど。 |
プログラムによりデータ構造が変わるため、各領域がどのアドレスから始まるかはプログラムにより異なります。以下は、SRAMの使い方を模式的に示した図です。ヒープはアドレスの大きい方向に伸びていき、スタックはアドレスの小さい方向に伸びていきます。両者がぶつかった時点でプログラムの動作は不定となります。
なお、avr-libcのmalloc()は、割り当て可能なメモリがなくなった時点で、エラーとなります。
初期化済みデータや非初期化データが上記に配置される様子は、上述のフラッシュメモリのセクションの結果を参照してください。
EEPROM
Arduino Unoは、1キロバイトのEEPROMを持っています。EEPROMライブラリ を利用することで、データの読み書きを行うことができます。アドレスを直接指定して、1バイトのデータ(0から255の値)を読み書きすることができます。
リファレンス
バージョン
Arduino 1.8.0
最終更新日
January 23, 2021