AVRはハーバードアーキテクチャーで,プログラム用メモリROMと内蔵SRAM(及び拡張RAM)メモリの2種類のメモリがあって,各々独立しているバスで接続されている。
WinAVR(avr-gcc)では,特に指定しないと定数はSRAM上に作成される。できるだけFlash ROM上に定数を作成して,使用する時だけプログラムメモリから読み出して利用するようにする。
プログラムメモリを使用する場合には,以下のヘッダをインクルードする。
#include <avr/pgmspace.h>
宣言の仕方
__attribute__ ((progmem))
を加えるか,<avr/pgmspace.h>内で定義されているマクロ,"PROGMEM"を使う。
const 型名 定数名 PROGMEM = 定数式;
整数や文字,文字列定数については<avr/pgmspace.h>で定義されているいくつかのデータ型が使える。
定数を実現する簡単な方法として,直接数値をコードに書いてしまう方法,それを#defineマクロで定数として扱う方法がある。
8bitや16bitの小さなサイズの定数を使用したい場合は,この方法でOK。
#define CONST_A (1234) uint16_t X; : X = X - CONST_A;
この定数は,プログラムメモリ内に置かれて命令の一部としてコードの中に置かれる。
配列・文字列・構造体についても同じようにはできるが,多少問題がある。
prog_char c_flash = 1; prog_uint16_t w_flash = 1234;
c_flashの名前で8bitの値1が,w_flashの名前で16bitの値1234が,プログラムメモリ内に作られる。変数c_flash,w_flashは,プログラムメモリ上のアドレスを保持しています。*1
char s[48]; strcpy_P(s,msg); // プログラムメモリ上文字列を,RAM上の文字列変数sにコピーする
上記例のように,WinAVRのライブラリには,文字列を扱う標準的なCライブラリ関数について,ROM上の文字列を利用できる文字列関数や入出力関数が用意されている。
これらは関数名末尾に "_P" がついている。
void * memcpy_P (void *, PGM_VOID_P, size_t) int strcasecmp_P (const char *, PGM_P) char * strcat_P (char *, PGM_P) int strcmp_P (const char *, PGM_P) char * strcpy_P (char *, PGM_P) size_t strlcat_P (char *, PGM_P, size_t) size_t strlcpy_P (char *, PGM_P, size_t) size_t strlen_P (PGM_P) int strncasecmp_P (const char *, PGM_P, size_t) char * strncat_P (char *, PGM_P, size_t) int strncmp_P (const char *, PGM_P, size_t) char * strncpy_P (char *, PGM_P, size_t) size_t strnlen_P (PGM_P, size_t)
宣言とコピーをまとめて使いたいときは,PSTR()マクロを使って以下のように書くことも出来る。
char s[48]; strcpy_P(s,PSTR("hogehoge")); LCD_print(s);
PSTRは,()内の文字列を前述の例と同様に宣言し,文字列のROM上のアドレスを返すマクロ。
ROM専用文字列関数strcpy_P()によって,ROMからRAMへのコピーが行われる。その後,sをRAM上の通常の文字列として扱う。
前記のように,ROM上の文字列をRAM上にコピーすれば,RAM文字列を扱える関数が利用できます。
しかしこれはRAMの容量を食うので,例えばLCDに文字列を表示する関数を作る場合,ROM上のアドレスを引数にとれる,LCD_print_P(pgm_s)のような関数を作ると便利。
void LCD_print_P(const prog_char *pgm_s) { char c; c=pgm_read_byte(pgm_s++); while ((c=pgm_read_byte(pgm_s++)) != 0) LCD_putc(c); //LCDに一文字を送る関数 } //main routine側 LCD_print_P(PSTR("hogehoge"));
PSTR()をつけるのも面倒なので,さらにマクロを咬ませるとわかりやすくなる。
// 関数ライブラリ側 #define lcd_printp LCD(s) _print_P(PSTR(s)) // main側 LCD_print_P(PSTR("hogehoge"));
LCD_print("The first line of my LCD display"); //LCD_print()は,RAM上文字列を表示する関数
この方法だと,文字列定数をRAM上に置く(変更できない初期値設定済み文字列変数を作る)ことになる。
構造体に対応するprog_型がないのでちょっとやっかいですが,<avr/pgmspace.h>で定義されているキーワードPROGMEMを使って自分で定義すればOK。 たとえば,
typedef struct { int16_t X,Y,R; uint8_t color; } circle; circle en PROGMEM = {-123,456,78,1};
としておいて,利用先で
X = pgm_read_word(&en.X);
のようにして各要素を読むことが出来ます。構造体全体をRAM変数にコピーするには,
circle en_sram; memcpy_P(&en_sram,&en,sizeof(circle));
単に↓でもいけるんだけど,RAM上にコピーを作ってメモリを圧迫するみたいだ。
#define en {-123,456,78,1} circle en_sram=en;
新しくコメントをつける