ESP32-WROOM-32EのFLASH(SPIFFS)の応用例

組み込みエンジニア

こんにちは、ENGかぴです。

ESP32シリーズのSPIFFSライブラリを使用するとSDカードのようにFLASHにアクセスしてデータの読み書きができます。ブラウザー(スマホ)からアクセスポイントの設定に関わる設定値を変更しFLASHに保存して動作させる方法をまとめました。

SPIFFSライブラリの使い方などは下記記事にまとめています。本記事はSPIFFSを使った応用例としてまとめています。

ESP32-WROOM-32EのFLASH(SPIFFS)を使用する方法

ESP32-WROOM-32Eで動作確認したことについてリンクをまとめています。

ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方

SPIFFSの応用例

ESP32シリーズでアクセスポイントの設定をFLASHから取得したデータを使って行います。ブラウスマホなどのブラウザーでSSIDやパスワードなどアクセスポイントの設定やIPアドレスやゲートウェイ設定も変更できるようにします。

SPIFFSの初期設定

#include <FS.h>
#include <SPIFFS.h>

void setup() {
  SPIFFS.begin(); //SPIFFSの初期化
  FfsRead();      //フラッシュのデータを読み込み
  SetAp();        //フラッシュデータを使ってアクセスポイントの
}

SPIFFSライブラリを使用するためにSPIFFS.hをインクルードします。ファイルを開いて読み書きするためFS.hライブラリをインクルードします。Webページをブラウザー上に表示するためWebServer.hライブラリをインクルードします。

setup()関数内で最初にSPIFFSのbegin()メソッドを使ってSPIFFSの初期化を行います。ユーザー自作のFfsRead()関数でフラッシュデータの読み込みSetAp()関数でアクセスポイントの設定を行います。

FLASHのデータを読み込む

void FfsRead(void){

  File fp = SPIFFS.open(settings,"r");
  if(fp){ //ファイルが開いて以下を処理
    ssid = (fp.readStringUntil('\n')); //ssid部分を読み込む
    pass = fp.readStringUntil('\n'); //password部分を読み込む
    ipadr1 = fp.readStringUntil('\n'); //IP部分を読み込む(MSB)
    //以下ゲートウェイ設定
    fp.close();
  }
  else{ //ファイルが開けなかった場合
    Serial.println("Flash Read error");
    ssid = "EngKapi1";
    pass = "22223333";  
    ipadr1 = "192";
  }
  ssid.trim(); //null文字を削除
  pass.trim();
  ipadr1.trim();
  if( ipadr1.toInt() == 0){ //整数型に直したとき0であれば初期値を入れる
    ipadr1 = "192";
  }
}

SPIFFSを使ってファイルを開くためにopen()メソッドを使用します。open()メソッドの第1引数にはファイルのパスを指定します。第2引数にデータを読み込む場合は”r”を指定します。

ファイルが開けるとフラッシュに保管したデータ分のサイズをreadStringUntil()メソッドを使用し引数に(‘\n’)を指定して読み込みます。readStringUntil()メソッドは引数で指定した文字列を検出するまで読み込むメソッドです。

読み込んだ後はファイルを閉じるためclose()メソッドを使用します。

Webページ上で設定したデータをprintln()メソッドを使用して書き込むと文字列の最後に改行コードである(‘\n’)が入るため文字列の終わりを区別して文字データができます。

プログラムを初期動作させる場合はファイルが存在しないため初期値を入力しています。

WiFi.softAP(ssid.c_str(),pass.c_str());
IPAddress ip(ipadr1.toInt(), ipadr2.toInt(), ipadr3.toInt(), ipadr4.toInt());
IPAddress gateway(gwadr1.toInt(), gwadr2.toInt(), gwadr3.toInt(), gwadr4.toInt());

FLASHからデータを読み込んだ後WiFiの設定を行います。SSIDやパスワードは文字列の型からchar型に変換するためc_str()メソッドを指定しています。

WiFiのIPやゲートウェイは整数型で指定する必要があるためtoInt()メソッドで文字列の型からint型に変換しています。整数型に変換できない場合(数値以外の文字が入っている)場合は0になります。

IPやゲートウェイに0が指定されると予期せぬ動作の可能性(ソフトを書き直さない限り接続できなくなるなど)があるためユーザーがデータを指定する際に注意が必要です。

本記事のソフトは対策としてIPとゲートウェイの最上位ビットから16ビット分が0にならないように対策しています。

FLASHにデータを書き込む

bool FfsWrite(void){
  bool ret = true;

  File fp = SPIFFS.open(settings,"w");
  if( fp ){
    fp.println(ssid);
    fp.println(pass);
    //IP及びゲートウェイを書き込み(省略)
    fp.close();
  }
  else{
    Serial.println("Flash Write Error");
    ret = false;
  }
  return ret;
}

SPIFFSを使ってファイルを開くためにopen()メソッドを使用します。open()メソッドの第1引数にはファイルのパスを指定します。第2引数にデータを書き込む場合は”w”を指定します。

書き込んだ後はファイルを閉じるためにclose()メソッドを使用します。

ファイルを開くとprintln()メソッドを使って改行コード付きの文字列で書き込みを行います。読み込んだ際に改行コードをデータの区切りとして判断させることができます。

テキストデータで文字列を保管する場合文字列の長さによって各データが書き込まれる位置が異なるためデータの区切りとなる改行コードを入れて保存する方法が有効です。

write()メソッドで書き込む場合はバイナリデータを書き込むことが前提になるためあらかじめデータのサイズなどのフォーマットを統一しておくとよいでしょう。

一見するとテキストデータの方が文字として見やすく感じますが1文字あたり2バイトデータとなるためバイナリデータで管理するよりもデータ容量が大きくなってしまうデメリットもあります。

WebサーバーでHTML情報を取得する

#include <WebServer.h>

WebServer Wserver(80);

void SetAp(void){
 //WIFIの設定(省略)
  Wserver.on("/",HTTP_GET,HtmlGet); //HTMLの表示
  Wserver.on("/",HTTP_POST,HtmlPost); //HTMLからデータを受け取る
  Wserver.onNotFound(handleNotFound); //ページが存在しない場合
  Wserver.begin();
}

WebServerライブラリを使用するためにインクルードします。WebServerの型で変数を宣言しインスタンスを生成します。初期化関数内でWebServerの設定を行います。

on()メソッドはURLに対して接続要求がある場合に処理する関数を指定します。第1引数にはURLアドレスの階層を示す文字を指定します。

例えばブラウザーから「192.168.11.2」を指定する場合は”/”となり「192.168.11.2/ti」を指定して表示する場合は”/ti”を指定します。

第2引数にHTTPの種類(HTTP_GETやHTTP_POST)などを指定します。

HTTP_GETはデータを読み込む場合に使用、HTTP_POSTはHTTPからデータを送信した情報を取得する際に使用する使い分けがあります。

第3引数に接続要求に対するURLが存在する場合に処理する関数を指定します。

例ではHTMLの表示用の関数としてHtmlGet()を実装し、HTMLからデータを受け取った場合に遷移する関数としてHtmlPost()を指定しています。

onNotFound()はURLが見つからない場合の処理する関数を指定します。

begin()で指定したポートでWebServerが動作開始します。指定しない場合はポート80で動作開始します。

void HtmlGet(void) { //webページの内容を一部抜粋
html += "<input type='text' name='ssid' value='" + ssid + "' placeholder='EngKapi1'style ='width:100px;font-size:20px;'>";
}
void HtmlPost(void){

  ssid = Wserver.arg("ssid"); //送信された値を取得
  pass = Wserver.arg("pass");
  ipadr1 = Wserver.arg("ipadr1");

  if( FfsWrite()){ //データを書き込み
    FfsRead(); //書き込んだデータを読み返し
    readSet(); //変更した内容を表示
  }
}

ブラウザーに表示するHtmlGet()ではSSIDの設定用にテキストボックスを実装していますがユーザーがテキストボックス内に入力した値がvalueにセットされます。テキストボックスの値はSSIDの名前でリンクしているためHtmlPost()内でデータの名前で指定することができます。

HtmlPost()ではHTMLから送信された値をarg()メソッドを使って読み込んでいます。arg()メソッドの引数に読み込みたいデータの名前を指定します。戻り値は読み込んだ値の文字列となるためHTML内でユーザーが指定した値がセットされます。

HtmlGet()でユーザーが入力したarg()メソッドによって取得しSPIFFSライブラリを使ってFLASHにアクセスしてデータを書き込みます。

動作確認

動作確認(IPアドレス変更前)
動作確認(IPアドレス変更前)
動作確認(データ書き込み後)
動作確認(データ書き込み後)

スマホでアクセスポイントとして表示されている「Engkapi」を接続し「192.168.11.3」をスマホのGoogle Chromeでアクセスすると設定項目が表示されます。動作確認としてIPアドレスを「192.168.11.5」に変更し設定値送信のボタンを押すと現在設定値が表示されます。

ESP32-WROOM-32Eをリスタート(電源OFFまたはリセットボタン)すると変更した値でWiFi通信がスタートします。使用するブラウザーによってテキストボックスやボタンの大きさが変化するため可能な限りテキストボックスの幅やボタンの大きさをHTML言語で指定しておく方がよさそうです。

今回はSPIFFSでWIFIの設定を変更して動作させる例としてアクセスポイント設定を変更しましたが、他のセンサーの設定情報などを保管する方法等にも応用することができます。

設定がスマホからできて便利だと感じますが、WiFi通信が届かないことやAndroidスマホ(環境によると思います)では接続が継続できずに切断したりするため通信ができない場合の対策についても十分に検討する必要がありそうです。

ソースコード全体

以下のソースコードはコンパイルして動作確認をしております。チェックはしておりますがコメントなど細かな部分で間違っている可能性があります。参考としてお使いいただければと思います。

#include <FS.h>
#include <SPIFFS.h>
#include <WebServer.h>

const char *ssiddef = "EngKapi1"; //SSID
const char *passdef = "22223333"; //password
String settings = "/settings_dat.txt"; //フラッシュに置くテキストファイル

const IPAddress ip2(192,168,11,2); //IPアドレス
const IPAddress subnet(255,255,255,0); //サブネットマスク

/* 変数の宣言 */
WebServer Wserver(80);
uint32_t beforetimCnt = millis();

/* フラッシュ保存変数 */
String ssid;
String pass;
String ipset;
String ipadr1; String ipadr2; String ipadr3; String ipadr4;
String gwadr1; String gwadr2; String gwadr3; String gwadr4;

String w_ssid;
String w_pass;
String w_ipset;
String w_ipadr1; String w_ipadr2; String w_ipadr3; String w_ipadr4;
String w_gwadr1; String w_gwadr2; String w_gwadr3; String w_gwadr4;

/* プロトタイプ宣言 */
void FfsRead(void);
bool FfsWrite(void);
void SetAp(void);
void HtmlGet(void);
void HtmlPost(void);
void readSet(void);
void handleNotFound(void);

void setup() {
  
  Serial.begin(115200);
  SPIFFS.begin();
  FfsRead(); 
  SetAp();
  Serial.println("start");
}

void loop() {
  Wserver.handleClient();
}
/* FFSデータ読み込み処理 */
void FfsRead(void){

  File fp = SPIFFS.open(settings,"r");

  if(fp){ //ファイルが開いて以下を処理
    ssid = (fp.readStringUntil('\n'));
    pass = fp.readStringUntil('\n');
    ipset = fp.readStringUntil('\n');
    ipadr1 = fp.readStringUntil('\n');
    ipadr2 = fp.readStringUntil('\n');
    ipadr3 = fp.readStringUntil('\n');
    ipadr4 = fp.readStringUntil('\n');
    gwadr1 = fp.readStringUntil('\n');
    gwadr2 = fp.readStringUntil('\n');
    gwadr3 = fp.readStringUntil('\n');
    gwadr4 = fp.readStringUntil('\n');
    fp.close();
  }
  else{ //ファイルが開けなかった場合
    Serial.println("Flash Read error");
    ssid = "EngKapi1";
    pass = "22223333";
    ipset = "MANUAL";   
    ipadr1 = "192";
    ipadr2 = "168";
    ipadr3 = "11";
    ipadr4 = "2";
    gwadr1 = "192";
    gwadr2 = "168";
    gwadr3 = "11";
    gwadr4 = "1";
  }
  ssid.trim();
  pass.trim();
  ipset.trim();
  ipadr1.trim();
  if( ipadr1.toInt() == 0){
    ipadr1 = "192";
  }
  ipadr2.trim();
  if( ipadr2.toInt() == 0){
    ipadr2 = "168";
  }
  ipadr3.trim();
  if( ipadr3.toInt() == 0){
    ipadr3 = "0";
  }
  ipadr4.trim();
  if( ipadr4.toInt() == 0){
    ipadr4 = "0";
  }
  gwadr1.trim();
  if( gwadr1.toInt() == 0){
    gwadr1 = "192";
  } 
  gwadr2.trim();
  if( gwadr2.toInt() == 0){
    gwadr2 = "168";
  }   
  gwadr3.trim();
  if( gwadr3.toInt() == 0){
    gwadr3 = "0";
  }   
  gwadr4.trim();
  if( gwadr4.toInt() == 0){
    gwadr4 = "1";
  }   
}
/* WiFiセットアップ処理 */
void SetAp(void){

  if( WiFi.softAP(ssid.c_str(),pass.c_str()) == false ){
    WiFi.softAP(ssiddef,passdef);
    ssid = "EngKapi1";
    pass = "22223333";
  }

  if( ipset =="MANUAL"){
    IPAddress ip(ipadr1.toInt(), ipadr2.toInt(), ipadr3.toInt(), ipadr4.toInt());
    IPAddress gateway(gwadr1.toInt(), gwadr2.toInt(), gwadr3.toInt(), gwadr4.toInt()); 
    Serial.println(ip);
    Serial.println(gateway);
    if( WiFi.softAPConfig(ip,gateway,subnet) == false){
      WiFi.softAPConfig(ip2,ip2,subnet);
      ipadr1 = "192";
      ipadr2 = "168";
      ipadr3 = "11";
      ipadr4 = "2";
      gwadr1 = "192";
      gwadr2 = "168";
      gwadr3 = "11";
      gwadr4 = "1";
    }
  }
  Wserver.on("/",HTTP_GET,HtmlGet);
  Wserver.on("/",HTTP_POST,HtmlPost);
  Wserver.onNotFound(handleNotFound);
  Wserver.begin();
  Serial.println("WiFi start");

}
/* 設定したIPアドレスで表示するページ */
void HtmlGet(void) {
  String html = "";
  html += "<!DOCTYPE html>";
  html += "<html lang=\"ja\">";
  html += "<head>";
  html += "<meta charset=\"UTF-8\">";
  html += "<title>Flash Settings Ver1.00</title>";
  html += "<h2>設定項目</h2>";
  html += "</head>";
  html += "<body>";
  html += "<form method='post'>";
  html += "SSID: ";   //SSID
  html += "<input type='text' name='ssid' value='" + ssid + "' placeholder='EngKapi1'style ='width:100px;font-size:20px;'>";
  html += " PASSWORD: ";   //パスワード
  html += "<input type='text' name='pass' value='" + pass + "' placeholder='22223333'style ='width:100px;font-size:20px;'>";
  html += "<br><br>";
  //IP取得設定
  html += "IPアドレス設定方法: ";
  html += "<input type='radio' name='ipset' value='DHCP' ";
  if(ipset == "DHCP") {
  html += "checked='checked'"; }
  html += ">自動(DHCP) ";
  html += "<input type='radio' name='ipset' value='MANUAL' ";
  if(ipset == "MANUAL") {
  html += "checked='checked'"; }
  html += ">手動 ";
  html += "<br><br>";
  html += "<h4>手動の場合はIPアドレス及びゲートウェイアドレスの設定が必要です。</h4>";
  //装置IPアドレス設定
  html += "IPアドレス:  ";
  html += "<input type='text' name='ipadr1' value='" + ipadr1 + "' placeholder='192' style ='width:50px;font-size:20px;'>";
  html += ".";
  html += "<input type='text' name='ipadr2' value='" + ipadr2 + "' placeholder='168' style ='width:50px;font-size:20px;'>";
  html += ".";
  html += "<input type='text' name='ipadr3' value='" + ipadr3 + "' placeholder='11' style ='width:50px;font-size:20px;'>";
  html += ".";
  html += "<input type='text' name='ipadr4' value='" + ipadr4 + "' placeholder='2' style ='width:50px;font-size:20px;'>";
  html += "<br><br>";
  //ゲートウェイアドレス設定
  html += "ゲートウェイアドレス:  ";
  html += "<input type='text' name='gwadr1' value='" + gwadr1 +"' placeholder='192' style ='width:50px;font-size:20px;'>";
  html += ".";
  html += "<input type='text' name='gwadr2' value='" + gwadr2 +"' placeholder='168' style ='width:50px;font-size:20px;'>";
  html += ".";
  html += "<input type='text' name='gwadr3' value='" + gwadr3 +"' placeholder='11' style ='width:50px;font-size:20px;'>";
  html += ".";  
  html += "<input type='text' name='gwadr4' value='" + gwadr4 +"' placeholder='1' style ='width:50px;font-size:20px;'>";
  html += "<br><br>";
  //送信ボタン
  html += "<input type='submit' value='設定値送信' style='width:200px;padding:20px;font-size:20px;'><br>";
  html += "</form>";
  html += "</body>";
  html += "</html>";

  Wserver.send(200, "text/html", html);
}
/* 入力文字を取得*/
void HtmlPost(void){

  ssid = Wserver.arg("ssid"); //送信された値を取得
  pass = Wserver.arg("pass");
  ipset = Wserver.arg("ipset");
  ipadr1 = Wserver.arg("ipadr1");
  ipadr2 = Wserver.arg("ipadr2");
  ipadr3 = Wserver.arg("ipadr3");
  ipadr4 = Wserver.arg("ipadr4");
  gwadr1 = Wserver.arg("gwadr1");
  gwadr2 = Wserver.arg("gwadr2");
  gwadr3 = Wserver.arg("gwadr3");
  gwadr4 = Wserver.arg("gwadr4");

  if( FfsWrite()){ //データを書き込み
    FfsRead(); //書き込んだデータを読み返し
    readSet(); //変更した内容を表示
  }
}
/* FFSデータ書き込み処理 */
bool FfsWrite(void){
  bool ret = true;

  File fp = SPIFFS.open(settings,"w");
  if( fp ){
    fp.println(ssid);
    fp.println(pass);
    fp.println(ipset);
    fp.println(ipadr1);
    fp.println(ipadr2);
    fp.println(ipadr3);
    fp.println(ipadr4);
    fp.println(gwadr1);
    fp.println(gwadr2);
    fp.println(gwadr3);
    fp.println(gwadr4);
    fp.close();
  }
  else{
    Serial.println("Flash Write Error");
    ret = false;
  }

  return ret;
}
/* 書込み結果をWebページに表示 */
void readSet(void){

  String html = "";
  html += "<html lang=\"ja\">";
  html += "<head>";
  html += "<meta charset=\"UTF-8\">";
  html += "<h1>現在設定値</h1>";
  html += "</head>";
  html += "<body>";
  html += "SSID: " + ssid + "<br>";
  html += "PASSWORD: " + pass + "<br>";
  html += "IPアドレス設定方法: " + ipset + "<br>";
  html += "IPアドレス: " + ipadr1 + "." + ipadr2 + "." + ipadr3 + "." + ipadr4 + "<br>";
  html += "ゲートウェイアドレス: " + gwadr1 + "." + gwadr2 + "." + gwadr3 + "." + gwadr4 + "<br>";
  html += "</body>";
  html += "</html>";

  Wserver.send(200, "text/html", html);
}
/* URLが存在しない場合の処理 */
void handleNotFound(void) {

  String message = "File Not Found\n\n";
  message += "URI: ";
  message += Wserver.uri();
  message += "\nMethod: ";
  message += (Wserver.method() == HTTP_GET) ? "GET" : "POST";
  message += "\nArguments: ";
  message += Wserver.args();
  message += "\n";

  for (uint8_t i = 0; i < Wserver.args(); i++) {
    message += " " + Wserver.argName(i) + ": " + Wserver.arg(i) + "\n";
  }
  Wserver.send(404, "text/plain", message); //テキストファイルであることを示している。
}

関連リンク

Arduinoのライブラリを使って動作確認を行ったことを下記リンクにまとめています。

Arduinoで学べるマイコンのソフト開発と標準ライブラリの使い方

Seeeduino XIAOで学べるソフト開発と標準ライブラリの使い方

ESP32-WROOM-32Eで学べるソフト開発と標準ライブラリの使い方

テックジム|ゼロからはじめるPython入門講座の申込

最後まで、読んでいただきありがとうございました。

タイトルとURLをコピーしました