プログラミング素人のはてなブログ

プログラミングも電気回路も専門外の技術屋の末端が勉強したことや作品をアウトプットするブログ。コードに間違いなど見つけられたら、気軽にコメントください。 C#、Python3、ラズパイなど。

Arduinoの不揮発メモリを使う

ArduinoにはEEPROMが搭載されている

Arduinoでは通常電源を切ると変数の値はリセットされます。
このために、何らかの値を再起動後にも保持していたいと考えた場合、いくつかの方法が考えられます。
・SDカードに保存しておき、起動時に読み込む
クラウドに保存しておき、起動時に読み込む
・人の頭脳に保存しておき、起動時に手動でセットする
・EEPROMに保存しておき、起動時に読み込む

プログラミングになじみがない人だと、上記の順番に思いつくのではないかと思います。
EEPROMってなんだよ?って感じですよね。

電子機器に搭載されていて、ちょっとしたメモリとして使うことができます。
秋月電子などで部品として入手することもできますが、ArduinoではそのマイコンであるATMEGAxxシリーズにEEPROMが搭載されています。
jp.rs-online.com
akizukidenshi.com

Arduinoでは1kbyte~4kbyteの容量があります。
kbyte単位だと、フロッピーディスク以下でとても小さいように見えますが、目的として画像やテキストを保存するわけではないのが一般的です。
この容量以下であれば画像も保存できますが、あまりそのような目的では使わないかと思います。
https://docs.arduino.cc/learn/built-in-libraries/eeprom/

EEPROMは一つのセルあたり1byteで0~255の値を保存できます。
ここでは、例としてArduinoで目覚まし時計を作るとしたときにそのアラーム時刻を保存することを考えてみます。
アラーム時刻は00:00~23:59までの時刻を保存できる必要があります。
プログラミング上はこの時刻をint alarm_time = 2130(例として21時30分)のようにして保持することができますが、EEPROMにはこれをこのまま書き込むことはできません。
int型変数は符号付32bit(4byte)であるのに対して、EEPROMは1つあたり1byteのためです。
ここでは、簡単のために時部分(21)と分部分(30)に分けて保存するようにします。

これが以下のコードです。
割り算と余りを使うことで、上2桁とした2桁に分割することができます。

void write_EEPROM(int alarmtime)
{
  EEPROM.write(0x00, alarmtime % 100); //下2桁 mm部分
  EEPROM.write(0x01, alarmtime / 100); //上2桁 hh部分
  Serial.println("Update EEPROM Alarm time = " + String(alarm_time));
}

これを再び読み込むときは、時部分を100倍して分部分を足します。
ここで、読み込んだ値が正しいかどうかのチェックが必要になります。
EEPROMが一度も使われていない場合、親切なつくりであれば0で初期化されていますが、かならずしもそのようになっていない場合があります。
また、何らかの理由でデータが壊れていることもあります。
ここでは、分部分は60未満、時刻としては2400未満である必要があるので、以下のようになります。

int read_EEPROM()
{
  alarm_time = EEPROM.read(0x00); //1byte目
  if (alarm_time >= 60)  alarm_time = 0; //時刻としてのERRチェック
  alarm_time += EEPROM.read(0x01) * 100; //2byte目
  if (alarm_time >= 2400)  alarm_time = 0; //時刻としてのERRチェック
  Serial.println("Read EEPROM Alarm time = " + String(alarm_time));
  return alarm_time;
}

このようにして、作ったコードの全体は以下のようになります。
Arduinoの起動時に「y」を入力するとアラーム時刻の更新モードになるようにしています。
また、プログラム動作中にflagを見て更新することも可能です。

#include <EEPROM.h>
#define TIME_SET_WAIT 5 //入力待ち時間設定
//EEPROMで保持できるのは255以下なのでHHとMMに分割して格納
int alarm_time = 0700; //24時間計で07:00

int read_EEPROM()
{
  alarm_time = EEPROM.read(0x00); //1byte目
  if (alarm_time >= 60)  alarm_time = 0; //時刻としてのERRチェック
  alarm_time += EEPROM.read(0x01) * 100; //2byte目
  if (alarm_time >= 2400)  alarm_time = 0; //時刻としてのERRチェック
  Serial.println("Read EEPROM Alarm time = " + String(alarm_time));
  return alarm_time;
}

void write_EEPROM(int alarmtime)
{
  EEPROM.write(0x00, alarmtime % 100); //下2桁 mm部分
  EEPROM.write(0x01, alarmtime / 100); //上2桁 hh部分
  Serial.println("Update EEPROM Alarm time = " + String(alarm_time));
}

void alarmTime_set()
{
  Serial.println("Enter [y] to reset time in 5sec.");

  //シリアル入力または5秒経過を待つ
  int t = millis();
  while (Serial.available() == 0 && millis() - t < TIME_SET_WAIT * 1000 ) {}
  int c = Serial.read();

  if (c == 121) {
    //"y"の文字コードのとき時刻セット
    alarm_time = 900; //24時間計で09:00
    write_EEPROM(alarm_time);
  }
  else
  {
    alarm_time = read_EEPROM();
  }
}

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

bool flag = false;
void loop ()
{
  if (flag)
  {
    write_EEPROM(alarm_time);
  }
}

また、もっと専門的にはEEPROMを有効に使いたいため、bit演算を使うことがあります。
先のサンプルコードでは0~255まで使える領域に0~99までしか使わない処理となっているためです。
ただし、このとき分部分のエラー処理は少し変わってきます。(1byte目に時部分が含まれる場合があるため)
エラー処理まで考えると今回の場合は100で割るほうが簡単かと思います。

int read_EEPROM2()
{
  alarm_time = EEPROM.read(0x00); //1byte目
  Serial.println(alarm_time);
  //if (alarm_time >= 60)  alarm_time = 0; //時刻としてのERRチェック
  alarm_time += EEPROM.read(0x01) << 8; //2byte目
  if (alarm_time >= 2400)  alarm_time = 0; //時刻としてのERRチェック
  Serial.println("Read EEPROM Alarm time = " + String(alarm_time));
  return alarm_time;
}

void write_EEPROM2(int alarmtime)
{
  EEPROM.write(0x00, alarmtime & 0xFF); //下2桁 mm部分
  EEPROM.write(0x01, alarmtime >> 8); //上2桁 hh部分
  Serial.println("Update EEPROM Alarm time = " + String(alarm_time));
}

まとめ

Arduinoの不揮発メモリ(EEPROM)の使い方を紹介しました。
再起動したときに値を保持しておくことができます。