●ArduinoのEEPROMを使う

プログラム中の値を電源断後も保持しておくためには何らかの記録装置が必要です。
そのために使用するのがEEPROMであり、不揮発性のメモリーです。
構造として電解効果トランジスタの電圧を制御するゲートが切断されて絶縁されているのと同じ構造で(フローティングゲート)
絶縁されて宙に浮いたゲートに対して外部から高い電圧で無理やり電荷を注入してトランジスタのON・OFFが操作ができますし、
閉じ込められた電荷は電源に関係なく維持されるため、それを利用して不揮発性のメモリーを作り出しています。
しかし、無理やり電荷を注入するときにチョットづつ絶縁が劣化していくので書き込み回数に制限があり、最終的には絶縁が破壊されて寿命となります。

Arduino UNOなどに使われているコントローラの ATmega328 では 1k (1024) バイトもの広大なEEPROMが搭載されており、8ビットのデータが1024個も保存できるそうです。
ATmega328では書き込み可能回数が10万回以上保証されていますが、書き込み回数はなるべく抑えた方がいいと思います。


▼EEPROMを使うための準備

プログラムの先頭で、 #include <EEPROM.h> と記述してライブラリをインクルードします。
EEPROM.hは標準でサポートされており、たったこれだけです。
ハードウエアが見えなくなるライブラリを標準で用意してくれている所はホント凄いです。
しかもアドレスは0から順番に始まるため、ハードウエアを全く気にせずに使えます。


▼EEPROMの容量を取得

EEPROM.length()
にて容量を取得できます。
#include <EEPROM.h>

void setup() {
  Serial.begin(9600);
  Serial.println(EEPROM.length());
}

void loop() {
}

このようにすれば容量、ATmega328だと1024を取得できます。


▼バイト単位での読みだし

EEPROM.read( アドレス )
にてEEPROMから値を読み出す事が出来ます。
#include <EEPROM.h>

void setup() {
  Serial.begin(9600);
  int i = EEPROM.read(0x00);
  Serial.println(i);
}

void loop() {
}

アドレス0(0x00)の値が読み出されます。
EEPROMが操作されていないArduinoでは最初は255(0xFF)が格納されているみたいです。


▼バイト単位での書き込み

EEPROM.write( アドレス , 値 )
にてバイト単位でEEPROMに書き込む事が出来ます。
#include <EEPROM.h>

void setup() {
  Serial.begin(9600);
  EEPROM.write(0x00,100);
  int i = EEPROM.read(0x00);
  Serial.println(i);
}

void loop() {
}

アドレス0(0x00)に対して100という値を書き込んでいます。
その後、読み出してシリアルモニタに出力しています。

値が更新されている場合には書き込み、更新されていないと何もしないという操作をしたい場合には、
EEPROM.write()をEEPROM.update()に入れ替えます。


▼大きいサイズのデータ型の読み書き

byte型だと1バイトの領域を読み書きしていれば事が足りますが、
他の型だと1バイト以上のサイズがあり、バイト単位で読み書きすると少々面倒なことになります。
そこで、EEPROM.get( アドレス , 変数 )EEPROM.put( アドレス , 変数 ) を使うとデータ型サイズに合わせて自動的に読み書きしてくれます。
ただし、複数の値を読み書きするには後述のデータ型のサイズをもとめてアドレスの計算が必要です。
#include <EEPROM.h>

void setup() {
  Serial.begin(9600);
  unsigned long i = 123456,j;
  EEPROM.put(0x00,i);//EEPROMに書き込み
  EEPROM.get(0x00,j);//EEPROMから読み出し
  Serial.println(j);
}

void loop() {
}

このプログラムでは、EEPROMに123456という値を書き込み、その後、読み出して表示しています。


▽文字列の読み書き

#include <EEPROM.h>

void setup() {
  Serial.begin(9600);
  unsigned char ar[5];
  EEPROM.put(0x00,"abcd");
  EEPROM.get(0x00,ar);
  Serial.println((char*)ar);
}

void loop() {
}

このプログラムでは文字列abcdがEEPROMに書き込まれて、その後、読み出して表示しています。
出力結果は abcd です。


▽配列の読み書き

#include <EEPROM.h>

void setup() {
  Serial.begin(9600);
  unsigned long ar[2]={12345,67890};
  EEPROM.put(0x00,ar);
  unsigned long pos[2];
  EEPROM.get(0x00,pos);
  Serial.println(pos[0]);
  Serial.println(pos[1]);
}

void loop() {
}

このプログラムでは配列がEEPROMに書き込まれて、その後、読み出して表示しています。
出力結果は、
12345
67890

です。


▽構造体の読み書き

#include <EEPROM.h>

void setup() {
  Serial.begin(9600);
  
  typedef struct {
    int a;
    int b;
  } st;
  st _st;
  _st.a = 100;
  _st.b = 200;
  
  EEPROM.put(0x00,_st);

  st pos;
  EEPROM.get(0x00,pos);
  Serial.println(pos.a);
  Serial.println(pos.b);
}

void loop() {
}

このプログラムでは構造体がEEPROMに書き込まれて、その後、読み出して表示しています。


▼データ型のサイズをもとめる

これは、EEPROMライブラリとは直接関係ありませんが、複数の値を保存するには前の値の後ろに次の値を保存する必要があります。
そうしないと、後ろの値で前の値を破壊してしまいます。
そのためには、アドレスの計算が必要でありデータ型のサイズをもとめる必要があります。
void setup() {
  Serial.begin(9600);
  unsigned long i;
  Serial.println(sizeof(i));
}

void loop() {
}

sizeof演算子によりデータのサイズが求められて、unsigned long型は4バイトと表示されます。
また、byte変数の場合には1と表示されます。
もちろん他のデータ型、構造体や配列でもサイズをもとめる事ができます。
ただ、ソースを分けたりすると配列はポインタ型と混同されるため注意が必要(あくまでもsizeofはマクロ演算子ですのでファイル毎のコンパイル時点で値が決まってしまいますので)


▼アドレスの計算方法

一つの値や一つの構造体を保存するだけならば毎回先頭から書き込み、読み出しすれば良いのですが、 複数の値を保存する場合には、保存した値のサイズをもとめてアドレスを進める必要があります。
もしアドレスを進めないと最初に保存した値を破壊してしまいます。
EEPROMに限らず基本的にどのようなメモリーでも、アドレスを+1すると1byte進むように作られています。
前回保存したデータが1byteならば、サイズが1ですので後ろに次のデータを保存するのにはアドレスを+1する必要があります。
複数の値を保存するには、データを保存する毎に型のサイズをsizeof()でもとめてアドレスを進めていきます。

次のプログラムは複数の値 long変数→配列→構造体→byte変数 の順にEEPROMに保存していきます。
一つ保存するたびに、sizeofでサイズをもとめてアドレスを進めています。

#include <EEPROM.h>

typedef struct {
  int a;
  int b;
} st;

void setup() {
  unsigned int address = 0x00;
  //long変数の書き込み
  unsigned long i = 123456;
  EEPROM.put(address, i);
  address += sizeof(i);
  //配列の書き込み
  unsigned long ar[2] = {12345, 67890};
  EEPROM.put(address, ar);
  address += sizeof(ar);
  //構造体の書き込み
  st _st = {100, 200};
  EEPROM.put(address, _st);
  address += sizeof(_st);
  //byteの書き込み
  EEPROM.write(address, 231);
}

void loop() {
}

▽次にEEPROMに保存した複数の値を読み出して表示します。
保存するときと同様にsizeofでサイズをもとめて読み出してゆきます。

#include <EEPROM.h>

typedef struct {
  int a;
  int b;
} st;

void setup() {
  Serial.begin(9600);

  unsigned int address = 0x00;
  //long変数の読み込み
  unsigned long i;
  EEPROM.get(address, i);
  address += sizeof(i);
  Serial.print("unsigned long i = ");
  Serial.println(i);
  //配列の読み込み
  unsigned long ar[2];
  EEPROM.get(address, ar);
  address += sizeof(ar);
  Serial.print("ar[0] = ");
  Serial.println(ar[0]);
  Serial.print("ar[1] = ");
  Serial.println(ar[1]);
  //構造体の読み込み
  st _st;
  EEPROM.get(address, _st);
  address += sizeof(_st);
  Serial.print("_st.a = ");
  Serial.println(_st.a);
  Serial.print("_st.b = ");
  Serial.println(_st.b);
  //byteの読み込み
  Serial.print("byte = ");
  Serial.println(EEPROM.read(address));
}

void loop() {
}

実行結果

unsigned long i = 123456
ar[0] = 12345
ar[1] = 67890
_st.a = 100
_st.b = 200
byte = 231

保存した値がEEPROMから正しく読み出されているのが確認できます。


▲トップページ > マイコンなど