Arduino Unoで内蔵メモリEEPROMを使って状態を保存する方法
Arduino Unoでデータを保存するには、以前に紹介した SDカードを使った外部記憶装置の利用 以外にも、内部のEEPROMへ保存する方法があります。何か状態の保存をする程度であればEEPROMで十分かもしれません。この記事ではEEPROMの使い方を解説するとともに、スイッチでLチカした状態を保存してみましたのでご参考ください。
Arduino Uno R3のEEPROM
EEPROM(Electrically Erasable Programmable Read-Only Memory)は電気的に消去でき、プログラムで書き換え可能な不揮発性メモリです。Arduino Uno R3で1024バイトのEEPROMが内蔵されています。わずか1kB程度のメモリではありますが、プリセット値やちょっとした状態保存であれば十分使えます。ちなみにArduino Uno R4ではEEPROMの値は8kBへ拡張されてます。
EEPROMの使い方
Arduino IDEではEEPROMが簡単に使えるようにライブラリが内蔵されています。スケッチでEEPROMを使う場合は下記ヘッダをインクルードします。
#include <EEPROM.h>
次のようにしてEEPROMのサイズを取得できます。
unsigned int eeprom_size = EEPROM.length();
Serial.println(eeprom_size);
データを読み込むread()
read() 関数を使ってEEPROMから保存されている値を読み込みます。address には0〜1023の範囲で値を指定します。int value = EEPROM.read(address)
データを書き込むwrite()
EEPROMへデータを書き込むには write() 関数を使います。
int value = 16
EEPROM.write(address, value)
ただしArduino Uno R3は8ビットマイコンですから、write()で書き込める値 value は8ビットになります。つまり0〜255までの値となります。それ以上大きい値を書き込む場合は分割してメモリへ保存するなどの工夫が必要となります。
他にもEEPROMライブラリには update() get() put() といった関数が存在しますが、ここで紹介した read() と write() の2つを使えば十分事足りります。
EEPROM Library | Arduino Documentation
【実践】2つのLEDの状態を保存する
2進数を使って2つのLEDの状態を記録する
LEDの状態保存には次のように2進数を使うと便利です。今後、状態管理が複数に増えたときもプログラムを完結に書くことができます。
10進数 | 二進数 | LED1 | LED2 |
---|---|---|---|
0 | 0x00 | オフ | オフ |
1 | 0x01 | オフ | オン |
2 | 0x10 | オン | オフ |
3 | 0x11 | オン | オン |
Arduinoとタクトスイッチ、LEDの配線
ここでは実際にArduino Unoを使って、タクトスイッチでLEDを点灯させ、その状態をEEPROMに書き込んで保存してみます。Arduinoとタクトスイッチ、LEDは次のように配線しました。抵抗は220Ω〜1kΩ程度で構いません。タクトスイッチの片側はGNDに接地し、もう一方をArduinoのデジタルピンへ接続させます。
2つのLEDの状態を保存するスケッチ
こちらが2つのLEDの状態を保存するソースコードになります。2進数を使うことでEEPROMの1つのアドレスに2つのLEDの状態を保存することができました。
/**
* @file ledx2.ino
* @author Toshihiko Arai ( i.officearai@gmail.com)
* @brief Arduino Uno R3のEEPROMを使って、2つのLEDの状態を記憶させるデモ
* @version 0.1
* @date 2023-07-24
*
* @copyright Copyright (c) 2023
*
*/
#include <Arduino.h>
#include <EEPROM.h>
const uint8_t LED_PINS[] = {2, 3};
const uint8_t SWITCH_PINS[] = {4, 5};
const uint8_t BIT_SIZE = 2;
uint8_t ledStates = 0b00;
void setup() {
Serial.begin(115200);
for(uint8_t i = 0; i < BIT_SIZE; i++) {
pinMode(LED_PINS[i], OUTPUT);
pinMode(SWITCH_PINS[i], INPUT_PULLUP);
}
ledStates = EEPROM.read(0);
blinkLed(ledStates);
}
void loop() {
for(uint8_t i = 0; i < BIT_SIZE; i++) {
if (digitalRead(SWITCH_PINS[i]) == LOW) {
if(bitRead(ledStates, i)) {
bitClear(ledStates, i);
} else {
bitSet(ledStates, i);
}
blinkLed(ledStates);
saveStates(ledStates);
Serial.println(ledStates, BIN);
delay(200); // チャタリング防止用
}
}
delay(100); // チャタリング防止用
}
void blinkLed(uint8_t state) {
for(uint8_t i = 0; i < BIT_SIZE; i++) {
bool isOn = bitRead(state, i);
digitalWrite(LED_PINS[i], isOn);
}
}
void saveStates(uint8_t state) {
EEPROM.write(0, state);
}
▼ 実際にArduinoで動作させている様子を動画にしました。
Arduinoでビット演算の扱い
Arduino言語はC++ベースですので、2進数のビット演算は論理演算で書くこともできます。が、Arduino言語にはとても便利な関数が用意されています。それが bitRead(x, n) bitClear(x, n) bitSet(x, n) です。x は処理したい2進数の変数を入れ、n で何ビット目かを指定します。bitRead(x, n) では n ビット目の値を取り出すことができます。bitClear(x, n) では特定の n ビットの値のみを0へ書き換えることができます。bitSet(x, n) ではその逆に特定のビットを1へ書き換えます。
タクトスイッチの使い方
タクトスイッチを使うため INPUT_PULLUP で内蔵プルアップを設定しておきます。タクトスイッチを押すことでGNDとショートし、digitalRead が LOW になります。ただしタクトスイッチはそのままだとチャッタリング生じるため、プログラムでは短い間に何十回もボタンを押したと判断してしまいます。そこで delay 関数でタイムラグを設けることでチャタリング時間を排除させてます。簡易的な方法ではありますが、結構有効です。 他にも attachInterrupt 関数を使って割り込み処理でタクトスイッチのオンオフをハンドリングする方法もあります。ただし attachInterrupt で呼ばれるイベント内では、変数の扱いに注意が必要です。メインスレッドの変数をそのまま参照できないため、処理を工夫する必要がありプログラムが複雑になります。詳しくは volatile や ISR あたりを調べてみてください。 スイッチ判定を割り込み処理で行ってた時もありますが、トラブルが多く管理が複雑になるためやめました。上記のプログラムソースのように loop() イベント内でスイッチ判定させるようにしてます。ちなみにこういった処理方法をポーリングと呼びます。