|
現: 2020-12-26 (土) 15:07:42 yuji |
| + | #contents |
| | | |
| + | *ROM上の定数・変数 [#o73b8eab] |
| + | AVRはハーバードアーキテクチャーで,プログラム用メモリROMと内蔵SRAM(及び拡張RAM)メモリの2種類のメモリがあって,各々独立しているバスで接続されている。 |
| + | |
| + | -プログラムメモリ:まあまあサイズがある(512~256kB) |
| + | -レジスタおよび内蔵SRAMメモリ:サイズが小さい(32~数kB) |
| + | |
| + | WinAVR(avr-gcc)では,特に指定しないと定数はSRAM上に作成される。できるだけFlash ROM上に定数を作成して,使用する時だけプログラムメモリから読み出して利用するようにする。 |
| + | |
| + | **ヘッダーファイル [#h12a6057] |
| + | プログラムメモリを使用する場合には,以下のヘッダをインクルードする。 |
| + | #include <avr/pgmspace.h> |
| + | |
| + | 宣言の仕方~ |
| + | __attribute__ ((progmem)) |
| + | を加えるか,<avr/pgmspace.h>内で定義されているマクロ,"PROGMEM"を使う。 |
| + | const 型名 定数名 PROGMEM = 定数式; |
| + | 整数や文字,文字列定数については<avr/pgmspace.h>で定義されているいくつかのデータ型が使える。~ |
| + | |
| + | **小さな整数定数は#defineマクロまたは直接記述で [#o469c31d] |
| + | 定数を実現する簡単な方法として,直接数値をコードに書いてしまう方法,それを#defineマクロで定数として扱う方法がある。~ |
| + | 8bitや16bitの小さなサイズの定数を使用したい場合は,この方法でOK。 |
| + | #define CONST_A (1234) |
| + | uint16_t X; |
| + | : |
| + | X = X - CONST_A; |
| + | この定数は,プログラムメモリ内に置かれて命令の一部としてコードの中に置かれる。 |
| + | |
| + | 配列・文字列・構造体についても同じようにはできるが,多少問題がある。 |
| + | |
| + | ***単一の値(配列でない定数)をROM上に置く宣言: [#r9f18391] |
| + | -8 bitデータ型: prog_uint8 , prog_int8 , prog_char~ |
| + | -16bitデータ型: prog_uint16 , prog_int16~ |
| + | -32bitデータ型: prog_uint32 , prog_int32~ |
| + | -64bitデータ型: prog_uint64 , prog_int64~ |
| + | などなど,すべての整数型の頭に"prog_"がついた型が用意されている。 |
| + | |
| + | ***ROMに各種サイズの定数を作る [#c576fa09] |
| + | prog_char c_flash = 1; |
| + | prog_uint16_t w_flash = 1234; |
| + | c_flashの名前で8bitの値1が,w_flashの名前で16bitの値1234が,プログラムメモリ内に作られる。変数c_flash,w_flashは,プログラムメモリ上のアドレスを保持しています。((普通の変数で,変数が,RAM上のアドレスを保持しているのと同様。ただ場所がRAMではなくROMであるということ。)) |
| + | |
| + | ***定数の読み出し [#p4d6ab5c] |
| + | 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) |
| + | |
| + | **配列 [#cb97af4f] |
| + | ***プログラムメモリの中に配列を作る [#fe0abd07] |
| + | const prog_char *TEN = {0,1,2,3,4,5,6,7,8,9}; |
| + | ***配列の要素を読む [#t8201cc9] |
| + | char res = pgm_read_byte(&TEN[5]); // TEN[X]の読み出し |
| + | char res = pgm_read_byte(TEN+X); // TENからXバイト目の読み出し |
| + | ***文字列の配列 [#b346dea9] |
| + | 次のようにする。 |
| + | #include <avr/pgmspace.h> |
| + | // 文字列をROMに置く |
| + | const char foo[] PROGMEM = "Foo"; |
| + | const char bar[] PROGMEM = "Bar"; |
| + | |
| + | // ROM上のアドレスポインタを要素とする配列を作る |
| + | PGM_P array[2] PROGMEM = { |
| + | foo, |
| + | bar |
| + | }; |
| + | |
| + | **文字列定数 [#h696863c] |
| + | ***文字列定数宣言 [#i994a871] |
| + | const prog_char msg[] = "The first line of my LCD display1"; |
| + | ***1文字だけを取り出す [#ie63c19e] |
| + | char res = pgm_read_byte(&msg[position]); |
| + | char res = pgm_read_byte(msg+position); |
| + | ***strcpy_Pで全体をRAM上文字列変数にコピーして使用 [#bcb428fd] |
| + | 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上の文字列を関数に引き渡す [#oaab93ae] |
| + | 前記のように,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")); |
| + | |
| + | **RAM上の文字列定数 [#rd9cf06f] |
| + | LCD_print("The first line of my LCD display"); |
| + | //LCD_print()は,RAM上文字列を表示する関数 |
| + | この方法だと,文字列定数をRAM上に置く(変更できない初期値設定済み文字列変数を作る)ことになる。~ |
| + | +RAM上に,文字列定数を確保する(あらかじめ、プログラムに出てくるすべての文字列定数をプログラムメモリからRAM上にコピー)~ |
| + | +LCD_printなどの関数は,RAM上の文字列を参照する ~ |
| + | この領域は開放することが出来ないので,文字列定数を使った分だけ使用できるRAMが少なくなってしまう。文字列がそれほど大きくなく,RAMに充分余裕があるならこれでいいのですが,使用する文字列サイズが大きく,RAMに余裕がないなら,使わない。 |
| + | |
| + | **構造体定数とその利用 [#ce4d67d2] |
| + | 構造体に対応する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; |
| + | **PROGMEM [#qb9041f7] |
| + | 基本的には, |
| + | const 型名 定数名 PROGMEM = 定数式; |
| + | で,ROM上に定数を置いてくれるようです。 |