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

組み込みエンジニア

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

ESP32シリーズはFLASHを搭載しており基本的にはプログラム用に使用しますが一部をデータ用で使用することができます。SPIFFSライブラリを使用するとSDカードのようにFLASHにアクセスしてデータの読み書きができます。

ESP32のEEPROMライブラリを使用してデータを管理して動作確認した下記記事の内容をSPIFFSを使って実装します。

ESP32-WROOM-32EのEEPROMを使用する方法

ESP32-WROOM-32Eの開発環境はArduino IDEを使用しています。モジュールはESP32-WROOM-32E開発ボード(秋月電子)、LCDとしてAQM1602XA-RN-GBW(秋月電子)を使用しています。LCDの使い方については下記記事にまとめています。

Seeeduino XIAOを使ってBME280のデータをLCDに表示する

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

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

SPIFFSの使い方

FLASH領域は基本的にプログラム用に使用されますが、すべての領域を使用することはほとんどありません。そのためFLASH領域の一部をデータ用のストレージとして使用することができます。

ESP32シリーズ専用にSPIFFSライブラリが搭載されており、本ライブラリを使用することでArduino環境でSDカードを操作するようなイメージでFLASH領域にデータの読み書きができるようになります。

SPIFFSの初期設定

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

String settings = "/flash_dat.txt"; //フラッシュに置くテキストファイル

SPIFFS.begin(); //SPIFFSの初期化

SPIFFSライブラリを使用するためにSPIFFS.hをインクルードします。ファイルを開いて読み書きするためFS.hライブラリもインクルードします。ファイル名はファイルを開く際に文字列として指定してもよいですが、固定のパスとなるため変数で宣言しています。

例では文字列で/flash_dat.txtを宣言し保存するデータのファイルのパスとしています。SPIFFSの初期化を行うためbegin()メソッドを使用します。

FLASHのデータを読み込む

void FfsRead(void){

  File fp = SPIFFS.open(settings,"r"); //データを読み込む

  if(fp){ //ファイルを開いて以下を処理
    for(uint8_t i = 0; i < sizeof(flash_dat); i++){
      flash_dat[i] = fp.read();
    }
  //fp.readBytes((char*)&flash_dat[0], sizeof(flash_dat));
    fp.close(); //ファイルを閉じる
  }
  else{ //ファイルが開けなかった場合
    Serial.println("Flash Read error");
    //データを読み込めない場合の処理
  }
}

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

ファイルが開けるとフラッシュに保管したデータ分のサイズをread()メソッドを使用して読み込みます。read()メソッドの他にも読み込みのためのメソッドが実装されています。読み込んだ後はファイルを閉じるためclose()メソッドを使用します。

スケッチを書き込んだ初期時はFLASH領域にファイルが存在しないためファイルが開けずデータの読み込みが失敗します。不定値によって動作不良にならないようなデフォルト値をセットするなど工夫が必要な場合もあります。

ファイルをあらかじめESP32 Sketch Data Uploadを使ってアップロードしておくことでスケッチ書き込み後に初めて動作させる場合でもデータを読み込ませることもできます。

FLASHにデータを書き込む

void FfsWrite(void){
 
  File fp = SPIFFS.open(settings,"w");
  if( fp ){
    fp.write(btncnt);
    fp.write(btncnt);
 //fp.write( &btncnt, 2);
    fp.close();
  }
  else{
    Serial.println("Flash Write Error");
  }
}

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

ファイルを開きwrite()メソッドを使用して書き込むデータを指定します。例ではbtncntを2バイト分書き込んでいます。コメントアウトで記述している方法でも同じ処理になります。書き込んだ後はファイルを閉じるためにclose()メソッドを使用します。

一度でもFLASHに書き込むことでファイルが生成されるため ハード故障など別の要因で読み込めない場合を除いて読み込み時のファイル読み込みの失敗が発生しなくなります。

ESP32 Sketch Data Uploadでファイルをアップロードする

ESP32-WROOM-32EではESP32 Sketch Data Uploadを使用することでファイルをFLASH領域にアップロードすることができます。ESP32 Sketch Data Uploadを追加する手順についてまとめています。

小規模のデータの格納であれば特にアップロードする必要はありませんが外部のAPIなど規模が大きなライブラリをFLASHに格納したい場合に有効です。

ESP32 Sketch Data Uploadを追加する手順
  • 手順1
    SPIFFSのプラグインをダウンロード
  • 手順2
    Arudinoスケッチ保存場所にtoolsフォルダを作成
  • 手順3
    toolsフォルダにダウンロードしたファイルを解凍して移動(コピー)

SPIFFSプラグインをダウンロードしてArduino IDEのツールに「ESP32 Sketch Data Upload」を追加します。

GitHub-arduino-esp32fs-プラグインのダウンロード

ダウンロードしたファイルをArduinoフォルダに追加します。ダウンロードしたファイルを解凍すると「ESP32FS」フォルダが生成されます。Arduinoのスケッチブックの保存場所に指定しているフォルダ内で「tools」フォルダを作成してtoolsファルダー内にESP32FSをコピー(移動)します。

Arduinoフォルダを保存場所
Arduinoフォルダを保存場所

環境設定でスケッチブックの保存場所を確認することができます。初期のままであればドキュメント内のArduinoフォルダが指定されています。例ではC:\WorkSpace\Arduinoを指定しています。

解凍したファイルを作成したtoolsフォルダに移動
解凍したファイルを作成したtoolsフォルダに移動

toolsフォルダにESP32FSを追加した後でArduino IDEを起動するとツール欄にESP32 Sketch Data Upload」が追加されます。

ESP32 Sketch Data Upload追加後
ESP32 Sketch Data Upload追加後

ESP32 Sketch Data UploadをクリックするとFlashにデータを書き込むことができますがFlashにデータを書き込む前に書き込むファイルを準備する必要があります。Arduinoファイルが保管されているフォルダに「data」フォルダを作成します。

アップロードするファイルをdataフォルダに置く
アップロードするファイルをdataフォルダに置く

例ではArduinoファイルが「spiffs」フォルダにあります。このフォルダ内に「data」フォルダを作成しflash_dat.txtファイルを追加しています。

「data」フォルダにファイルを追加しツール内のESP32 Sketch Data Uploadをクリックすると書き込みが開始します。

動作確認

動作確認用の回路図
動作確認用の回路図

ESP32-WROOM-32EのWire(I2C)をLCDに接続しSW1をDIに接続します。SW1を押した回数をLCD上に表示します。SW1を2秒間長押しすると現在のボタンを押した回数をFLASHに書き込みます。長押しする際もSW1を押した回数がカウントされるためFLASHに書き込む前にカウント値を-1するようにしています。

FLASHへの書き込みが終わる(カウントが-1された後)と電源をOFF(モジュール上のリセットボタンでもよい)します。FLASHに保存されているデータは電源をOFFしても保持されるため次に電源をONしたときにFLASHのデータの確認を行いデータが消えていないことを確認します。

FLASHのデータを読み込んでLCDに表示
FLASHのデータを読み込んでLCDに表示

電源をONすると初期画面が2秒表示されます。メイン処理に遷移すると初期化時に読み込んだFLASHのデータを表示します。LCDの1段目に「FLASH DATA READ:10」と表示されているためFLASHのデータが読み出せていることが分かります。

ソースコード全体

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

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

#define SLAVE_ADRS 0x3E
#define LINE1_ADRS 0x40
#define LINE2_TOP (0x40 +0x80)
#define FUNC1_SET 0x38
#define FUNC2_SET 0x39
#define INT_OSC 0x14
#define CONST_SET 0x70
#define PWR_ICON_SET 0x56
#define FOLLOWER_SET 0x6B
#define CLR_DISP 0x01
#define DISP_ONOFF_SET 0x0C

#define PIN_DI1 33
#define PIN_DO1 25

#define TIME_OFF -1         //タイマーを使用しない場合
#define TIME_UP 0           //タイムアップ
#define DI_NUM 4            //DIフィルタのサンプリング数
#define	BASE_CNT 10     //10ms×10 = 100ms
#define TIME_FILTER_MAX 1
#define FS_MAX 200
#define LCD_TIM_MAX 10
#define LED_TIM_MAX 50

typedef struct{
  uint8_t    wp;
	uint8_t    buf[ DI_NUM ];
  uint8_t    di;
}FILT_DATA;

String settings = "/flash_dat.txt"; //フラッシュに置くテキストファイル

/* 変数の宣言 */
FILT_DATA DiData;
uint8_t initmoji[2][16] ={"ESP32-FS-Test ","        Ver0.01"};
uint8_t CntInit;	//初期化時のみ使用
int8_t timfilter;   //DIフィルタ起動
int16_t timlcd;
int16_t timled;
int16_t timbtnwait = TIME_OFF;
uint32_t beforetimCnt = millis();
uint8_t btncnt;
uint8_t btncntInit;
uint8_t btncntInitshow;
uint8_t ledcnt;
bool btn1hold;

/* フラッシュ保存変数 */
uint8_t flash_dat[2];
//String str3;
//String str4;

/* プロトタイプ宣言 */
void LcdInit(void);
void LcdComand(uint8_t cmd);
void LcdWriteData(uint8_t *data, uint8_t sz);
void DspLine2Top(void);
void DspClear(void);
void ShowData(void);
void mainTimer(void);
void mainApp(void);
void DiFilterInit(void);
void DiFilter(void);
void FfsWrite(void);

void setup() {
  
  pinMode(PIN_DI1,INPUT_PULLUP);
  pinMode(PIN_DO1,OUTPUT);
  Wire.begin();
  Serial.begin(115200);
  SPIFFS.begin();
  FfsRead();  //フラッシュのデータを読み出す
  DiFilterInit();
  LcdInit();
  Serial.println("start");
}

void loop() {
  mainTimer();
  DiFilter();
  mainApp();
}
/* メインアプリ処理 */
void mainApp(void){

  if( DiData.di == 0){
    if( btn1hold == false ){
      btn1hold = true;
      if( timbtnwait == TIME_OFF ){
        timbtnwait = FS_MAX;
      }
      if(++btncnt >= 100 ){
        btncnt = 0;
      }
    }
  }
  else{
    timbtnwait = TIME_OFF;
    btn1hold = false;
  }    
 
  if( timlcd == TIME_UP ){
    timlcd = LCD_TIM_MAX;
    ShowData(); //LCD表示部分をセット
  }

  if( timled == TIME_UP ){
    timled = LED_TIM_MAX;

    if( digitalRead(PIN_DO1)){
      digitalWrite(PIN_DO1,LOW);
    }
    else{
      digitalWrite(PIN_DO1,HIGH);
    }

    if( ++ledcnt >= btncntInit*2){
      timled = TIME_OFF;
    }
  }
    
  if( timbtnwait == TIME_UP){
    timbtnwait = TIME_OFF;
        
    if(btncnt != 0x00){
       --btncnt;
    }
        
    if( btncnt != btncntInit){
      FfsWrite();
      Serial.println("flash-ok");
      btncntInit = btncnt;
    }
  }
}
/* FFSデータ読み込み処理 */
void FfsRead(void){
  uint8_t dat;
  uint8_t chk;

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

  if(fp){ //ファイルが開いて以下を処理
    for(uint8_t i = 0; i < sizeof(flash_dat); i++){
      flash_dat[i] = fp.read();
    }
    //fp.readBytes((char*)&flash_dat[0], sizeof(flash_dat));
    fp.close();
    dat = flash_dat[0];
    chk = flash_dat[1];
  }
  else{ //ファイルが開けなかった場合
    Serial.println("Flash Read error");
    dat = 0;
    chk = 0;
  }

  if( dat == 0xFF or chk ==0xFF){ 
    dat = 0;
    chk = 0;
  }
  else{
    btncntInit = dat;
    btncntInitshow = dat;
  }

  Serial.print("dat: "); Serial.println(dat);
  Serial.print("chk: "); Serial.println(chk);  
}
/* FFSデータ書き込み処理 */
void FfsWrite(void){

  File fp = SPIFFS.open(settings,"w");
  if( fp ){
    fp.write(btncnt);
    fp.write(btncnt);
    //fp.write( &btncnt, 2);
    fp.close();
  }
  else{
    Serial.println("Flash Write Error");
  }
}
/* LCD初期化処理 */
void LcdInit(void){
 
  LcdComand(FUNC1_SET); //8ビットバス・2LINE表示
  LcdComand(FUNC2_SET); //拡張コマンド
  LcdComand(INT_OSC); //内部周波数調整
  LcdComand(CONST_SET); //コントラスト1
  LcdComand(PWR_ICON_SET); //コントラスト2
  LcdComand(FOLLOWER_SET); //フォロワー制御
  LcdComand(FUNC1_SET); //拡張コマンドをオフ
  DspClear();
  LcdComand(DISP_ONOFF_SET);
  LcdWriteData( &initmoji[0][0], sizeof(initmoji[0])); //1段目の表示
  DspLine2Top(); //カーソル移動
  LcdWriteData( &initmoji[1][0], sizeof(initmoji[1])); //2段目の表示
  delay(2000);
}
/* LCDへのコマンド処理 */
void LcdComand(uint8_t cmd){

  Wire.beginTransmission(SLAVE_ADRS);
  Wire.write(0x00);
  Wire.write(cmd);
  Wire.endTransmission();
  delay(1);
}
/* LCDに文字を表示 */
void LcdWriteData(uint8_t *data, uint8_t sz){

  Wire.beginTransmission(SLAVE_ADRS);
  Wire.write(0x40);
  Wire.write(data, sz);
  Wire.endTransmission();
}
/* 2段目にカーソル移動 */
void DspLine2Top(void){
    
  LcdComand(LINE2_TOP);
  delayMicroseconds(40);
}
/* ディスプレイクリア */
void DspClear(void){
  
  LcdComand(CLR_DISP);
  delayMicroseconds(40);
}
/* ディスプレイ表示 */
void ShowData(void){
  char line1[16];
  char line2[16];

  DspClear();
  memset(&line1[0],0x20,sizeof(line1));
  memset(&line2[0],0x20,sizeof(line2));
  sprintf(line1,"FLASH DATA:%u",btncntInitshow);
  sprintf(line2,"btncnt:%u",btncnt);
  delayMicroseconds(40); //文字が表示されないことがあるためウェイトを置く

  LcdWriteData( (uint8_t*)&line1, sizeof(line1)); //1段目の表示
  DspLine2Top(); //2段目にカーソル移動
  LcdWriteData( (uint8_t*)&line2, sizeof(line2)); //2段目の表示
}
//--------SWのDIフィルタ----------------------------
void DiFilter(void){
  uint8_t i;
  bool    boo = true;

  if( timfilter == TIME_UP ){
    timfilter = TIME_FILTER_MAX;
    DiData.buf[ DiData.wp ] = digitalRead(PIN_DI1);

    for( i = 1; i < DI_NUM; i++){
      if( DiData.buf[i-1] != DiData.buf[i] ){
        boo = false;
        break;
      }
    }

    if( boo ){ ////比較して一致したら値を採用
       DiData.di = DiData.buf[ 0 ]; 
    }

    if( ++DiData.wp >= DI_NUM){
      DiData.wp = 0;
    }
  }
}
// DIフィルタの初期化 //
void DiFilterInit(void){
	
  CntInit = BASE_CNT;
	while(CntInit > 0){  //0になるまでフィルタを実施
		DiFilter();         //DIフィルタ処理
    delay(10);     //10ms遅延させてDIフィルタ処理
    CntInit--;
		timfilter = TIME_UP;
	}    
}
/* タイマ管理関数 */
void mainTimer(void){

  if ( millis() - beforetimCnt > BASE_CNT ){
    beforetimCnt = millis();
            
    if( timlcd > TIME_UP ){
        --timlcd;
    }
    if( timfilter > TIME_UP ){
        --timfilter;
    }
    if( timbtnwait > TIME_UP ){
        --timbtnwait;
    }
    if( timled > TIME_UP ){
        --timled;
    }
  }
}

関連リンク

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

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

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

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

テックジム-将来のためにプログラミングを学ぶ

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

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