トップ 新規 編集 差分 一覧 ソース 検索 ヘルプ PDF RSS ログイン

Getting Started Notes - EEPROM

最終更新時間:2011年06月18日 21時37分07秒

EEPROM上の変数

 概要

EEPROMとは、(Electrical Erasable Programmable Read Only Memory)で、「書き換えることもできるROM」という感じのものです。プログラムメモリ(FlashROM)もそうですが、EEPROMはプログラムメモリより書き換え可能回数が多いので、データの保存の用途にも使えます。電源を切ったりリセットしたりしても書き込まれたデータが失われないので、電源切断時各種設定を保存して次回起動時に備えるような使い方に最適です。

過去には一度だけ書き込めるROM(PROM,ワンタイムROM)や、チップ上に石英窓があり紫外線(直射日光)を当てて消去するPROM(UV-EPROM)がありました。これを電気的に消去できるようにしたのがEEPROMです。それまでのマイコン(Z80など)はUV-EPROMにプログラムを焼き込んで開発し、電源切断時も保持したいデータは、変数の保存しているSRAMをリチウム電池やキャパシタ(コンデンサ)でバックアップしていました。ちなみに、元々EEPROMの技術を得意としていたMicrochip社やAtmel社がマイコンをリリースしたのが、PicMicroシリーズとAVRシリーズです。~

EEPROMもプログラムメモリ(FlashROM)も基本的には同じ原理でデータを保持しますが、FlashROMが複数バイトのページ単位でイレース、ライトするのに対して、バイト単位でリード・ライトできるものを「EEPROM」と呼ぶようです。AVRではFlashROMは大容量で1万回の書き換え回数まで耐えられ、プログラムと定数の格納(PROGMEMキーワード必要)に使われます。一方EEPROMは小容量ですが10万回まで耐えられ、電源切断やリセットしても保持したい設定データ等を退避する目的で使います。

 宣言

SRAM上変数の単純な宣言と異なり、I/Oレジスタを介して接続されているEEPROMは、特別な関数を介して使用します。これらの関数はインクルードファイル<avr/eeprom.h>内で見ることが出来ます。

EEPROMを扱うときは<avr/eeprom.h>をインクルードしてください。

#include <avr/eeprom.h>
※※※ ATmega48/88/168でのeepromの利用 ※※※

avr-libc1.2.3(WinAVR 20050214)のeepromルーチンはATmega48/88/168では動作しません。[サンプルプログラム]に対応版がアップされています。

avr-libc 1.2.5以降/WinAVR 20060125以降では改善されたようです

 関数一覧

eeprom_write_byte (unsigned int addr, unsigned char val);

指定されたアドレス'addr'に値'val'を書き込みます。

unsigned char eeprom_read_byte (unsigned int addr);

指定アドレスにあるEEPROM上の値(1byte)を読み取ります。

value = eeprom_read_byte (addr);
unsigned int eeprom_read_word (unsigned int addr);

指定アドレスにあるEEPROM上の値(2bytes)をword値で読み取ります。

value = eeprom_read_word (addr);
eeprom_read_block (void *buf, unsigned int addr, size_t n);

指定アドレスにあるEEPROM上の値 ( n bytes) を、bufの指し示すSRAM上に転送します。

void eeprom_write_block (const void *buf, void *addr, size_t n);

bufの指し示すSRAM上の値 ( n bytes) を、EEPROMS上の指定アドレスに転送します。

int eeprom_is_ready (void);
この関数はeepromがアクセス可能なら1を、busyなら0を返します。

eeprom_busy_wait();

EEPROMがbusyでなくなるまでloopして待ちます。

 関数の使用方法

注意事項

  • eepromはその性質上、書き込みを行うとその後数msecの間(マイコンにとってはとても長い時間)読み書きとも不可能になります。eeprom_busy_wait();で読み書き不能時間が終わるまで待つ処理ができます。
  • 書き込み可能になるまでただ待ってもいいのですが、それでは時間がもったいない場合はeeprom_is_ready()関数をうまく活用して、待っている間他の処理を行うようにしましょう。割り込みをうまく使う手もあるでしょう。
  • (デバイスによっては?)eeprom書き込み中に電源が不安定になるととんでもないアドレスに書き込んだり、読み込みなのに書き込みを行ったりする問題があるもようです。これに対し以下のような対策が可能です。
    • EEPROMを使わない(^^;)
    • バックアップ電池を使用。外部電源が切れたらEEPROMへのアクセスを止め、サスペンドスリープモードに入る
    • リセットICをつける。
    • BODリセット機構があるAVRではそれを有効にする。

アドレス指定による読み書き

値を直接eepromに記憶させることが可能です。ただアドレスと値を指定するだけです。

例題1:

書き込みの後はEEPROMの読み書きができる状態になるまで待ってからEEPROMアクセス関数を使うこと(eeprom_busy_wait() )

#include <avr/io.h>
#include <avr/eeprom.h>
int main(void)
{
    uint8_t result;
    eeprom_busy_wait();                /* 読み書き可能になるまで待つ */
    eeprom_write_byte(0x00, 0xAA);  /* 値0xAAをEEPROMの0番地に書き込む */
    eeprom_busy_wait();                /* 読み書き可能になるまで待つ */
    result = eeprom_read_byte(0x00); /* EEPROMの0番地の値を読み出し変数val2に納める */
    DDRB = 0xff;
    PORTB = result;                 /* PORTBに出力、LEDなどで表示 */
    for (;;) {}
}

変数val1の値はeepromの0番地に記憶されます。その後、0番地にあるその値は読み出され、変数2に保存されます。この値は(PORTBにつないだ)LEDで確認できます。

EEPROM書き込み後の次のEEPROMアクセス(読み書きとも)の前には、必ずeeprom_busy_wait();をおく必要があります。面倒なら全てのEEPROMアクセスの前に置いても良いと思います。

eeprom上に変数宣言

 上記のやり方では、どの変数がどのアドレスを使うのかよく掌握しなくてはなりません。納めるデータ数が変動するとアドレスの再設定が面倒です。これって結構面倒です。SRAM上の変数のように宣言するだけで使えればありがたい・・・・・・。

 というわけで、EEPROM用のオプションをつけるだけで、EEPROM上の変数が宣言できます。下記のフォーマットでEEPROMに対しても変数宣言によるメモリ確保ができます。

'''static 型名 変数名 __attribute__((section(".eeprom")));'''

 この変数をそのまま参照するとEEPROMではなく同じ番地のSRAMに読み書きにいってしまいますので、EEPROMへの読み書きを行わせるために先のEEPROMアクセス関数を用います。残念ながら変数サイズまで判断してくれないようなので、変数のサイズに応じて_byteタイプと_wordタイプを使い分けてください。

result = eeprom_read_byte( &変数名);
eeprom_write_byte(&変数名 , 書き込みデータ);

この辺はプログラムメモリに対するアクセス方法と全く同じです。

例題2: eepromセクション上宣言による方法

番地を指定する必要がないこと、2つめの変数のアドレスも自動で決まることに注意

#include <avr/io.h>
#include <avr/eeprom.h>

/* EEPROM上に16bit整数を確保する */
static uint8_t e1 __attribute__((section(".eeprom")));
static uint8_t e2 __attribute__((section(".eeprom")));

int main(void)
{
    uint8_t r1,r2;
    
    eeprom_busy_wait();
    eeprom_write_byte (&e1, 0x54);  /* e1書き込み。アドレス値を引数とすることに注意 */
    eeprom_busy_wait();
    r1 = eeprom_read_byte(&e1);     /* e1読み出し。同上 */
    
    eeprom_write_byte (&e2, 0x56);  /* e2書き込み。アドレス値を引数とすることに注意 */
    eeprom_busy_wait();
    r2 = eeprom_read_byte(&e2);     /* e1読み出し。同上 */
    
    DDRB = 0xFF;
    PORTB = r1+r2;
    for (;;);
}

見づらいのでprogram memoryのprog_charのような専用の型があれば便利なのですが。

例題3: 32bit(4bytes)変数
uint32_tなど2バイトを越える変数の場合はeeprom_read_block()/eeprom_write_block()関数で同じサイズのSRAM上変数にコピーしてから使うことになります。

#include <avr/io.h>
#include <avr/eeprom.h>

/* EEPROM上に32bit整数を確保する */
static uint32_t e4 __attribute__((section(".eeprom")));

int main(void)
{
    uint32_t data1,data2;
    uint8_t result;
    data1 = 12345678;
    eeprom_busy_wait();
    eeprom_write_block (&data1, &e4); /* data書き込み。アドレス値を引数とすることに注意 */
    eeprom_busy_wait();
    data2 = eeprom_read_block(&e4);   /* data読み出し。同上 */
    DDRB = 0xFF;
    PORTB = (char)(data2 & 255);   /* 下位8bitのみ出力してみます。 */
    for (;;);
}

配列をeeprom上に宣言する

static 型名 変数名[要素数] __attribute__((section(".eeprom"))); 

宣言の方法は特にかわりないです。

例題4:eeprom上配列のアクセス

プログラムメモリと同様です。

#include <avr/io.h>
#include <avr/eeprom.h>
int main(void)
{
    /* 配列の eepromセクション内宣言 */
    static uint8_t val[5] __attribute__((section(".eeprom")));
    uint8_t val2;
    DDRB=0xFF;
    eeprom_busy_wait();
    eeprom_write_byte (&val[0], 0xAA); /* writing the val[0] */
    eeprom_busy_wait();
    eeprom_write_byte (&val[1], 0xBB); /* writing the val[1] */
    eeprom_busy_wait();
    eeprom_write_byte (&val[2], 0xCC); /* writing the val[2] */
    eeprom_busy_wait();
    eeprom_write_byte (&val[3], 0xDD); /* writing the val[3] */
    eeprom_busy_wait();
    eeprom_write_byte (&val[4], 0x00); /* writing the val[4] */
    eeprom_busy_wait();
    val2 = eeprom_read_byte(&val[4]); /* reading of val[4] */
    PORTB=val2;
    for (;;);
}
  • この例題では、5要素の配列フィールドが定義されます。

初期値を持つ定数をeeprom上に宣言する

uint8_t __attribute__((section(".eeprom"))) val[] =
        { 1,2,3,4,5,6,7,8,9,0 };

eeprom独特の修飾子がつくことを除けば、普通の配列や構造体の宣言と変わりありません。このような定数宣言を持つプログラムをコンパイルすると、プログラムに対するhexファイルとは別に、eepromデータに対応する.eepファイル(intelhex形式)が生成されます。ライターで両方を書き込んで下さい。ライターによっては、どちらか一方を書き込む時、書き込みに先立ってchip-eraseをかけるものがありますので、片方だけ更新する場合は注意して下さい。

構造体をEEPROMに置く

1バイト、2バイトデータの場合はeeprom_read/write_byte/word()が使えますが、それ以外の大きさのデータの場合は、eeprom_read_block()/eeprom_write_block()が利用できます。これにより同型のSRAM上変数にコピーして使います。

#include <avr/io.h>
#include <avr/eeprom.h>

typedef struct {
    uint8_t x,y;
    uint16_t z;
} hoge_t;
hoge_t hoge; 

int main(void)
{
   static hoge_t hoge_EEPROM[] __attribute__((section(".eeprom"))) =
       { {1,2,3456} , {7,8,9012} , {3,4,5678} , {9,0,1234} };
   hoge_t hoge_SRAM;
   
   eeprom_busy_wait();
   eeprom_read_block(&hoge_SRAM,&hoge_EEPROM[2],sizeof(hoge_t));
   hoge_SRAM.z++;
   eeprom_write_block(&hoge_SRAM,&hoge_EEPROM[2],sizeof(hoge_t));
   for (;;);
}

構造体内の1要素だけを読み出す方法もあります。これなら、要素の大きさによってはeeprom_read/writeが使えます。

#include <avr/io.h>
#include <avr/eeprom.h>

typedef struct {
    uint8_t x,y;
    uint16_t z;
} hoge_t;
hoge_t hoge; 

int main(void)
{
   static hoge_t hoge_EEPROM[] __attribute__((section(".eeprom"))) =
       { {1,2,3456} , {7,8,9012} , {3,4,5678} , {9,0,1234} };
   uint16_t SRAM_Z;
   
   eeprom_busy_wait();
   SRAM_Z = eeprom_read_word(&hoge_EEPROM[2].z);
   SRAM_Z++;
   eeprom_write_word(SRAM_Z,&hoge_EEPROM[2].z);
   for (;;);
}