Arduino EEPROMの使い方|電源が切れても消えない保存とレジスタ操作 練習問題7問
この記事では、電源が切れても消えないメモリEEPROM(データフラッシュ)へ、値を保存して読み戻す練習をします。設定値を覚えておくだけでなく、制御の状態や、異常が起きたときの記録を残して、あとから原因を解析するために使う、市場製品では必ず採用される機能ですね。
マイコンの普通の変数は、電源が切れると消えます。しかし「最後に運転していた設定温度」や「累積の動作回数」のように、電源が切れても覚えておきたい値もある。さらに実務では、異常が起きたときのセンサの値や、機能が切り替わった時刻を残しておき、あとから「なぜそうなったか」を解析する目的でも使います。そこで登場するのがEEPROMです。総合課題の温湿度調整システムでも、動作の状態や記録をEEPROMへ残します。
まずArduinoのEEPROMライブラリで読み書きし、次にEEAR・EEDR・EECRのレジスタを使って、書き込み手順を自分で組みます。EEPROMは書き込み回数に上限があるので、その扱いにも触れます。
<自己紹介>
筆者は現役の車載組込みソフトエンジニアです。転職前に作った温湿度調整システムでは、動作の状態や異常の記録をEEPROM(DF)へ残し、あとから「なぜその動作になったのか」をたどれるようにしました。この作り込みは面接で説明した成果物の一部でもあり、実務でも不揮発メモリの扱いは欠かせません。
目次
EEPROMで学ぶこと
EEPROMは、電源を切っても内容が消えない小さなメモリです。ATmega328Pには1024バイトのEEPROMが入っていて、1バイト単位で読み書きできます。
- EEPROMは電源を切っても消えない。設定値の保存や、状態・異常の記録(あとで解析する履歴)に使う
- アドレスは
EEAR、データはEEDR、操作はEECRで指定する - 書き込みはEEMPEを1にしてからEEPEを1にする、決まった手順で行う
- 書き込み回数には上限があるので、同じ値なら書かない工夫をする
本記事のレジスタ版は、Arduino Uno(ATmega328P)向けにPlatformIOでコンパイル確認しました。保存と読み戻しの動作は、手元のUno R3で確認しながら進めてください。
準備するもの
| 使うもの | 用途 |
|---|---|
| Arduino Uno R3またはR3互換ボード | ATmega328P内蔵のEEPROMを使う。追加部品は不要。本記事はUno R4対象外 |
| USBケーブル | スケッチの書き込みと、シリアルでの値の確認に使う |
保存した値をシリアルで確認するので、先にシリアルのドリルを済ませておくと分かりやすくなります。
まずライブラリでEEPROMに読み書きする
最初はArduinoのEEPROMライブラリを使います。起動のたびにアドレス0の値を読み、1つ増やして書き戻す「起動回数カウンタ」です。
#include <EEPROM.h>
void setup() {
Serial.begin(9600);
uint8_t count = EEPROM.read(0); // アドレス0を読む
Serial.print("boot count = ");
Serial.println(count);
EEPROM.update(0, count + 1); // 1つ増やして保存
}
void loop() {
}
電源を入れ直すたびに、表示される数字が増えます。電源を切っても値が残っている、これがEEPROMです。ここでEEPROM.write()ではなくEEPROM.update()を使ったのには理由があり、それはレジスタ版で説明します。
次にレジスタで読み書きする
次に、同じ読み書きをレジスタで組みます。EEPROMの書き込みには決まった手順があり、順番を間違えると書けません。ここでも、いきなりEECRと書く前に、データシートで各レジスタとビットを確認します。
データシートからレジスタを読み解く
使うのはATmega328Pのデータシート(Microchip、文書番号7810D–AVR)の「7. EEPROM Data Memory」(レジスタ定義は7.6 Register Description)です。以下では、データシートの該当箇所を原文引用しながら、3つの手順で読み解きます。
① 3つのレジスタの役割を知る
EEPROMの操作は、次の3つのレジスタで行います。
| レジスタ | 役割 |
|---|---|
EEAR |
読み書きするアドレスを指定する(0〜1023) |
EEDR |
書き込むデータを入れる。読み出し時は読んだデータが入る |
EECR |
読み出し・書き込みの操作を指示する |
② EECRのビット構成を見る
操作を指示するEECRは8ビットで、今回使うのは書き込みのEEMPE(2番ビット)・EEPE(1番ビット)と、読み出しのEERE(0番ビット)です。データシートの「7.6.3 EECR – The EEPROM Control Register」(定義図はp.20)とEEMPEの説明(p.21)には、原文でこう書かれています(英文は原文引用です)。

Bit 2 – EEMPE: EEPROM Master Write Enable
The EEMPE bit determines whether setting EEPE to one causes the EEPROM to be written. When EEMPE is set, setting EEPE within four clock cycles will write data to the EEPROM at the selected address.
(訳:ビット2 – EEMPE:EEPROMマスター書き込み許可。EEMPEビットは、EEPEを1にしたときにEEPROMへ書き込むかどうかを決める。EEMPEがセットされていると、4クロックサイクル以内にEEPEを1にすれば、選んだアドレスへデータが書き込まれる。)出典:ATmega328P Datasheet(Microchip, 7810D–AVR)7.6.3 EECR「Bit 2 – EEMPE」, p.21 より原文引用(© Microchip Technology)
下の表は、この定義から今回使うビットを抜き出したものです。
| レジスタ | bit7 | bit6 | bit5 | bit4 | bit3 | bit2 | bit1 | bit0 |
|---|---|---|---|---|---|---|---|---|
EECR |
– | – | EEPM1 | EEPM0 | EERIE | EEMPE | EEPE | EERE |
③ 書き込みの手順を守る
データシートには、書き込みの順番が決められています。EEPE(EEPROM Write Enable)の説明(p.21)には、原文でこう書かれています(英文は原文引用です)。
The EEMPE bit must be written to one before a logical one is written to EEPE, otherwise no EEPROM write takes place.
(訳:EEPEに1を書く前に、EEMPEビットに1を書いておかなければならない。さもないとEEPROMへの書き込みは起こらない。)出典:ATmega328P Datasheet(Microchip, 7810D–AVR)7.6.3 EECR「Bit 1 – EEPE」, p.21 より原文引用(© Microchip Technology)
この順番どおりにしないと、EEPROMは書き換わりません。データシートの手順を整理すると次のとおりです。
- 前の書き込みが終わるまで、
EEPEが0になるのを待つ EEARにアドレス、EEDRにデータを入れるEEMPEを1にする- その直後(4サイクル以内)に
EEPEを1にする
EEMPEを立ててからEEPEを立てるまでの時間が短いのは、意図しない書き込みを防ぐためです。読み出しはもっと単純で、EEARにアドレスを入れてEEREを1にし、EEDRを読むだけです。
#include <Arduino.h>
#include <avr/interrupt.h>
void eeprom_write_reg(uint16_t addr, uint8_t data) {
while (EECR & _BV(EEPE)) { // 前の書き込み完了を待つ
// 待機
}
cli(); // 手順の途中で割り込ませない
EEAR = addr;
EEDR = data;
EECR |= _BV(EEMPE); // マスター書き込み許可
EECR |= _BV(EEPE); // 書き込み開始
sei();
}
uint8_t eeprom_read_reg(uint16_t addr) {
while (EECR & _BV(EEPE)) { // 書き込み中は読まない
// 待機
}
EEAR = addr;
EECR |= _BV(EERE); // 読み出し
return EEDR;
}
EEPROM.read()はEEARとEEREによる読み出しを、EEPROM.write()はEEMPE→EEPEの書き込み手順を、それぞれ肩代わりしていました。EEPROM.update()は、書き込む前に一度読んで、値が同じなら書かないようにしてくれています。
練習問題(基礎5+発展2)
基礎5問は、レジスタの役割と書き込み手順を確認します。発展2問は、書き込み回数を減らす工夫と、2バイト以上の保存を扱います。まず自分で考えてから回答を開いてください。
基礎 Q1
EEPROMで、読み書きするアドレスを指定するレジスタはどれですか。
解答・解説を見る
解答:EEAR。
解説:EEARにアドレス(ATmega328Pは0〜1023)を入れます。データはEEDR、操作の指示はEECRです。
基礎 Q2
EEPROMへ書き込むとき、EEPEを1にする直前に1にしておくビットは何ですか。
解答・解説を見る
解答:EEMPE。
解説:EEMPE(マスター書き込み許可)を1にしてから、4サイクル以内にEEPEを1にします。順番と時間が決められているのは、意図しない書き込みを防ぐためです。
基礎 Q3
書き込みを始める前に、なぜEEPEが0になるのを待つのですか。
解答・解説を見る
解答:前の書き込みがまだ終わっていない可能性があるから。
解説:EEPROMの書き込みには時間がかかります。EEPEが1の間は書き込み中なので、0に戻る(完了する)まで待ってから次の操作をします。
基礎 Q4
アドレスを指定して1バイトを読み出す手順を、レジスタで書いてください。
解答・解説を見る
EEAR = addr;
EECR |= _BV(EERE);
uint8_t data = EEDR;
解説:読み出しは書き込みより単純です。アドレスをEEARに入れ、EEREを1にすると、EEDRに読んだ値が入ります。
基礎 Q5
Arduinoのライブラリで、無駄な書き込みを避けたいとき、write()とupdate()のどちらを使いますか。
解答・解説を見る
解答:update()。
解説:update()は、書き込む前に今の値を読み、同じなら書きません。EEPROMは書き込み回数に上限があるため、変化がないときに書かないことが寿命を延ばします。
発展 Q6
レジスタ版で、書き込み回数を減らすeeprom_update_reg()を書いてください(同じ値なら書かない)。
解答・解説を見る
void eeprom_update_reg(uint16_t addr, uint8_t data) {
if (eeprom_read_reg(addr) != data) {
eeprom_write_reg(addr, data);
}
}
解説:先に読み出して、今の値と違うときだけ書き込みます。これでライブラリのupdate()と同じ、寿命にやさしい動きになります。
発展 Q7
0〜255を超える値(例:設定温度を10倍した数など)を保存したいとき、どう保存しますか。
解答・解説を見る
解答:2バイトに分けて、上位8ビットと下位8ビットを別々のアドレスへ保存する。
解説:EEPROMは1バイト単位。value >> 8で上位、value & 0xFFで下位を取り出し、2つのアドレスへ保存します。読むときは逆に組み立てます。このビットの分け方はC言語 ビット演算のドリルと同じ考え方。ArduinoのEEPROM.put()とget()を使うと、これをまとめて扱えます。
例:実務で実際に書くコード
実務でも、電源が切れても保持したい情報は不揮発メモリへ書きます。車載では、故障の記録(ダイアグ)、学習した補正値、積算値などがその代表例。たとえば「真冬なのに、なぜか冷房が入った」ような異常が起きたとき、そのときのセンサの値や切り替わった時刻を残しておけば、あとから原因を追いやすくなります。ただし書き込み回数に上限があるので、いつ書くかが設計の勘どころです。
/* 毎回書かず、値が変わったときだけ保存する */
if (new_state != saved_state) {
eeprom_update_reg(ADDR_STATE, new_state);
saved_state = new_state;
}
ポイントは、日常的にこまめに書くより、値が変わったときだけ書くことです。EEPROMは書き換えられる回数に上限があるため、書くタイミングを絞ることが、長く使うためのコツになります。
例えばエアコン操作のプログラムであれば、自動ON/OFFが機能したタイミングや、日ごと の記録などしたりですね。あとはマイコン停止前に書きこむことも多いです。
最後につまずきポイントをおさらい
loop()で毎回書くと、早く寿命に達します。値が変わったときだけ書く、または電源が切れる直前に書く、といった形にしてください。読み出しには回数の上限はありません。
もう一つは、書き込みには時間がかかる点です。EEPEが0に戻るまで次の書き込みを待つ必要があり、待たずに連続で書くと失敗します。またEEAR・EECRなどのレジスタ名はATmega328P向けで、Uno R4など別のマイコンでは変わる点も、これまでの記事と同じです。
よくある質問(FAQ)
EEPROMとフラッシュメモリは違いますか?
どちらも電源を切っても消えないメモリですが、用途が違います。フラッシュは主にプログラム本体を置く場所で、EEPROMは動作中に書き換えたい小さなデータを置く場所です。この記事のEEPROMは、設定値や状態、異常の記録の保存に向いています。
どのくらいの大きさを保存できますか?
ATmega328PのEEPROMは1024バイトです。設定値や状態を残すには十分ですが、大きなデータの記録には向きません。多くのデータを残したいときは、外部のEEPROMやSDカードを使います。
書き込み回数の上限が心配です
毎ループで書くような使い方をしなければ、通常は問題になりません。値が変わったときだけ書くupdate()の考え方を使い、こまめな書き込みを避ければ、上限に達するのはかなり先です。
次に読む
状態を保存できるようになったら、次は電池で長く動かすための省電力です。使わない間はマイコンを眠らせ、必要なときだけ起こすスリープを学びます。
- Arduino シリアルのドリル(#2):保存した値を画面で確認する
- C言語 ビット演算の練習問題:2バイトへの分け方(シフトとマスク)を確認する
- Arduino 低消費電力のドリル(#7・作成中):スリープで消費電力を抑える