Arduinoで実験 (内蔵EEPROM)

2013/05/06


◆ EEPROM

Arduino UNO の心臓部である ATmega328P には本体に 1kバイトの EEPROM が搭載されている。

EEPROM は不揮発性メモリで、マイコンの電源を落としても格納されたデータが保持され、Arduino リファレンスなどでは小さなハードディスク等の表現がされている。

便利な EEPROM ではあるが注意する点として、書き込みに 3.3ms かかりタイミングに注意する必要があるのと、書込み回数に寿命があり約10万回で寿命に達する。 マイコンの速度で 10万回などあっという間に達するので、ループ内に書込み命令を入れるのには注意が必要です。

Arduino IDE には標準で、内蔵 EEPROM を使うためのライブラリがあり、1バイトの書込みと読込みがサポートされています。 1バイトの値の読み書きではそのまま命令を使用すればよいが、 int 型等、複数バイトで構成されている変数を格納/呼び出しするのには、それ用の関数を作成する必要があります。


◆ 基本的な使用方法

EEPROMに値を書き込み後、読み込んでシリアル通信で値を送信するスケッチです。

ライブラリ #include <EEPROM.h>
読込み (byte) = EEPROM.read(address)
aaddress は int型の 0以上の値
1バイトの戻り値
書込み EEPROM.write(address, value)
aaddress は int型の 0以上の値
value は 1バイトの書き込む値( 0 ~ 255 )

EEPROMに値を書き込み後、読み込んでシリアル通信で値を送信するスケッチです。

//
// 12-01 EEPROM 読み書き実験スケッチ
//

#include <EEPROM.h>      // IDE に同封されているライブラリをインクルード

void setup() {
  Serial.begin(9600);    // モニタ用にメッセージを出力
  Serial.println("EEPROM Test Start");

  byte a = 5;            // 変数を作成
  byte b = 3;
  byte c;

  Serial.print("write 0, a = ");
  EEPROM.write(0, a);    // 書込み
  Serial.println(a);
  delay(5);              // 3.3ms 以上の待機時間

  Serial.print("write 1, b = ");
  EEPROM.write(1, b);
  Serial.println(b);
  delay(5);

  Serial.print("write 2 a + b = ");
  EEPROM.write(2, a + b);
  Serial.println(a + b);
  delay(5);

  Serial.print("write 3 a - b = ");
  EEPROM.write(3, a - b);
  Serial.println(a - b);
  delay(1000);

  Serial.print("read 0 a = ");
  c = EEPROM.read(0);    // 読込み
  Serial.println(c);
  delay(5);              // 必要ないのだが万が一を考え一応待機時間を入れておく

  Serial.print("read 1 b = ");
  c = EEPROM.read(1);
  Serial.println(c);
  delay(5);

  Serial.print("read 2 a + b = ");
  c = EEPROM.read(2);
  Serial.println(c);
  delay(5);

  Serial.print("read 3 a - b = ");
  c = EEPROM.read(3);
  Serial.println(c);
  delay(5);
}

void loop() {
}

◆ 2バイトの変数を保存する方法

int 型の様な 2バイトの変数型を保存する際には、一度 1 バイトごとに分解し、読み取る際には再度構築する必要がある。 次は 2バイトの int 型の書込み/読取り関数です。

//
// 12-02 2バイトの読み書き実験スケッチ
//

#include <EEPROM.h>

void setup() {
  Serial.begin(9600);
  Serial.println("EEPROM Test Start");

  int a = 12;
  int b = 0;

  eeprom_write_int(5, a);

  b = eeprom_read_int(5);

  Serial.print("b = ");
  Serial.println(b);

}

void loop() {
}

void eeprom_write_int(int addr, int i) {
  byte tmp;
  tmp = lowByte(i);
  EEPROM.write(addr, tmp);
  delay(5);
  tmp = highByte(i);
  EEPROM.write(addr + 1, tmp);
  delay(5);
}

int eeprom_read_int(int addr) {
  int h, l, ret;
  l = EEPROM.read(addr);
  delay(5);
  h = EEPROM.read(addr + 1);
  delay(5);
  ret = (h << 8) + l;
  return(ret);
}

◆ double 型の変数を保存する方法

double 型の保存には少し面倒な手順が必要となります。 double 型の内部構造は符号、指数部、仮数部と分けられますが、指数部が 8 ビットで・・・と言い切れない部分があります。

Arduino UNO の double 型は 4バイトで一般的な double 型は 8 バイトです。 当然内部の構造も違ってきます。

また、整数型の変数と違い double 型にはビット操作という考え方が無いので右シフト等の操作ができません。

以下は共用体と構造体を利用した内部のデータを分解するサンプルです。

//
// 12-03 共用体と構造体を用いた分解、復元実験スケッチ
//

union mydata {                     // 共用体を定義/宣言
  struct { int i; } i;             // 構造体を定義(int)
  struct { byte b1, b2; } b;       // 構造体を定義(byte)
} test;

void setup() {
  Serial.begin(9600);
  Serial.println("EEPROM Test Start");

  test.i.i = 0x1234;               // 共用体へ値を格納
  Serial.println(test.i.i, HEX);   // int で読む 
  Serial.println(test.b.b1, HEX);  // byte 1 で読む
  Serial.println(test.b.b2, HEX);  // byte 2 で読む

  test.b.b1 = 0x21;                // byte 1 を書き換え
  test.b.b2 = 0x43;                // byte 2 を書き換え
  Serial.println(test.i.i, HEX);   // int で読む
}

void loop() {
}

上記では 2 バイト型を例にしましたが、 double 型の様な 4 バイト変数も同様に処理できます。 初めに共用体を定義することで煩わしいポインタから解放されるので、メモリ操作を意識せずにすみます。(というか、どうもポインタの概念がいまだにしっくりこない。)