M5Stack

M5Stack Atomで赤外線リモコンを作る(5)画面表示の追加

前回の記事M5Stack Atomで赤外線リモコンを作る(4)学習リモコン化で、リモコン信号を受信して学習し、それを送信ができるようになりました。しかし、動作のログがシリアルコンソールに出力されだけなので、シリアルコンソールを見ずに本体を操作していると、現在どういう状態にあるかがわからず不安になります。

そこで、動作状態をAtom MatrixのLED画面に表示してみることにしました。

LED Matrixに状態を表示

前回の記事で作成した学習リモコンには、状態が3つあります。

  • まだリモコン信号を受信学習していない状態
  • リモコン信号を受信待ちの状態
  • リモコン信号の学習が終了し、送信可能である状態

それぞれの状態をLED Matrixにバツ下矢印マルを表示して示すことにします。

LED Matrixに表示するデータは、手動で作ることもできますが、M5Stack社が提供しているAtomPixToolを使うとGUI操作で簡単に作ることができます。AtomPixToolは、M5Atomのgithubページからダウンロードできます。ダウンロードリンクはgithubページの一番下にあります。Windows用のみでMac用はありません。

AtomPixToolを起動するとこんな画面が表示されます。

右の部分は5×5のマトリックスになっています。左側にあるW,Hの数値を変更することで縦横のドット数を変更できます。

横を15ドット、縦を5ドットにして、バツ、マル、下矢印を描いてみた時の画面はこうなります。

TOOLSボタンを押してSAVEを選択すると、作成した発光パターンデータをファイルに書き出すことができます。上の画面例を書き出したファイルはこんな感じになります。

 

// File URLC:/Users/fukui/iCloudDrive/mp3/IRremoteLED.c
// Image Size: width=15,height=5
// Data  Size: 227 
const unsigned char image_IRremoteLED[227]=
{
/* width  015 */ 0x0f,
/* height 005 */ 0x05,
/* Line   000 */ 0xf0,0x14,0xff, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0xf0,0x14,0xff, 0x00,0x00,0x00, 0x55,0xff,0x7f, 0x55,0xff,0x7f, 0x55,0xff,0x7f, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0xff,0xff, 0x00,0x00,0x00, 0x00,0x00,0x00, // 
/* Line   001 */ 0x00,0x00,0x00, 0xf0,0x14,0xff, 0x00,0x00,0x00, 0xf0,0x14,0xff, 0x00,0x00,0x00, 0x55,0xff,0x7f, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x55,0xff,0x7f, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0xff,0xff, 0x00,0x00,0x00, 0x00,0x00,0x00, // 
/* Line   002 */ 0x00,0x00,0x00, 0x00,0x00,0x00, 0xf0,0x14,0xff, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x55,0xff,0x7f, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x55,0xff,0x7f, 0x55,0xff,0xff, 0x55,0xff,0xff, 0x00,0xff,0xff, 0x55,0xff,0xff, 0x00,0xff,0xff, // 
/* Line   003 */ 0x00,0x00,0x00, 0xf0,0x14,0xff, 0x00,0x00,0x00, 0xf0,0x14,0xff, 0x00,0x00,0x00, 0x55,0xff,0x7f, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x55,0xff,0x7f, 0x00,0x00,0x00, 0x00,0xff,0xff, 0x00,0xff,0xff, 0x00,0xff,0xff, 0x00,0x00,0x00, // 
/* Line   004 */ 0xf0,0x14,0xff, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0xf0,0x14,0xff, 0x00,0x00,0x00, 0x55,0xff,0x7f, 0x55,0xff,0x7f, 0x55,0xff,0x7f, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0xff,0xff, 0x00,0x00,0x00, 0x00,0x00,0x00, // 
};

 

データはunsinged charの一次元配列になっていて、1個目の要素は幅、2個目の要素は高さ、3個目以降は画素のRGB値が左上のドットから右下のドットに向けて順に並んでいます。1ドットあたり3要素あり、R,G,Bの順に値が並んでいます。したがって、上記の例では要素数が、

1+1+15x5x3 = 227

という計算で227になります。画素データは画面上の1行がファイル上の1行になっています。上の例では右端で折り返されているため複数行になっていますが、実際は1行です。左端の行番号を見るとどこが1行かわかります。配列名は「image_」にファイル名を連結した名前になっています。C++の変数名として不適切な文字が含まれている場合は、修正してください。

画像データの配列定義をAtomのプログラムにコピペして使います。画像をLED Matrixに表示するには、

void displaybuff(uint8_t *buffptr, int8_t offsetx = 0, int8_t offsety = 0)

を使います。buffptrには画像データの配列を指定します。offsetxとoffsetyは画像データをx軸、y軸方向にどれぐらいずらして表示するかを指定します。

例えば画像データの配列がこれの場合、

displaybuf((uint8_t*)IRremoteLED, 0, 0) では、Xが表示されます。

displaybuf((uint8_t*)IRremoteLED, -5, 0) では、が表示されます。

offsetを「どのドットから表示するかという値」と解釈してしまうと 5 を指定してしまいたくなりますが、「LED枠に対して画像起点を何ドットずらすかの値」と考え、-5(つまり左方向に5ドット)動かすと考えます。

では、displaybuf((uint8_t*)IRremoteLED, 5, 0) は何が表示されるかというと、下矢印の画像が表示されます。これは画像データが輪になっていて左端が右端につながっているものとして処理されるためです。

右端も左端につながっているように処理されるので、電光掲示板のような流れる文字列を表示する時にdisplaybuf()は便利に使える関数です。

学習リモコンプログラムにLED表示を追加

M5Stack Atomで赤外線リモコンを作る(4)学習リモコン化で作成したプログラムにLED表示機能を追加したプログラムはこうなります。

#define FASTLED_INTERNAL
#include <M5Atom.h>
#include <IRrecv.h>
#include <IRremoteESP8266.h>
#include <IRac.h>
#include <IRtext.h>
#include <IRutils.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 TUNEABLE 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
// =================================================================

IRrecv irrecv(kRecvPin, kCaptureBufferSize, kTimeout, true);
decode_results results;  // Somewhere to store the results
IRsend irsend(IR_LED);

uint16_t *rawData;        // IR message container
uint16_t dataLength = 0;  // IR message length

const unsigned char image_IRremoteLED[227]=
{
/* width  015 */ 0x0f,
/* height 005 */ 0x05,
/* Line   000 */ 0xf0,0x14,0xff, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0xf0,0x14,0xff, 0x00,0x00,0x00, 0x55,0xff,0x7f, 0x55,0xff,0x7f, 0x55,0xff,0x7f, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0xff,0xff, 0x00,0x00,0x00, 0x00,0x00,0x00, // 
/* Line   001 */ 0x00,0x00,0x00, 0xf0,0x14,0xff, 0x00,0x00,0x00, 0xf0,0x14,0xff, 0x00,0x00,0x00, 0x55,0xff,0x7f, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x55,0xff,0x7f, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0xff,0xff, 0x00,0x00,0x00, 0x00,0x00,0x00, // 
/* Line   002 */ 0x00,0x00,0x00, 0x00,0x00,0x00, 0xf0,0x14,0xff, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x55,0xff,0x7f, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x55,0xff,0x7f, 0x55,0xff,0xff, 0x55,0xff,0xff, 0x00,0xff,0xff, 0x55,0xff,0xff, 0x00,0xff,0xff, // 
/* Line   003 */ 0x00,0x00,0x00, 0xf0,0x14,0xff, 0x00,0x00,0x00, 0xf0,0x14,0xff, 0x00,0x00,0x00, 0x55,0xff,0x7f, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x55,0xff,0x7f, 0x00,0x00,0x00, 0x00,0xff,0xff, 0x00,0xff,0xff, 0x00,0xff,0xff, 0x00,0x00,0x00, // 
/* Line   004 */ 0xf0,0x14,0xff, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0xf0,0x14,0xff, 0x00,0x00,0x00, 0x55,0xff,0x7f, 0x55,0xff,0x7f, 0x55,0xff,0x7f, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0x00,0x00, 0x00,0xff,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();
  M5.dis.displaybuff((uint8_t*)image_IRremoteLED, 0, 0); // X(バッテン)を表示
}

void loop() {
  M5.update();
  if (M5.Btn.wasReleased()) {
    Serial.print("Button wasReleased(): ");
    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();
    Serial.println("M5.Btn.pressedFor(300): waiting for IR signal");
    M5.dis.displaybuff((uint8_t*)image_IRremoteLED, -10, 0); // ↓を表示
    while (! M5.Btn.wasReleased()) { // ボタンが離されるまで待つ
      delay(50);
      M5.update();
    }
  } else { // 赤外線信号を受信していたら、信号を配列に格納
    if (irrecv.decode(&results)) {
      irrecv.disableIRIn(); // 赤外線受信モードを解除
      if (results.overflow)
        Serial.printf(D_WARN_BUFFERFULL "\n", kCaptureBufferSize);
      rawData = resultToRawArray(&results);         // 信号データを抽出
      dataLength = getCorrectedRawLength(&results); // 配列長を抽出
      Serial.printf("rawData[%d] : ", dataLength);
      for (int i = 0; i < dataLength; i++) {
        Serial.printf("%d, ", rawData[i]);
      }
      Serial.println();
      M5.dis.displaybuff((uint8_t*)image_IRremoteLED, -5, 0); // ○(丸)を表示
    }
  }
}

1行目は、コンパイル時にLED表示ライブラリがwarningを出すのを防ぐために入れてあります。なくても動作に支障ありません。

33〜42行目はAtomPixToolで作成した画像データです。

45行目のM5.begin()の第3引数をtrueにしています。これはLEDを有効にするためです。

50行目でバツを表示しています。

66行目で下矢印を表示しています。

83行目でマルを表示しています。