esp32の不揮発メモリについて調べて試してみました。

# 概要
ESP32にRTCを利用してタイマー機能を実装しようとしている。電源が切断されたときの対応として、タイマーの予約情報などを不揮発性領域に保存するようにしておきたい。そのためにどのような方法があり今回の目的にはどの方法が適切なのかを判断したい。そのために実際に使用してみることにする。

# 目的
ESP32で電源が切断されても設定した情報が残るようにする。

# ESP32への記憶方法
マイコンへの保存方法はEEPROMを使用する方法とフラッシュメモリを使用する方法が考えられる。

EEPROMは基本的には読み取り専用のメモリであるが、電気的にデータを消去することにより再書き込みが可能である。電源を切ってもデータが消えない不揮発性メモリと呼ばれ、電源を消すたびに消えると困る情報を格納するためのメモリとして使用される。
電気的にデータを消去することは可能であるが、データを消去する時には全消去が必要であり一部を書き換えることができないという問題点がある。
フラッシュメモリはEEPROMの一種である。EEPROMはデータをバイト単位で書き換えるが、フラッシュメモリは数十キロバイト程度のデータをブロック単位で扱うことで構造を簡素化している。その結果メモリ容量を大きくすることができ、製造コストも安くなる。またブロック単位でデータを消去して書き換えることが出来るのでEEPROMの欠点をカバーすることができる。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244155/ae6b6bd8-2185-c051-fdb7-3bf7f9a09a40.png)
ESP32のブロックダイアグラムを見ればわかる通り、ESP32にはEEPROMはついていない。
そのためにフラッシュメモリを使用する必要があるが、疑似的にEEOROMのように扱うことができるライブラリが用意されている。
以上のことから考えて、Wi-Fiの接続情報やタイマーの設定情報等容量が小さいものはEEPROMに、htmlのソースコード等容量が大きくなりそうなものはフラッシュ領域に保存するのが良いと思われる。


# 実際に試してみたこと
* SPIFFSとEEPROMの動作確認
* アプリケーションへの応用例としてESP32のWi-Fiアクセスポイントをスマートホンから設定できるようにした

###### spiffs領域への書き込み
今回のテストではHTMLのように容量が大きくなりそうなものはフラッシュメモリに置くことによって、メモリの使用量を節約することにする。

###### eeperomとしての読み書き
使用するには初期化することが必要であるので、setup関数の中に書くことにした。また疑似EEPROM領域に書き込んだ後はcommitする必要があることに注意する必要がある。
※詳しい使い方は[こちら](https://github.com/espressif/arduino-esp32/tree/master/libraries/EEPROM)を参考にした。

# 手順
1. ESP32で使用するためのGUIをhtmlで用意する
1. 用意したhtmlをESP32のspiffsに書き込む
1. spiffsの動作確認
1. EEPROMの動作確認
1. アプリケーションを作成してみる

###### ESP32で使用するためのGUIをhtmlで用意する

```html
<!DOCTYPE html>
<!-- testpage.html -->
<html>
  <html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>testpage</title>
  </head>
  <body>
    <p style="font-size:150%">テストページ</p>
    <p style="font-size:150%">各種設定</p>
    <li><a href= "/wifiset" >Wi-Fi設定</a></li>
  </body>
</html>
``` 
###### 用意したhtmlをESP32のspiffsに書き込む
spiffsへの書き込みには[ArduinoESP32ファイルシステムアップローダ](https://github.com/me-no-dev/arduino-esp32fs-plugin)を使用した。
アップローダの使用方法についてはwebに優良な情報が豊富にあるので、そちらを参考にした。

ESP32への書き込みが終わったら本当にアップロードできたか確認するためにテストコードを実行してみた。

```cpp
/*spiffsに本当に書き込みができたか確認するシンプルなプログラム
 * spiffsの中のディレクトリ構造を把握して指定したファイルの中身を確認できる
 * ライブラリについていたスケッチ例を利用
*/

#include "FS.h"
#include "SPIFFS.h"

/* You only need to format SPIFFS the first time you run a
   test or else use the SPIFFS plugin to create a partition
   https://github.com/me-no-dev/arduino-esp32fs-plugin */
#define FORMAT_SPIFFS_IF_FAILED true

void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
    Serial.printf("Listing directory: %s\r\n", dirname);

    File root = fs.open(dirname);
    if(!root){
        Serial.println("- failed to open directory");
        return;
    }
    if(!root.isDirectory()){
        Serial.println(" - not a directory");
        return;
    }

    File file = root.openNextFile();
    while(file){
        if(file.isDirectory()){
            Serial.print("  DIR : ");
            Serial.println(file.name());
            if(levels){
                listDir(fs, file.path(), levels -1);
            }
        } else {
            Serial.print("  FILE: ");
            Serial.print(file.name());
            Serial.print("\tSIZE: ");
            Serial.println(file.size());
        }
        file = root.openNextFile();
    }
}

void readFile(fs::FS &fs, const char * path){
    Serial.printf("Reading file: %s\r\n", path);

    File file = fs.open(path);
    if(!file || file.isDirectory()){
        Serial.println("- failed to open file for reading");
        return;
    }

    Serial.println("- read from file:");
    while(file.available()){
        Serial.write(file.read());
    }
    file.close();
}

void setup(){
    Serial.begin(115200);
    if(!SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED)){
        Serial.println("SPIFFS Mount Failed");
        return;
    }
    Serial.print("Mount SPIFFS");
    listDir(SPIFFS, "/", 0);
    readFile(SPIFFS, "/testpage.html");
    Serial.println( "Test complete" );
}

void loop(){

}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244155/72d541de-b51f-eaa9-520a-595752029e6e.png)
SPIFFS領域に書き込んだファイルを確認することができた。

##### EEPRROMの動作の確認
用意したテストコードで疑似EEPROMが動作することを確認した

```cpp
/*eepromclassOperationcheck
 * EEPROMの動作を確認するためのシンプルなテストプログラムです
 * サンプルプログラムのEEPROM classを参考に作成しました
 * WRITEMODEを有効にすることで書き込みのテスト、無効にすることで読み出しのテストを行う
*/


#include "EEPROM.h"

//#define WRITEMODE

//第一引数：タグ
//第二引数：確保するサイズ（今回は適当に設定）
EEPROMClass  RESERVETIME("eeprom0", 0x200);
EEPROMClass  SSID("eeprom1", 0x500);
EEPROMClass  PASSWORD("eeprom2", 0x500);


void setup(){
  
  Serial.begin(115200);
  Serial.println("start...");
  //EEPROMの初期化
  if (!RESERVETIME.begin(RESERVETIME.length())) {
    Serial.println("Failed to initialise HEIGHT");
    Serial.println("Restarting...");
    delay(1000);
    ESP.restart();
  }
  if (!SSID.begin(SSID.length())) {
    Serial.println("Failed to initialise HEIGHT");
    Serial.println("Restarting...");
    delay(1000);
    ESP.restart();
  }
  if (!PASSWORD.begin(PASSWORD.length())) {
    Serial.println("Failed to initialise HEIGHT");
    Serial.println("Restarting...");
    delay(1000);
    ESP.restart();
  }
 
  time_t reservetime;
  char password_ar[32];
  char ssid_ar[32];
  char* password;
  char* ssid;
  
#ifdef WRITEMODE
  //書き込みのテスト
  reservetime = 1646118267;   //unixtime
  password = "testpass";      //適当に設定
  ssid = "testssid";
  /*変数からEEPROMに書き込む
  第一引数：書き込むアドレス
  第二引数：書き込むデータ（文字列）
  返り値：書き込んだデータのサイズ*/
  SSID.writeString(0, ssid);
  PASSWORD.writeString(0, password);
  RESERVETIME.put(0, reservetime);
  //電源を切っても保存するにはcommitしておく必要があります  
  SSID.commit();
  PASSWORD.commit();
  RESERVETIME.commit();
  Serial.println("write finish");
#else if
  Serial.println("reading mode");
 
  /*EEPROMから変数に読み出します
  第一引数：データを読み出すアドレス
  第二引数：読み出すデータ（イメージとしてはこの領域に読み出したデータが入ってきます）
  戻り値：読み出したデータへの参照*/
  SSID.get(0, ssid_ar);
  PASSWORD.get(0, password_ar);
  RESERVETIME.get(0, reservetime);
  Serial.print("ssid: ");   Serial.println(ssid_ar);
  Serial.print("password: "); Serial.println(password_ar);
  Serial.print("reservetime: ");    Serial.println(reservetime);
  Serial.println("read finish");
#endif
  Serial.println("test finish");
 
}

void loop(){

}
```

 ##### アプリケーションへの応用例
アプリケーションへの応用例として、ESP32のWi-Fiアクセスポイントをスマートホンから設定できるようにしてみた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244155/f86c1f27-cef2-7b1e-120c-65308f1d392b.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244155/062c4c3e-633f-3df0-0e4d-cbc30d4ac6cd.png)
# 結果
SPIFFSを利用することで、外部で作成したファイルをESP32の中で使用できることが確認できた。
この機能を利用することにより、ソースコードの見通しがよくなることが確認できた。
疑似EEPROMを使用することにより、ESP32のプログラム中で不揮発メモリが使用できることが確認できた。
# 考察と感想
スケジュール機能などを使用するためにはマイコンの電源が消えたときにもデータを保持しておくことが必須になる。EEPROMを使用することにより実装することが可能になる。
SPIFFSへはHTMLやテキストファイルだけではなく、画像等も保存できる。今回はシンプルなGUIをテストしてみたがリッチなUIなどを作成することも可能だと考えられる。
SPIFFSに保存した情報をもとにwebページを作成すると表示までに時間がかかることがある。どこに原因があるのかを引き続き調べていく必要があると考えられる。

※作成したアプリケーションについてもまとめる予定ですので希望があれば公開します。
# 参考情報
[コトバンク　EEPROM](https://kotobank.jp/word/EEPROM-597)
[ArduinoESP32ファイルシステムアップローダー　github](https://github.com/me-no-dev/arduino-esp32fs-plugin)
[ESP8266/ESP32でEEPROMにデータを保存する（データチェック付き）](https://programresource.net/2020/02/21/2912.html)
[SPIFFS File Systm](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/spiffs.html#:~:text=SPIFFS%20is%20a%20file%20system,system%20consistency%20checks%2C%20and%20more)
[ESP32でSPIFFSにファイルを読み書きする方法](https://otomizu.work/2019/10/22/esp32-spiffs/)
[工作と競馬2](https://dekuo-03.hatenablog.jp/entry/2021/02/14/223655)
[ESP32マウスPart.40　ESP32のアドレスマッピングを理解する](https://rt-net.jp/mobility/archives/14334#:~:text=ESP32%E3%83%81%E3%83%83%E3%83%97%E3%81%AB%E3%81%AF%E3%80%81448,%E3%81%AA%E3%81%91%E3%82%8C%E3%81%B0%E3%81%AA%E3%82%8A%E3%81%BE%E3%81%9B%E3%82%93%E3%80%82)
[ESP32プログラミングマニュアル](https://demo-dijiudu.readthedocs.io/en/latest/api-guides/external-ram.html)
[ESP32使用マニュアル](https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf)