M5Stack

M5Stack Atomで赤外線リモコンを作る(6)学習結果をフラッシュメモリに覚える

前回の記事M5Stack Atomで赤外線リモコンを作る(5)画面表示の追加で、学習リモコンとしての機能はほぼ実現できました。しかし、大きな問題が1つあります。それは

電源を切ると学習結果を忘れる

という問題です。

このプログラムでは、リモコン信号データをRAM上に記憶しているため、電源を切ると忘れてしまいます。M5シリーズにはフラッシュメモリーが搭載されており、ここにリモコン信号データを書き込んでおけば、電源を切っても忘れず、次回電源を入れた時に読み出せるようになります。

この記事ではフラッシュメモリーにリモコン信号データを読み書きする方法を説明します。

SPIFFSライブラリの概要

SPIFFS(SPI Flash File System)は、フラッシュメモリーをファイルシステムとして扱ってファイルの読み書きができるようにしてくれるライブラリです。ESP32用SPIFFSのドキュメントは今の所ないようですが、ESP8266用のドキュメントはここにあります。

SPIFFSの使い方の概要はArduino IDEにインストールされているサンプルプログラム SPIFFS_Test を見るとわかります。

コードの一部をここに掲載します。

SPIFFSを使うには、まず、ファイルシステムをマウントする必要があります。上記のプログラムの162行目の SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED) がその部分です。初めてSPIFFSを使う場合は、フラッシュメモリーをフォーマットする必要があります。begin()の引数としてtrueを渡すと、フォーマット済みかどうかを調べ、まだフォーマットされていない場合はフォーマットしてくれます。M5Atomの場合フォーマットに10秒ぐらいかかります。2回目以降はフォーマットしないのでbegin()は瞬時に終わります。

40〜70行目はファイルの読み書きの例です。ハードディスクあるファイルを読み書きする通常のC++のコードとよく似ていることがわかります。

学習リモコンプログラムにSPIFFS読み書き機能を追加

記事M5Stack Atomで赤外線リモコンを作る(5)画面表示の追加で作成したプログラムにSPIFFS読み書き機能を追加します。動作の概要は次の通りです。

 

・SPIFFSに作成するファイルのフルパスを /irdata と定義する。

・LEDに表示する図形として、フォーマット中を表す四角を追加する。

・リモコン信号データをRAM上で保持するために配列を用意する。前回のプログラムでは、
 resultToRawArray()がRAM領域を動的に確保してくれていたが、SPIFFSからデータ
 を読み出した時は自分で領域を確保しなければいけないため。

・setup()内で下記を行う。
  ・SPIFFSをマウントする。マウント時にフォーマット実行されると10秒程度待ちが発生
   するので、初期化中を表すLED表示をしておく。

  ・SPIFFS内の /irdata ファイルからRAMにリモコン信号を読み込んでみる。

  ・読み込みに成功したらLEDにマルを表示する。

  ・読み込みに失敗したらLEDにバツを表示する。

・loop()内で下記を行う。
  ・ボタンがクリックされたら
    ・もしリモコン信号受信待ちの状態だったら、待ち状態を解除し、SPIFFS内のファイルを削除し、LEDにバツを表示する。
    ・通常の状態で、かつ、リモコン信号データがあれば送信する
  ・ボタン長押しだったら
    ・LEDに下矢印を表示し、受信待ち状態に入る
  ・リモコン信号を受信したら
    ・受信したリモコン信号データを取り出しRAMに書き込む
    ・RAMに書いたリモコン信号データをSPIFFSに書き込む
    ・LEDにマルを表示する

 

この方針にしたがってプログラムを変更するとこうなります。

#define FASTLED_INTERNAL
#include <M5Atom.h>

//=== IRremoteESP8266 files
#include <IRrecv.h>
#include <IRremoteESP8266.h>
#include <IRac.h>
#include <IRtext.h>
#include <IRutils.h>

//=== SPIFFS files
#include "FS.h"
#include "SPIFFS.h"

// Select IR_LED in M5Atom or M5Stack IR Unit
#define IR_LED 12  // IR_LED in M5Atom
//#define IR_LED 26  // IR_LED in the M5Stack IR Unit

// ============ IRremoteESP8266 Tunable Parameters ================
const uint16_t kRecvPin = 32;
const uint32_t kBaudRate = 115200;
const uint16_t kCaptureBufferSize = 1024;
#if DECODE_AC
const uint8_t kTimeout = 50;
#else   // DECODE_AC
const uint8_t kTimeout = 15;
#endif  // DECODE_AC
const uint16_t kMinUnknownSize = 12;
#define LEGACY_TIMING_INFO false

//=== SPIFFS parameters
#define FORMAT_SPIFFS_IF_FAILED true
const char *FILE_NAME = "/irdata";

//== instances for receiving and sending IR messages
IRrecv irrecv(kRecvPin, kCaptureBufferSize, kTimeout, true);
decode_results results;  // Somewhere to store the results
IRsend irsend(IR_LED);

//== IR message container
#define BUFFER_SIZE 1024
uint16_t rawData[BUFFER_SIZE]; // IR message container
uint16_t dataLength = 0;       // IR message length

//== variable for remembering mode
bool receiving = false;

const unsigned char matrix_image[302]=
{
/* width  020 */ 0x14,
/* height 005 */ 0x05,
/* Line   000 */ 0xff,0xff,0x7f, 0xff,0xff,0x7f, 0xff,0xff,0x7f, 0xff,0xff,0x7f, 0xff,0xff,0x7f, 0xff,0x00,0xff, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0xff,0x00,0xff, 0x00,0x00,0x00, 0x00,0xff,0x00, 0x00,0xff,0x00, 0x00,0xff,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x55,0xff, 0x00,0x00,0x00, 0x00,0x00,0x00, // 
/* Line   001 */ 0xff,0xff,0x7f, 0xff,0xff,0x7f, 0xff,0xff,0x7f, 0xff,0xff,0x7f, 0xff,0xff,0x7f, 0x00,0x00,0x00, 0xff,0x00,0xff, 0x00,0x00,0x00, 0xff,0x00,0xff, 0x00,0x00,0x00, 0x00,0xff,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0xff,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x55,0xff, 0x00,0x00,0x00, 0x00,0x00,0x00, // 
/* Line   002 */ 0xff,0xff,0x7f, 0xff,0xff,0x7f, 0xff,0xff,0x7f, 0xff,0xff,0x7f, 0xff,0xff,0x7f, 0x00,0x00,0x00, 0x00,0x00,0x00, 0xff,0x00,0xff, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0xff,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0xff,0x00, 0x00,0x55,0xff, 0x00,0x55,0xff, 0x00,0x55,0xff, 0x00,0x55,0xff, 0x00,0x55,0xff, // 
/* Line   003 */ 0xff,0xff,0x7f, 0xff,0xff,0x7f, 0xff,0xff,0x7f, 0xff,0xff,0x7f, 0xff,0xff,0x7f, 0x00,0x00,0x00, 0xff,0x00,0xff, 0x00,0x00,0x00, 0xff,0x00,0xff, 0x00,0x00,0x00, 0x00,0xff,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0xff,0x00, 0x00,0x00,0x00, 0x00,0x55,0xff, 0x00,0x55,0xff, 0x00,0x55,0xff, 0x00,0x00,0x00, // 
/* Line   004 */ 0xff,0xff,0x7f, 0xff,0xff,0x7f, 0xff,0xff,0x7f, 0xff,0xff,0x7f, 0xff,0xff,0x7f, 0xff,0x00,0xff, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0xff,0x00,0xff, 0x00,0x00,0x00, 0x00,0xff,0x00, 0x00,0xff,0x00, 0x00,0xff,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x55,0xff, 0x00,0x00,0x00, 0x00,0x00,0x00, // 
};

void setup() {
  M5.begin(true, false, true); //SerialEnable = true, I2CEnable = false, DisplayEnable = true);
  Serial.printf("\n\n### IR Remote Controller ###\n");
  Serial.printf("IR Sensor Pin Number =%d \n", kRecvPin);
  irrecv.setUnknownThreshold(kMinUnknownSize);
  irsend.begin();

  Serial.print("Initializeing SPIFFS....");
  M5.dis.displaybuff((uint8_t*)matrix_image, 0, 0); // ■(四角)を表示
  SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED); // activate SPIFFS
  Serial.println("Done.");
  readRawDataFromSPIFFS(); // read data if available

  if (dataLength > 0) {
    M5.dis.displaybuff((uint8_t*)matrix_image, -10, 0); // ○(丸)を表示
  } else {
    M5.dis.displaybuff((uint8_t*)matrix_image, -5, 0); // X(バッテン)を表示
  }
}

void loop() {
  M5.update();
  if (M5.Btn.wasReleased()) {
    Serial.print("Button wasReleased(): ");
    if (receiving) {
      irrecv.disableIRIn();
      receiving = false;
      deleteRawDataInSPIFFS();
      dataLength = 0;
      M5.dis.displaybuff((uint8_t*)matrix_image, -5, 0); // X(バッテン)を表示
    } else if (dataLength > 0) {
      Serial.printf("send rawData[%d]\n", dataLength);
      irsend.sendRaw(rawData, dataLength, 38);
    } else {
      Serial.println("rawData[] is empty. Skip sending");
    }
  } else  if (M5.Btn.pressedFor(300)) { // 長押しなら赤外線信号を受信モードに遷移し、↓(下矢印)を表示
    irrecv.enableIRIn();
    receiving = true;
    Serial.println("M5.Btn.pressedFor(500): waiting for IR signal");
    M5.dis.displaybuff((uint8_t*)matrix_image, -15, 0); // ↓を表示
    while (! M5.Btn.wasReleased()) { // ボタンが離されるまで待つ
      delay(50);
      M5.update();
    }
  } else { // 赤外線信号を受信していたら、信号を配列に格納し、○を表示する。
    if (irrecv.decode(&results)) {
      uint16_t *rawData_tmp;
      irrecv.disableIRIn();
      receiving = false;
      if (results.overflow)
        Serial.printf(D_WARN_BUFFERFULL "\n", kCaptureBufferSize);
      rawData_tmp = resultToRawArray(&results);
      dataLength = getCorrectedRawLength(&results);
      Serial.printf("rawData[%d] : ", dataLength);
      for (int i = 0; i < dataLength; i++) {
        rawData[i] = rawData_tmp[i];
        Serial.printf("%d, ", rawData[i]);
      }
      delete[] rawData_tmp;
      Serial.println();
      writeRawDataToSPIFFS();
      M5.dis.displaybuff((uint8_t*)matrix_image, -10, 0); // ○(丸)を表示
      yield();             // Feed the WDT (again)
    }
  }
}

void readRawDataFromSPIFFS() {
  Serial.printf("reading rawData from SPIFF file: %s \n", FILE_NAME);
  fs::FS fs = SPIFFS;
  File file;

  if (fs.exists(FILE_NAME)) {
    file = fs.open(FILE_NAME);
  } else {
    Serial.printf("file \"%s\" not found in SPIFFS\n", FILE_NAME);
    return;
  }
  int i = 0;
  uint8_t *rawData8 = (uint8_t *)rawData;
  while (file.available()) {
    rawData8[i] = file.read();
    Serial.printf("%d ", rawData8[i]);
    i++;
  }
  Serial.println();
  dataLength = i / 2;
  file.close();

  Serial.printf("dataLength = %d\n", dataLength);
  for (int j = 0; j < dataLength; j++) {
    Serial.printf("%d", rawData[j]);
    Serial.print(" ");
  }
  Serial.println("done");
}

void writeRawDataToSPIFFS() {
  fs::FS fs = SPIFFS;
  Serial.println("writing data to SPIFF");
  File file = fs.open(FILE_NAME, FILE_WRITE);
  file.write((uint8_t *)rawData, dataLength * 2);
  file.close();
  Serial.println("done");
}

void deleteRawDataInSPIFFS() {
  Serial.printf("Deleting rawData in SPIFFS: %s\r\n", FILE_NAME);
  fs::FS fs = SPIFFS;
  if (fs.remove(FILE_NAME)) {
    Serial.println("- file deleted");
  } else {
    Serial.println("- deletion failed");
  }
}