こんにちは、ENGかぴです。
ESP32シリーズはFLASHを搭載しており基本的にはプログラム用に使用しますが一部をデータ用で使用することができます。SPIFFSライブラリを使用するとSDカードのようにFLASHにアクセスしてデータの読み書きができます。
ESP32のEEPROMライブラリを使用してデータを管理して動作確認した下記記事の内容をSPIFFSを使って実装します。
ESP32-WROOM-32E開発ボード(秋月電子)を使用しArduino IDEで開発を行います。また、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()関数を使用します。第1引数にはファイルのパスを指定します。第2引数にデータを読み込む場合は”r”を指定します。
ファイルが開けるとフラッシュに保管したデータ分のサイズをread()関数を使用して読み込みます。read()関数の他にも読み込みのための関数が実装されています。読み込んだ後はファイルを閉じるためclose()関数を使用します。
ファイルをあらかじめ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()関数を使用します。第1引数にはファイルのパスを指定します。第2引数にデータを書き込む場合は”w”を指定します。
ファイルを開きwrite()関数を使用して書き込むデータを指定します。例ではbtncntを2バイト分書き込んでいます。コメントアウトで記述している方法でも同じ処理になります。書き込んだ後はファイルを閉じるためにclose()関数を使用します。
ESP32 Sketch Data Uploadでファイルをアップロードする
ESP32-WROOM-32EではESP32 Sketch Data Uploadを使用することでファイルをFLASH領域にアップロードすることができます。ESP32 Sketch Data Uploadを追加する手順についてまとめています。
小規模のデータの格納であれば特にアップロードする必要はありませんが外部のAPIなど規模が大きなライブラリをFLASHに格納したい場合に有効です。
- 手順1SPIFFSのプラグインをダウンロード
- 手順2Arudinoスケッチ保存場所にtoolsフォルダを作成
- 手順3toolsフォルダにダウンロードしたファイルを解凍して移動(コピー)
SPIFFSプラグインをダウンロードしてArduino IDEのツールに「ESP32 Sketch Data Upload」を追加します。
GitHub-arduino-esp32fs-プラグインのダウンロード
ダウンロードしたファイルをArduinoフォルダに追加します。ダウンロードしたファイルを解凍すると「ESP32FS」フォルダが生成されます。Arduinoのスケッチブックの保存場所に指定しているフォルダ内で「tools」フォルダを作成してtoolsファルダー内にESP32FSをコピー(移動)します。
環境設定でスケッチブックの保存場所を確認することができます。初期のままであればドキュメント内のArduinoフォルダが指定されています。例ではC:\WorkSpace\Arduinoを指定しています。
toolsフォルダにESP32FSを追加した後でArduino IDEを起動するとツール欄にESP32 Sketch Data Upload」が追加されます。
ESP32 Sketch Data UploadをクリックするとFlashにデータを書き込むことができますがFlashにデータを書き込む前に書き込むファイルを準備する必要があります。Arduinoファイルが保管されているフォルダに「data」フォルダを作成します。
例ではArduinoファイルが「spiffs」フォルダにあります。このフォルダ内に「data」フォルダを作成しflash_dat.txtファイルを追加しています。
広告
PR:わからないを放置せず、あなたにあったスキルを身に着けるコツを教える テックジム 「書けるが先で、理解が後」を体験しよう!
動作確認
ESP32-WROOM-32EのWire(I2C)をLCDに接続しSW1をDIに接続します。SW1を押した回数をLCD上に表示します。SW1を2秒間長押しすると現在のボタンを押した回数をFLASHに書き込みます。長押しする際もSW1を押した回数がカウントされるためFLASHに書き込む前にカウント値を-1するようにしています。
FLASHへの書き込みが終わる(カウントが-1された後)と電源をOFF(モジュール上のリセットボタンでもよい)します。FLASHに保存されているデータは電源をOFFしても保持されるため次に電源をONしたときにFLASHのデータの確認を行いデータが消えていないことを確認します。
電源を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で学べるソフト開発と標準ライブラリの使い方
PR:わからないを放置せず、あなたにあった最低限のスキルを身に着けるコツを教える テックジム 「書けるが先で、理解が後」を体験しよう!
最後まで、読んでいただきありがとうございました。