ROM上の定数・変数 anchor.png

AVRはハーバードアーキテクチャーで,プログラム用メモリROMと内蔵SRAM(及び拡張RAM)メモリの2種類のメモリがあって,各々独立しているバスで接続されている。

  • プログラムメモリ:まあまあサイズがある(512~256kB)
  • レジスタおよび内蔵SRAMメモリ:サイズが小さい(32~数kB)

WinAVR(avr-gcc)では,特に指定しないと定数はSRAM上に作成される。できるだけFlash ROM上に定数を作成して,使用する時だけプログラムメモリから読み出して利用するようにする。

Page Top

ヘッダーファイル anchor.png

プログラムメモリを使用する場合には,以下のヘッダをインクルードする。

#include <avr/pgmspace.h> 

宣言の仕方

__attribute__ ((progmem))

を加えるか,<avr/pgmspace.h>内で定義されているマクロ,"PROGMEM"を使う。

const 型名  定数名  PROGMEM = 定数式;

整数や文字,文字列定数については<avr/pgmspace.h>で定義されているいくつかのデータ型が使える。

Page Top

小さな整数定数は#defineマクロまたは直接記述で anchor.png

定数を実現する簡単な方法として,直接数値をコードに書いてしまう方法,それを#defineマクロで定数として扱う方法がある。
8bitや16bitの小さなサイズの定数を使用したい場合は,この方法でOK。

#define CONST_A  (1234)
uint16_t X;
 :  
X = X - CONST_A;

この定数は,プログラムメモリ内に置かれて命令の一部としてコードの中に置かれる。

配列・文字列・構造体についても同じようにはできるが,多少問題がある。

Page Top

単一の値(配列でない定数)をROM上に置く宣言: anchor.png

  • 8 bitデータ型: prog_uint8 , prog_int8 , prog_char
  • 16bitデータ型: prog_uint16 , prog_int16
  • 32bitデータ型: prog_uint32 , prog_int32
  • 64bitデータ型: prog_uint64 , prog_int64
    などなど,すべての整数型の頭に"prog_"がついた型が用意されている。
Page Top

ROMに各種サイズの定数を作る anchor.png

prog_char     c_flash = 1;
prog_uint16_t w_flash = 1234;

c_flashの名前で8bitの値1が,w_flashの名前で16bitの値1234が,プログラムメモリ内に作られる。変数c_flash,w_flashは,プログラムメモリ上のアドレスを保持しています。*1

Page Top

定数の読み出し anchor.png

ROMから読ませるために,関数を介して参照する。

char     res1 = pgm_read_byte(&c_flash);
uint16_t res2 = pgm_read_word(&w_flash);

c_flashのアドレス値(&c_flash)を使って,値をROMから読み出し,RAM上の変数res1,res2に保存する。データサイズに応じて以下のような関数が用意されている。

pgm_read_byte(address)
pgm_read_word(address)
pgm_read_dword(address)
Page Top

配列 anchor.png

Page Top

プログラムメモリの中に配列を作る anchor.png

const prog_char *TEN  = {0,1,2,3,4,5,6,7,8,9};
Page Top

配列の要素を読む anchor.png

char res = pgm_read_byte(&TEN[5]);    // TEN[X]の読み出し
char res = pgm_read_byte(TEN+X);      // TENからXバイト目の読み出し
Page Top

文字列の配列 anchor.png

次のようにする。

#include <avr/pgmspace.h>
// 文字列をROMに置く
const char foo[] PROGMEM = "Foo";
const char bar[] PROGMEM = "Bar";

// ROM上のアドレスポインタを要素とする配列を作る
PGM_P array[2] PROGMEM = {
    foo,
    bar
};
Page Top

文字列定数 anchor.png

Page Top

文字列定数宣言 anchor.png

const prog_char msg[] = "The first line of my LCD display1";
Page Top

1文字だけを取り出す anchor.png

char res = pgm_read_byte(&msg[position]);
char res = pgm_read_byte(msg+position);
Page Top

strcpy_Pで全体をRAM上文字列変数にコピーして使用 anchor.png

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上の通常の文字列として扱う。

Page Top

ROM上の文字列を関数に引き渡す anchor.png

前記のように,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"));
Page Top

RAM上の文字列定数 anchor.png

LCD_print("The first line of my LCD display"); 
//LCD_print()は,RAM上文字列を表示する関数

この方法だと,文字列定数をRAM上に置く(変更できない初期値設定済み文字列変数を作る)ことになる。

  1. RAM上に,文字列定数を確保する(あらかじめ、プログラムに出てくるすべての文字列定数をプログラムメモリからRAM上にコピー)
  2. LCD_printなどの関数は,RAM上の文字列を参照する
    この領域は開放することが出来ないので,文字列定数を使った分だけ使用できるRAMが少なくなってしまう。文字列がそれほど大きくなく,RAMに充分余裕があるならこれでいいのですが,使用する文字列サイズが大きく,RAMに余裕がないなら,使わない。
Page Top

構造体定数とその利用 anchor.png

構造体に対応する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;
Page Top

PROGMEM anchor.png

基本的には,

const 型名  定数名  PROGMEM = 定数式;

で,ROM上に定数を置いてくれるようです。


*1 普通の変数で,変数が,RAM上のアドレスを保持しているのと同様。ただ場所がRAMではなくROMであるということ。

新しくコメントをつける

題名
ゲスト名
投稿本文
より詳細なコメント入力フォームへ

トップ   凍結 差分 バックアップ 複製 名前変更 リロード   ページ新規作成 全ページ一覧 単語検索 最新ページの一覧   ヘルプ   最新ページのRSS 1.0 最新ページのRSS 2.0 最新ページのRSS Atom
Counter: 1610, today: 2, yesterday: 0
最終更新: 2020-12-26 (土) 16:07:42 (JST) (1210d) by yuji