ソースに絡まるエスカルゴ

貧弱プログラマの外部記憶装置です。

【M5StickC Plus/Arduino】Arduinoでjsonを扱う

 最近ArduinoというかESP32というかM5StickC Plusをいじって遊んでいますが、ふと「jsonって扱えたりするのかな」と思って調べてみるとすでにそういうライブラリがいくつか存在することがわかりました。

 その中で今回は「ArduinoJson」というライブラリを使ってみたのでその備忘録です。

 では、始めます。


1:ArduinoJsonライブラリについて
 ArduinoJsonの公式ページは以下のリンクです。

 今回は公式ページのDocumentationのInstallationの「Option 1」に沿ってやっていきます。

 その他にもスケッチ例など様々な情報が公式ページにあるので、詳しく知りたい場合は各自で参照してください。


2:ArduinoJsonライブラリのインストール
 Arduino IDEを起動させてメニューの「スケッチ」→「ライブラリをインクルード」→「ライブラリを管理」を選択します。
f:id:rikoubou:20210412152028p:plain

 ライブラリマネージャの画面が出てくるので検索欄に「arduinojson」と入力してEnterキーを押し、出てきた一番上にある「Arduino Json」とかかれたライブラリの「インストール」ボタンをクリックします。
f:id:rikoubou:20210412152246p:plain

 Arduino Jsonのライブラリに「INSTALLED」の文字が出ていたらインストール完了です。
f:id:rikoubou:20210412152523p:plain


3:ArduinoJsonを使ってみる
 インストールができたので、とりあえず文字列のjsonから値を取得してみます。

 基本的な記述方法は以下のようになります。

#include <ArduinoJson.h>

char json[] = "{\"hello\":\"world\"}"; // json文字列

StaticJsonDocument<64> doc;  // メモリを確保(静的)
// DynamicJsonDocument doc(64); // メモリを確保(動的)

deserializeJson(doc, json);       // json形式に変換
const char* world = doc["hello"]; // 値の取り出し

 StaticJsonDocumentは静的、DynamicJsonDocumentは動的な場合に使用します。StaticJsonDocumentの方が処理が速いらしいのでできる限りこちらを利用するのが良いかと思います。
 このStaticJsonDocumentまたはDynamicJsonDocumentを使用する際には予めメモリを確保しておく必要があります。メモリをどのぐらい確保するべきかは次に紹介する「ArduinoJson Assistant」を使うと簡単です。


4:ArduinoJson Assistantの使い方
 例えば以下のようなjsonがあったとします。

{ "test": "hello", "value": 314 }

 このjsonから値を取得する際のStaticJsonDocumentまたはDynamicJsonDocumentのメモリを計算していきます。

 始めに「ArduinoJson Assistant」のページを開きます。

 開くと「Processor」、「Mode」、「Input type」の項目があるので、プルダウンから選んでいきます。

 今回は「文字列として受け取ったjsonをM5StickC Plusで使いたい」のでProcessorは「ESP32」、「Mode」は「Deserialize」、「Input type」は「String」を選択して「Next:JSON」をクリックします。
f:id:rikoubou:20210416160205p:plain

 テキストボックスのある画面が表示されるので、取得したいjsonを入力して「Next:Size」をクリックします。ちなみに右下にある「Prettify」をクリックするとjsonを見やすい形に整形してくれます。
f:id:rikoubou:20210416160413p:plain

 この画面でメモリサイズの計算結果が表示されます。ここの数値を使えば良いのですが「Next:Program」をクリックするとサンプルのコードが表示されます。
f:id:rikoubou:20210416160618p:plain

「Next:Program」をクリックしてみると以下のようにサンプルコードが表示されるのでコピペしてそのまま使うことができます。
f:id:rikoubou:20210416160914p:plain



5:実際に動かしたスケッチ
 サンプルコードだけを見てもどう使うかイマイチわからないかもしれないので、実際にスケッチにして使ってみます。

 4の例と同じ以下のjsonとします。

{ "test": "hello", "value": 314 }

 このjsonのそれぞれの項目の値を取得してM5StickC Plusの画面に表示させるスケッチは以下の通りです。

・M5StickCPlus_json_test.ino

#include <M5StickCPlus.h>
#include <ArduinoJson.h>

void setup() {
  Serial.begin(115200);

  M5.begin();
  M5.Axp.ScreenBreath(10); // 画面の明るさ(7-12)
  M5.Lcd.setRotation(0);   // 画面を縦向きに(0-3)
  M5.Lcd.setTextSize(2);   // フォントサイズは2

  // jsonの文字列
  String jsonStr = "{ \"test\": \"hello\", \"value\": 314 }";

  // メモリを確保してjsonに変換
  StaticJsonDocument<64> doc;
//  DynamicJsonDocument doc(64); // 動的な場合はこっち
  DeserializationError error = deserializeJson(doc, jsonStr);

  if (error) {
    // エラーの場合
    M5.Lcd.print(F("deserializeJson() failed: "));
    M5.Lcd.println(error.f_str());
  } else {
    // 正常な場合は値を取得して表示
    const char* test = doc["test"];
    int value = doc["value"];   

    M5.Lcd.print("test: ");
    M5.Lcd.println(test);
    M5.Lcd.print("value: ");
    M5.Lcd.println(value);
  }

  Serial.println("setup end");
}

void loop() {
  
}

 このスケッチを実行すると以下のようにちゃんとjsonの値が取得でき、画面に表示されています。
f:id:rikoubou:20210416161841p:plain

 以上がArduinojsonを扱うためのライブラリ「ArduinoJson」についてです。

 今回はESP32というかM5StickC Plusでやりましたが、普通のArduino Unoなどでも使えると思うので割と色々便利そうです。


・参考資料

【M5StickC Plus/Arduino】M5StickC Plusで時計を作る

 M5StickC Plusには「ESP32-PICO-D4」が使われているので、ESP32に搭載されているRTC(リアルタイムクロック)を使用することができます。
 WiFi通信もできるのでインターネットに接続して時刻合わせをすることもできます。

 つまり機能的には時計として使うことも可能というわけです。

 なので今回はM5StickC PlusのRTCを使って簡単な時計を作ってみたのでその備忘録です。

 では、始めます。


1:RTCの使い方
 RTCの日時は以下のように取得できます。

#include <M5StickCPlus.h>

RTC_DateTypeDef rtc_date;  // 日付
M5.Rtc.GetData(&rtc_date); // 日付の取り出し

int year  = rtc_date.Year;    // 年
int month = rtc_date.Month;   // 月
int date  = rtc_date.Date;    // 日
int wd    = rtc_date.WeekDay; // 曜日(0~6で日曜日から順番になっている)


RTC_TimeTypeDef rtc_time;  // 時刻
M5.Rtc.GetTime(&rtc_time); // 時刻の取り出し

int hours   = rtc_time.Hours;   // 時
int Minutes = rtc_time.Minutes; // 分
int Seconds = rtc_time.Seconds; // 秒

 またインターネットに接続できている状態で時刻合わせをするには以下のようにします。

#include <M5StickCPlus.h>

// 書き方
configTime([時差], [サマータイム有効/無効], [接続NTPサーバ(複数可能)]);

// 例:時差が9時間(日本)でサマータイム無効で「ntp.nict.jp」と「ntp.jst.mfeed.ad.jp」を使う
configTime(3600L*9, 0, "ntp.nict.jp", "ntp.jst.mfeed.ad.jp");

 M5StickC Plusは出荷時点ではRTCの時刻は特に設定されていないので、ちゃんとした時刻を知りたい場合は一度は時刻合わせをしておくとよいです。


2:時計のサンプル
 RTCの使い方がわかったので時計のサンプルスケッチを作りました。

・RTCClock.h

/**
 * RTCクロック関係
 */
#include <M5StickCPlus.h>
#include <WiFi.h>

class RTCClock {
  public:
    const char *wd[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
    const char *defaultFormat = "%04d/%02d/%02d %s\n%02d:%02d:%02d";
    const int O_YEAR   = 0;
    const int O_MONTH  = 1;
    const int O_DAY    = 2;
    const int O_WD     = 3;
    const int O_HOUR   = 4;
    const int O_MINUTE = 5;
    const int O_SECOND = 6;

    RTCClock();
    void setDefault();
    void setSpriteArea(int width, int height);
    void setShowPosition(int x, int y);
    void setFontAndSize(int fontNum, int textSize);
    void setTextAndBgColor(uint32_t textColor, uint32_t bgColor);
    void setFormat(const char *str, ...);
    void setWiFi(const char *ssid, const char *password);
    void connectWifFi();
    void syncTime();
    void disconnectWifFi();
    
    RTC_TimeTypeDef getRTCTime();
    RTC_DateTypeDef getRTCDate();
    void showDateAndTime();
    void deleteDateAndTime();
    void updateDateAndTime();

  private:
    static const int MAX_SHOW_DATE_TIME = 7;
    static const int HIDE_ORDER = -1;

    TFT_eSprite _sprite = TFT_eSprite(&M5.Lcd);

     // wifi関係
    const char *_ssid_p;
    const char *_password_p;

    // 表示フォーマット関連
    const char *_format;
    int _orderArray[MAX_SHOW_DATE_TIME];
    int _orderArraySize;

    // ディスプレイ表示関連
    bool _showFlg;
    String _dateFormat;
    String _timeFormat;
    int _fontNum;
    int _textSize;
    uint32_t _text_color;
    uint32_t _bg_color;
    int _x;
    int _y;
    int _width;
    int _height;

    int getArgumentsSize(const char *str);
    void printDateAntTime(int a[]);
};

// コンストラクタ
RTCClock::RTCClock() {
  // 初期化
  _showFlg = true;

  _ssid_p = "";
  _password_p = "";

  setDefault(); // デフォルト値を設定
}

// WiFi以外の情報のデフォルト値を設定する関数
void RTCClock::setDefault() {
  _format = defaultFormat;
  for (int i=0; i<MAX_SHOW_DATE_TIME; i++) {
    _orderArray[i] = i;
  }
  _orderArraySize = MAX_SHOW_DATE_TIME;

  _fontNum = 1;
  _textSize = 1;
  _text_color = TFT_WHITE;
  _bg_color = TFT_BLACK;

  _x = 0;
  _y = 0;
  _width  = 84 * _textSize;
  _height = 16 * _textSize;
}

// 表示するスプライトの範囲を設定する関数
void RTCClock::setSpriteArea(int width, int height) {
  _width  = width;
  _height = height;
}

// 表示の左上の位置を設定する関数
void RTCClock::setShowPosition(int x, int y) {
  _x = x;
  _y = y;
}

// フォントとサイズを設定する関数
void RTCClock::setFontAndSize(int fontNum, int textSize) {
  _fontNum = fontNum;
  _textSize = textSize;
}

// テキストの色と背景色を設定する関数
void RTCClock::setTextAndBgColor(uint32_t textColor, uint32_t bgColor) {
  _text_color = textColor;
  _bg_color = bgColor;
}

// 表示フォーマットを設定する関数
void RTCClock::setFormat(const char *str, ...) {
  // フォーマットと使用する引数の数を設定
  _format = str;
  _orderArraySize = getArgumentsSize(str);

  // 初期化
  for (int i=0; i<MAX_SHOW_DATE_TIME; i++) {
    _orderArray[i] = HIDE_ORDER;
  }

  // 可変部分の引数の値を設定
  va_list ap;
  va_start(ap, str);
  for (int i=0; i<_orderArraySize; i++) {
    _orderArray[i] = va_arg(ap, int);
  }
  va_end(ap);
}

// WiFiのSSIDとPassを設定する関数
void RTCClock::setWiFi(const char *ssid, const char *password) {
  _ssid_p = ssid;
  _password_p = password;
}

// WiFiに接続する関数
void RTCClock::connectWifFi() {
  _showFlg = false;
  _sprite.fillSprite(_bg_color);
  _sprite.setCursor(0, 0);
  _sprite.setTextFont(1);
  _sprite.setTextSize(1);
  _sprite.print("connecting");

  int counter = 0;
  WiFi.begin(_ssid_p, _password_p);

  while (counter < 10) {
    _sprite.pushSprite(_x, _y);
    if (WiFi.status() == WL_CONNECTED) {
      _sprite.print(" OK");
      _sprite.pushSprite(_x, _y);
      break;
    } else {
      counter++;
      _sprite.print(".");
      delay(500);
    }
  }
  delay(1000);
  _showFlg = true;
}

// 時刻合わせをする関数
void RTCClock::syncTime() {
  _showFlg = false;
  _sprite.fillSprite(_bg_color);
  _sprite.setCursor(0, 0);
  _sprite.setTextFont(1);
  _sprite.setTextSize(1);

  // WiFiに繋がっていたら時刻合わせをする
  if (WiFi.status() == WL_CONNECTED) {
    configTime(3600L*9, 0, "ntp.nict.jp", "ntp.jst.mfeed.ad.jp");
    _sprite.print("sync OK");
  } else {
    _sprite.print("sync NG");
  }
  _sprite.pushSprite(_x, _y);
  delay(1000);
  _showFlg = true;
}

// WiFiを切断する関数
void RTCClock::disconnectWifFi() {
  _showFlg = false;
  _sprite.fillSprite(_bg_color);
  _sprite.setCursor(0, 0);
  _sprite.setTextFont(1);
  _sprite.setTextSize(1);

  if (WiFi.status() == WL_CONNECTED) {
    WiFi.disconnect(true);
    WiFi.mode(WIFI_OFF);
    _sprite.print("disconnected.");
  } else {
    _sprite.print("disconnect NG");
  }

  _sprite.pushSprite(_x, _y);
  delay(1000);
  _showFlg = true;
}

// 引数の数を計算する関数(%の数で見ている)
int RTCClock::getArgumentsSize(const char *str) {
  int sizeCount = 0;
  while( *str != 0x00 ) {
    char c = str[0];
    if (c == '%') {
      sizeCount += 1;
    }
    str++;
  }
  return sizeCount;
}

// 時刻を取得する関数
RTC_TimeTypeDef RTCClock::getRTCTime() {
  RTC_TimeTypeDef rtc_time;  // 時刻
  M5.Rtc.GetTime(&rtc_time); // 時刻の取り出し
  return rtc_time;
}

// 日付を取得する関数
RTC_DateTypeDef RTCClock::getRTCDate() {
  RTC_DateTypeDef rtc_date;  // 日付
  M5.Rtc.GetData(&rtc_date); // 日付の取り出し
  return rtc_date;
}

// 表示する関数
void RTCClock::showDateAndTime() {
  _showFlg = true;
  _sprite.setColorDepth(16);
  _sprite.createSprite(_width, _height);
}

// 非表示にする関数
void RTCClock::deleteDateAndTime() {
  // スプライト全体を_bg_colorで塗りつぶしてメモリ開放
  _sprite.fillSprite(_bg_color);
  _sprite.pushSprite(_x, _y);
  _sprite.deleteSprite();
  _showFlg = false;
}

// 日時を更新する関数
void RTCClock::updateDateAndTime() {
  if (!_showFlg) { return; }

  _sprite.fillSprite(_bg_color); // 塗りつぶし

  _sprite.setCursor(0, 0);
  _sprite.setTextFont(_fontNum);
  _sprite.setTextColor(_text_color);
  _sprite.setTextSize(_textSize);

  RTC_TimeTypeDef rtc_time = getRTCTime(); // 時刻
  RTC_DateTypeDef rtc_date = getRTCDate(); // 日付

  int dateTimeArray[MAX_SHOW_DATE_TIME] = {
    rtc_date.Year,
    rtc_date.Month,
    rtc_date.Date,
    rtc_date.WeekDay,
    rtc_time.Hours,
    rtc_time.Minutes,
    rtc_time.Seconds
  };

  // 日付と時刻をフォーマットに合わせて表示
  printDateAntTime(dateTimeArray);

  // ディスプレイに表示
  _sprite.pushSprite(_x, _y);
}

// フォーマットに合わせて表示する関数
void RTCClock::printDateAntTime(int a[]) {
  String s = _format;
  int oa[MAX_SHOW_DATE_TIME] = { -1,-1,-1,-1,-1,-1,-1 };

  // 配列を詰め替え
  int count = 0;
  for (int i=0; i<_orderArraySize; i++) {
    int orderNum = _orderArray[i];
    if (orderNum == O_WD) {
      s.replace("%s", wd[a[O_WD]]); // %sを曜日に変更
    } else {
      oa[count] = orderNum;
      count++;
    }
  }

  // それぞれのサイズに合わせてフォーマット
  switch (count) {
    case 1:
       _sprite.printf(s.c_str(), a[oa[0]]);
      break;
    case 2:
       _sprite.printf(s.c_str(), a[oa[0]], a[oa[1]]);
      break;
    case 3:
       _sprite.printf(s.c_str(), a[oa[0]], a[oa[1]], a[oa[2]]);
      break;
    case 4:
       _sprite.printf(s.c_str(), a[oa[0]], a[oa[1]], a[oa[2]], a[oa[3]]);
      break;
    case 5:
       _sprite.printf(s.c_str(), a[oa[0]], a[oa[1]], a[oa[2]], a[oa[3]], a[oa[4]]);
      break;
    case 6:
       _sprite.printf(s.c_str(), a[oa[0]], a[oa[1]], a[oa[2]], a[oa[3]], a[oa[4]], a[oa[5]]);
      break;
    case 7:
      // 7になることはないはずだが一応設定しておく
       _sprite.printf(s.c_str(), a[oa[0]], a[oa[1]], a[oa[2]], a[oa[3]], a[oa[4]], a[oa[5]], a[oa[6]]);
      break;
  }
}


・M5StickCPlus_clock.ino

#include <M5StickCPlus.h>
#include "RTCClock.h"

const char *SSID_CHAR = "*********"; // 任意のものに書き換える
const char *PASS_CHAR = "********"; // 任意のものに書き換える

RTCClock rtcClock = RTCClock();

void setup() {
  Serial.begin(115200);
  
  M5.begin();
  M5.Axp.ScreenBreath(10);  // 画面の明るさ(7-12)
  M5.Lcd.setRotation(3);    // 画面を横向きに(0-3)
  M5.Lcd.fillScreen(GREEN); // 背景を緑

  // 任意の設定を行う
  setCustomClock();

  // WiFiのSSIDとPASSを設定
  rtcClock.setWiFi(SSID_CHAR, PASS_CHAR);

  // 時刻表示
  rtcClock.showDateAndTime();
}

bool defaultFlg = false;

void loop() {
  M5.update();

  // ボタンAが押された時
  if (M5.BtnA.wasPressed()) {
    // WiFiに接続して時刻合わせをして切断
    rtcClock.connectWifFi();
    rtcClock.syncTime();
    rtcClock.disconnectWifFi();

    defaultFlg = !defaultFlg;

    rtcClock.deleteDateAndTime(); // 一旦消す(設定した背景色で塗りつぶし)
    if (defaultFlg) {
      rtcClock.setDefault(); // デフォルトの設定
    } else {
      setCustomClock();      // 自分の設定
    }
    rtcClock.showDateAndTime(); // 再度表示
  }

  rtcClock.updateDateAndTime();
  delay(100);
}

// 自分の設定
void setCustomClock() {
  int textSize = 2;
  int width    = 84 * textSize;
  int height   = 16 * textSize;

  rtcClock.setSpriteArea(width, height);
  rtcClock.setShowPosition(5, 5);
  rtcClock.setFontAndSize(1, textSize);
  rtcClock.setTextAndBgColor(TFT_PINK, TFT_BLUE);

  const char *formatChar = "%s %04d/%02d/%02d\n%02d:%02d:%02d";
  rtcClock.setFormat(formatChar, rtcClock.O_WD, rtcClock.O_YEAR, rtcClock.O_MONTH, rtcClock.O_DAY, rtcClock.O_HOUR, rtcClock.O_MINUTE, rtcClock.O_SECOND);

  // 以下のようなフォーマットもできる
//  const char *formatChar = "%02d:%02d:%02d";
//  rtcClock.setFormat(formatChar, rtcClock.O_HOUR, rtcClock.O_MINUTE, rtcClock.O_SECOND);
}

 少し解説すると、このサンプルを書き込むと時刻が表示されます。「M5」と書かれたボタンを押すとWiFiに接続して時刻合わせをして切断されます。
 ボタンが押されるとデフォルトと自分で設定した時刻表示が切り替わるようになっています。

 以上がM5StickC Plusで時計を作った内容です。

 IoT機器として使い、時刻毎に何かを行うような場合には何かと便利かと思います。

 今回のサンプルを使って時刻合わせを行うことでかなり近い時刻になるのですが、なぜか自分の場合だと14秒ほど遅れて時刻が表示されるという現象が発生しました。何度時刻合わせをしてもその遅延は変わらなかったので何か知っている方はコメントを頂けると助かります…。


・参考資料

【M5StickC Plus/Arduino】M5StickC Plusでバッテリー残量を表示する

 M5StickC Plusには容量が小さいながらもバッテリーを搭載しているので、USBからの給電がない状態でも動かすことができます。
 バッテリーを内蔵しているのならば残量が知りたくなると思い調べてみると、バッテリーの電圧から電池残量を計算して出すことができるらしいことがわかりました。

 今回は電圧から電池残量を計算してそれっぽい表示までやってみた備忘録です。

 では、始めます。


1:電源に関する関数
 M5StickCには電源に関する関数がいくつか実装されています。

 今回のバッテリー残量を表示するために使用する関数は以下の3つです。

// 1:電圧を取得する関数(3.0~4.2Vの範囲で3.0V以下になるとシャットダウンするらしい)
float _vbat = M5.Axp.GetBatVoltage();

// 2:低電圧状態をチェックする関数(3.4V以下だと1, それ以外は0を返す)
uint8_t _low_bat = M5.Axp.GetWarningLeve();

// 3:バッテリー稼働か充電中かを判定する関数(プラスの値なら充電、マイナスの値ならバッテリー稼働)
float _ibat = M5.Axp.GetBatCurrent();

 この他にも色々と関数が実装されているので、詳しく知りたい場合は以下のページ様を参照してください。


2:バッテリー表示のサンプル
 電源に関する関数がわかったので、これらを使ってバッテリー表示のサンプルを作ってみました。
 できるだけヘッダーファイル「Battery.h」に機能をまとめており、それを読み込んで「M5StickCPlus_battery.ino」で実際に表示しています。

・Battery.h

/**
 * 電池残量表示
 */
#include <M5StickCPlus.h>

/**
 * Batteryクラス
 * 
 * 電池残量を表示する。
 */
class Battery {
  public:
    Battery();
    void setPosAndSize(int posX, int posY, int showSizeNum);
    void setDeleteBgColor(uint32_t color);
    void setTextColor(uint32_t color);
    void showBattery();
    void deleteBattery();
    void batteryUpdate(int percent);

  private:
    const float MAX_BATTERY_VOLTAGE = 4.2f;
    const float MIN_BATTERY_VOLTAGE = 3.0f;
    const int8_t BITS_PER_PIXEL     = 1;
    const int TRANS_PARENTS         = 0;
    const int MAX_SHOW_SIZE         = 7;
    const int MIN_SHOW_SIZE         = 1;

    TFT_eSprite _sprite = TFT_eSprite(&M5.Lcd);
    bool _showFlg;
    int _x;
    int _y;
    int _showSize;
    int _width;
    int _height;
    int _top_width;
    uint32_t _bg_color;
    uint32_t _line_color;
    uint32_t _text_color;

    void drawBatteryLines();
    int calcBatteryPercent();
    uint32_t getBatteryColor();
    void showBatteryPercent(int i_percent);
    bool isLowBattery();
    bool isUsingBattery();
};

/**
 * コンストラクタ
 */
Battery::Battery() {
  // 初期化
  _showFlg = true;
  _x = 0;
  _y = 0;
  _showSize = 1;
  _width     = 28*_showSize;
  _height    = 10*_showSize;
  _top_width =  2*_showSize;
  _bg_color   = TFT_BLACK;
  _line_color = TFT_WHITE;
  _text_color = TFT_WHITE;
}

/**
 * 表示位置(x, y)と表示サイズを設定する関数
 */
void Battery::setPosAndSize(int posX, int posY, int showSizeNum) {
  _x = posX;
  _y = posY;
  
  if (MAX_SHOW_SIZE < showSizeNum) {
    _showSize = MAX_SHOW_SIZE;
  } else if (showSizeNum < MIN_SHOW_SIZE) {
    _showSize = MIN_SHOW_SIZE;
  } else {
    _showSize  = showSizeNum;
  }
  
  _width     = 28*_showSize;
  _height    = 10*_showSize;
  _top_width =  2*_showSize;
}

/**
 * 削除時の背景色を設定する関数
 */
void Battery::setDeleteBgColor(uint32_t color) {
  _bg_color  = color;
}

/**
 * 電池図形と文字の色を設定する関数
 */
void Battery::setTextColor(uint32_t color) {
  _line_color = color;
  _text_color = color;
}

/**
 * 電池残量を表示する関数
 */
void Battery::showBattery() {
  _showFlg = true;
  drawBatteryLines();
  _sprite.setColorDepth(16);
  _sprite.createSprite(_width-1, _height-1);
}

/**
 * 電池残量を非表示(塗りつぶし)する関数
 */
void Battery::deleteBattery() {
  // スプライト全体を_bg_colorで塗りつぶしてメモリ開放
  _sprite.deleteSprite();
  _sprite.createSprite(_width+_top_width, _height+1);
  _sprite.fillSprite(_bg_color);
  _sprite.pushSprite(_x, _y);
  _sprite.deleteSprite();
  _showFlg = false;
}

/**
 * バッテリー残量の表示を更新する関数
 */
void Battery::batteryUpdate(int percent=-1) {
  if (!_showFlg) { return; }

  // 電池図形内部背景塗りつぶし
  _sprite.fillRect(0, 0, _width-1, _height-1, TFT_BLACK);

  // 実際にバッテリーの値を計算するか判定
  int i_percent = percent;
  if (i_percent == -1) {
    i_percent = calcBatteryPercent();
  }

  // バッテリー残量の割合を計算して背景色塗りつぶし
  int b_width = int((_width-2) * (i_percent/100.0f));
  _sprite.fillRect(0, 0, b_width+1, _height-1, getBatteryColor());

  showBatteryPercent(i_percent); // バッテリー数値を表示
  _sprite.pushSprite(_x+1, _y+1); // ディスプレイに表示
}

/**
 * 電池の図形を作成する関数
 */
void Battery::drawBatteryLines() {
  // 透明部分も作れるスプライト範囲を作成
  _sprite.setColorDepth(BITS_PER_PIXEL);
  _sprite.createSprite(_width+_top_width, _height+1);
  _sprite.fillSprite(TRANS_PARENTS);

  // 電池図形を作成
  _sprite.fillRect(0, 0, _width+1, _height+1, _line_color);
  _sprite.fillRect(1, 1, _width-1, _height-1, TRANS_PARENTS);
  _sprite.fillRect(_width+1, _top_width, _top_width, _height-(_top_width*2)+1, _line_color);

  _sprite.setBitmapColor(_line_color, TRANS_PARENTS); // 色を設定
  _sprite.pushSprite(_x, _y, TRANS_PARENTS); // 表示
  _sprite.deleteSprite(); // メモリ開放
}

/**
 * バッテリー残量%を計算する関数(戻り値は0~100)
 */
int Battery::calcBatteryPercent() {
  float _vbat = M5.Axp.GetBatVoltage();
  float percent = (_vbat - MIN_BATTERY_VOLTAGE) / (MAX_BATTERY_VOLTAGE - MIN_BATTERY_VOLTAGE);
  return roundf(percent * 100.0f);
}

/**
 * バッテリーの状態に応じて色を取得する関数
 */
uint32_t Battery::getBatteryColor() {
  // バッテリー稼働中は緑、電圧が低い時は赤色、充電中は青色
  uint32_t color = TFT_DARKGREEN;
  if (isLowBattery()) {
    color = TFT_RED;
  } else if (!isUsingBattery()) {
    color = TFT_BLUE;
  }
  return color;
}

/**
 * バッテリー残量を表示する関数
 */
void Battery::showBatteryPercent(int i_percent) {
  // バッテリー数値を表示
  _sprite.setCursor(0+_showSize, 0+_showSize);
  _sprite.setTextFont(1);
  _sprite.setTextColor(_text_color);
  _sprite.setTextSize(_showSize);
  _sprite.print(i_percent);
  _sprite.print("%");
}

/**
 * 低電圧状態かを判定する関数
 */
bool Battery::isLowBattery() {
  // 低電圧状態(3.4V以下)だと1それ以外は0
  uint8_t _low_bat = M5.Axp.GetWarningLeve();
  if (_low_bat == 0) {
    return false;
  } else {
    return true;
  }
}

/**
 * バッテリー稼働か充電中かを判定する関数
 */
bool Battery::isUsingBattery() {
  // プラスが充電、マイナスがバッテリー稼働
  float _ibat = M5.Axp.GetBatCurrent();
  if (_ibat < 0.0f) {
    return true;
  } else {
    return false;
  }
}


・M5StickCPlus_battery.ino

/**
 * バッテリー残量を表示する
 */
#include <M5StickCPlus.h>
#include "Battery.h"

Battery battery = Battery();

void setup() {
  Serial.begin(115200);
  
  M5.begin();
  M5.Axp.ScreenBreath(10); // 画面の明るさ(7-12)
  M5.Lcd.setRotation(3);   // 画面を横向きに(0-3)

  // 表示左上の位置x, yと表示サイズ(1-7)を設定
  battery.setPosAndSize(5, 5, 2);

  // deleteBattery()時の塗りつぶし色を設定
  battery.setDeleteBgColor(TFT_BLACK); 

  // 電池図形と%表示の色を設定
  battery.setTextColor(TFT_WHITE);

  // バッテリーを表示
  battery.showBattery();
  Serial.println("setup end!");
}

int count = 0;
bool showFlg = true;

void loop() {
  M5.update();

  if (count > 100) {
    count = 0;
    showFlg = !showFlg;
    if (showFlg) {
      battery.showBattery();
    } else {
      battery.deleteBattery();
    }
  }

//  // 以下の関数だと電池の電圧から計算して表示(通常はこちらを使う)
//  battery.batteryUpdate();

  // 引数の値を表示
  battery.batteryUpdate(count);
  count++;
  delay(100);
}

 このサンプルを実行すると、電池残量のサンプルとしてcountの値が表示されます。100カウント毎に電池表示が消えたり表示されたりします。

 少しだけ解説すると、充電中はパーセント表示している後ろのバーが青色で表示され、バッテリー稼働中になると緑色になります。3.4V以下の低電圧状態になるとバーは赤色になります。 「setPosAndSize」で表示位置と表示サイズ、「setDeleteBgColor」でdelete時の塗りつぶし色、「setTextColor」でパーセント表示の文字と電池の図形部分の色をそれぞれ設定できるようにしています。
 また「batteryUpdate」の引数をなしにすると実際のバッテリーの状態を表示し、引数に数値(0~100)を設定するとその数値がそのまま表示するようにしています。
 ちなみに表示にスプライトを使用しているのはスプライトの方が表示速度が速かったからです。


 以上がM5StickC Plusでバッテリー残量を表示してみた内容です。

 残量表示自体は割とすぐにできたのですが、表示に使ったスプライトの仕様がいまいちわからずに色々と苦戦しました。ただスプライトの方が画面自体に表示させるよりも高速にできることがわかったので、簡単なゲームみたいなものを作りたい場合はスプライトで表示させる方が良いかと思いました。

 今回ヘッダーファイルに分けたことでスマホの画面のように常に表示させるみたいなこともやりやすくなったかと思うので、色々有効活用していきたいです。
 

・参考資料

【M5StickC Plus/Arduino】M5StickC PlusでQRコードを表示する

 今回もまたM5StickC Plusの記事です。

 調べてみるとデフォルトで簡単にQRコードを生成できることがわかったのでその備忘録です。

 では、始めます。


1:QRコード表示方法
 以下の関数を使うことでQRコードを表示することができます。

#include <M5StickCPlus.h>

M5.begin();
M5.Lcd.qrcode([文字列], [表示x座標], [表示y座標], [横幅], [QRコードバージョン]); // QRコード表示

 QRコードのバージョンは1~15まで対応しているようです。


2:QRコード表示のサンプル
 表示の仕方がわかったので実際にGoogleのホーム画面をQRコードで表示してみます。

・M5StickCPlus_QRCode_sample.ino

#include <M5StickCPlus.h>

const char* URL_STR = "https://www.google.co.jp/"; // GoogleのURL
const int QR_VERSION = 2;

void setup() {
  Serial.begin(115200);

  M5.begin();
  M5.Axp.ScreenBreath(10); // 画面の明るさ(7-12)
  M5.Lcd.setRotation(0);   // 画面を縦向きに(0-3)
  M5.Lcd.qrcode(URL_STR, 0, 0, 135, QR_VERSION); // QRコード表示

  Serial.println("setup end!");
}

void loop() {

}

 このサンプルスケッチを書き込むとM5StickCPlusの画面にQRコードが表示されます。
 そのQRコードスマホなどで読み込むとGoogleのホーム画面が表示されます。


 以上がM5StickC PlusでQRコードを表示する方法です。

 簡単に表示できるで、ちょっとした時の電子名刺代わりにM5Stack系を使用するなんて用途もありかもしれないですね。


おまけ:QRコードのバージョンについて
 QRコードはバージョンによってQRコード内に埋め込める情報量に違いがあります。
 例えばバージョン2だと最大で32バイト、バージョン4だと最大78バイトまで埋め込みができます。

 またバージョンが上がれば上がるほどQRコードのドットサイズが小さくなっていくので、バージョン15をM5StickCPlusに表示させてもQRコードを読み取れない可能性が高いです。

 なので必要な情報量を満たす丁度良いQRコードのバージョンを設定する必要があります。

 詳しくは以下のページ様を参考にしてください。


・参考資料

【M5StickC Plus/Arduino】M5StickC Plusで文字列スクロール

 最近M5StickC Plusをいじっています。

 今回は「文字列スクロール」をやってみたのでその備忘録です。

 では、始めます。


1:efontの導入
 今回も日本語表示のためにefontを使用します。

 まだefontを入れてない場合は、以下の記事にある「1:日本語フォントを導入する」と「2:M5StickC Plus用のファイルを作成する」を行ってください。


2:文字列スクロールのサンプル
 文字スクロールは以下のライブラリの「examples/Standard/」の中にある「LongTextScroll.ino」を参考にさせていただきました。


 実際に文字スクロールを行ったスケッチが以下になります。
(※2021/03/29:色々と間違っていたのでスケッチを修正しました)

・M5StickCPlus_efont_scroll.ino

#include <M5StickCPlus.h>
#include "efont.h"
#include "efontM5StickCPlus.h"
#include "efontEnableJaMini.h"

const uint16_t TEXT_COLOR    = WHITE; // 文字色
const uint16_t TEXT_BG_COLOR = GREEN; // 文字背景色
const uint8_t TEXT_SIZE = 7;          // 文字サイズの倍率

// M5StickC Plusの画面解像度は(135*240)
const int SCROLL_PIXEL = 8;      // 1回にスクロールするpixel数
const int DELAY_TIME   = 0;      // 1スクロールする時のディレイタイム(ミリ秒)
const int32_t SPRITE_POS_X = 0;  // スプライトを表示するX座標
const int32_t SPRITE_POS_Y = 11; // スプライトを表示するY座標
const uint16_t SPRITE_WIDTH_PIXEL  = 400;          // スプライトの横幅
const uint16_t SPRITE_HEIGHT_PIXEL = 16*TEXT_SIZE; // スプライトの高さ

// スクロールする文字列
static char TEXT_MESSAGE[] = "[start]メロスは激怒した。必ず、かの邪智暴虐(じゃちぼうぎゃく)の王を除かなければならぬと決意した。メロスには政治がわからぬ。メロスは、村の牧人である。笛を吹き、羊と遊んで暮して来た。けれども邪悪に対しては、人一倍に敏感であった。[end] ";
const int TEXT_ARRAY_MAX = sizeof(TEXT_MESSAGE) / sizeof(TEXT_MESSAGE[0]); // 文字列の配列数

TFT_eSprite tftSprite = TFT_eSprite(&M5.Lcd); // スプライト

// 文字幅とフォントの構造体
struct TextChar {
  int width;
  int height;
  byte font[32];
  char *str;
};

void setup() {
  Serial.begin(115200);

  M5.begin();
  M5.Axp.ScreenBreath(10); // 画面の明るさ(7-12)
  M5.Lcd.setRotation(3);   // 画面を横向きに(0-3)

  // フォントサイズ倍率と文字色、文字背景色を設定
  tftSprite.setTextSize(TEXT_SIZE);
  tftSprite.setTextColor(TEXT_COLOR, TEXT_BG_COLOR);

  // スプライト範囲の作成
  tftSprite.createSprite(SPRITE_WIDTH_PIXEL, SPRITE_HEIGHT_PIXEL);
  tftSprite.setCursor(0, 0); // カーソル移動

  showScrollChar(&tftSprite, TEXT_MESSAGE, 0); // スクロールを表示
  Serial.println("setup end!");
}

int cursorX = 0;  // スクロール位置
int char_num = 0; // 文字列の配列番号

void loop() {
  M5.update();
  
  tftSprite.pushSprite(SPRITE_POS_X, SPRITE_POS_Y);  // スプライト描画

  char *str = &(TEXT_MESSAGE[char_num]);
  
  // 一文字分の文字サイズを計算
  TextChar textChar = getCharWidthFont(str, tftSprite.textsize);
  int char_size = textChar.width;

  if (cursorX >= textChar.width) {
    // 半角全角判定して次の文字の配列番号を取得
    char_num = char_num + getNextchar_number(char_size, tftSprite.textsize);

    // 全部の文章が終わっていた場合
    if (char_num >= (TEXT_ARRAY_MAX-1)) {
      char_num = 0;
    }

    // スクロールを戻す
    tftSprite.scroll(char_size, 0);
    cursorX = cursorX - char_size;
    tftSprite.setCursor(0, 0); // カーソル移動

    // スクロール用文字列を表示
    showScrollChar(&tftSprite, TEXT_MESSAGE, char_num);
  }

  cursorX += SCROLL_PIXEL;
  tftSprite.scroll(-SCROLL_PIXEL, 0); // スクロールさせる
//  showData(char_num, TEXT_ARRAY_MAX); // 情報表示

  delay(DELAY_TIME);
}

// 情報を表示する関数
void showData(int char_num, int array_max_num) {
  M5.Lcd.setCursor(0,10);
  M5.Lcd.setTextColor(RED);
  M5.Lcd.print(" char_num:");
  M5.Lcd.println(char_num);
  M5.Lcd.print(" cursorX:");
  M5.Lcd.println(cursorX);
  M5.Lcd.print(" array_max_num:");
  M5.Lcd.println(array_max_num);
}

// 一文字分の大きさを取得する関数
TextChar getCharWidthFont(char* str, uint8_t textsize) {
  byte font[32];

  // フォント取得とchar*の次の位置を取得
  uint16_t strUTF16;
  char *strNext = efontUFT8toUTF16( &strUTF16, str );
  getefontData( font, strUTF16 );

  // 文字横幅
  int width = 16 * textsize;
  if( strUTF16 < 0x0100 ){
    width = 8 * textsize; // 半角
  }

  // 結果の構造体を返す
  TextChar textChar;
  textChar.width = width;
  textChar.height = 16 * textsize;
  memcpy(textChar.font, font, sizeof(font)); // フォント配列をコピー
  textChar.str = strNext;

  return textChar;
}

// 半角全角を判定して次の文字の配列位置にプラスする値を計算
int getNextchar_number(int char_size, int textSize) {
  int result;

  int font_size = char_size / tftSprite.textsize;
  if (font_size < 16) {
    result = 1; // 半角
  } else {
    result = 3; // 全角
  }

  return result;
}

// スクロール用文字列を表示
void showScrollChar(TFT_eSprite *sprite, char message[], int char_num) {
  printEfontScroll(sprite, message, char_num); // 現在位置の文字表示

  // スプライト範囲内を満たすまで先頭から繰り返す
  while (tftSprite.getCursorX() <= tftSprite.width()) {
    printEfontScroll(sprite, message, 0);
  }
}

// スクロール用の文字表示
void printEfontScroll(TFT_eSprite *sprite, char message[], int char_num) {
  int posX = sprite->getCursorX();
  int posY = sprite->getCursorY();
  uint8_t textsize = sprite->textsize;
  uint32_t textbgcolor = sprite->textbgcolor;

  // 現在の文字列位置のポインタ取得
  char *str = &(message[char_num]);

  while( *str != 0x00 ) {
    // 一文字分のフォントとサイズを取得
    TextChar textChar = getCharWidthFont(str, textsize);
    str = textChar.str;

    // 文字幅と高さを取得
    int width = textChar.width;
    int height = textChar.height;

    // 背景塗りつぶし
    sprite->fillRect(posX, posY, width, height, textbgcolor);

    // 一文字だけ表示して描画カーソルを進める
    displayOneChar(sprite, textChar.font, posX, posY);
    posX += width;

    // スプライトの範囲外まで行ったらループ抜ける
    if (posX > sprite->width()) {
      break;
    }
  }

  // カーソルを更新
  sprite->setCursor(posX, posY);
}

// 一文字だけ表示
void displayOneChar(TFT_eSprite *sprite, byte font[], int posX, int posY) {
  uint8_t textsize = sprite->textsize;
  uint32_t textcolor = sprite->textcolor;

  for (uint8_t row = 0; row < 16; row++) {
    word fontdata = font[row*2] * 256 + font[row*2+1];
    for (uint8_t col = 0; col < 16; col++) {
      if( (0x8000 >> col) & fontdata ){
        int drawX = posX + col * textsize;
        int drawY = posY + row * textsize;
        if( textsize == 1 ){
          sprite->drawPixel(drawX, drawY, textcolor);
        } else {
          sprite->fillRect(drawX, drawY, textsize, textsize, textcolor);
        }
      }
    }
  }
}

 このスケッチを書き込むと、TEXT_MESSAGEで設定した文字列がスクロールで表示されます。

 少し解説すると、画面よりも大きい範囲でスプライトを作成してそこに表示できるだけ文字列を表示し、スクロールして一文字消えたら次の文字を後ろに追加して表示させているというだけです。またスクロールが1週すると最初の文字からまた表示するようにもしています。


 以上がM5StickC Plusで文字列スクロールをした内容になります。

 M5StickC Plusの画面表示やスプライトについての解説もやりたいのですが、それも含めると長くなりそうなのでそれはまた別の機会に記事にしようかと思います。


・参考資料

【M5StickC Plus/Arduino】M5StickC Plusでおみくじを作ってみる

 以前の記事でM5StickC Plusの環境構築をしました。

 今回はそのM5StickC Plusを使って簡単な「おみくじ」を作ってみた備忘録になります。

 では、始めます。


1:日本語フォントを導入する
 おみくじとして「大吉」や「凶」といった文字を表示させたいのですが、ArduinoにもM5Stackのライブラリにも日本語のフォントはありません。
 なので日本語フォントを導入する必要があります。

 今回は以下のefontという日本語を含んだフォントを導入していきます。

 ちなみにこのフォントはソースコードの中に含まれているため、フォントデータを別途SDカードなどに保存する必要なく扱えるという利点があります(その分コンパイル後のサイズは大きくなります)。

 では実際にefontをArduino IDEに導入していきます。

 Arduino IDEを起動させてメニューの「スケッチ」→「ライブラリをインクルード」→「ライブラリの管理」を選択します。
f:id:rikoubou:20210315165534p:plain

 ライブラリマネージャの画面が表示されるので検索欄に「efont」と入力して出てきた「efont Unicode Font Data」を選択して「インストール」ボタンを押してインストールします。
f:id:rikoubou:20210315165821p:plain

 これでefontが導入できました。


2:M5StickC Plus用のファイルを作成する
 efontにはM5StickCには対応していますがM5StickC Plusには対応していないので、M5StickC Plus用のライブラリファイルを新たに作成します。

 先ほどインストールしたefontのライブラリがあるフォルダを開きます。デフォルトのArduino IDEの設定だと以下のパスに保存されているはずです。

C:\Users\[ユーザ名]\Documents\Arduino\libraries\efont_Unicode_Font_Data

「efont_Unicode_Font_Data」内にある「src」フォルダを開いたところにある「efontM5StickC.h」のファイルをコピーして「efontM5StickCPlus.h」という名前に変更します。
f:id:rikoubou:20210315170747p:plain

 新たに作成した「efontM5StickCPlus.h」をエディタで開き、6行目を以下のように修正します。

 // 変更前
#include <M5StickC.h>

 // 変更後
#include "M5StickCPlus.h"

 これで「efontM5StickCPlus.h」のファイルをincludeすることでM5StickC Plusでもefontを使えるようになります。


3:おみくじのスケッチを書き込む
 efontが使えるようになったので、実際におみくじのスケッチを書き込みます。

 おみくじのスケッチは以下の通りです。

・M5StickCPlus_text_omikuji.ino

/**
 * M5StickC Plusおみくじ
 */
#include <M5StickCPlus.h>
#include "efont.h"
#include "efontM5StickCPlus.h"
#include "efontEnableJaMini.h"

const int MAX_ARRAY = 7;
const uint16_t BG_COLOR = BLACK; // 画面の背景色

// 運勢の構造体
struct _unsei {
  char *str;
  int x;
  int y;
  int fontSize;
};

// 運勢の構造体配列(文字列, x座標, y座標, フォントサイズ倍率)
const struct _unsei OMIKUJI_ARRAY[MAX_ARRAY] = {
  {"大\n吉", 10, 10, 7},
  {"吉", 10, 60, 7},
  {"中\n吉", 10, 10, 7},
  {"小\n吉", 10, 10, 7},
  {"末\n吉", 10, 10, 7},
  {"凶", 10, 60, 7},
  {"大\n凶", 10, 10, 7}
};

bool btn_flg = false; // ボタンフラグ

void setup() {
  M5.begin();
  M5.Axp.ScreenBreath(10);           // 7-12で明るさ設定
  M5.Lcd.setRotation(0);             // 縦画面
  M5.Lcd.setTextColor(WHITE, BLACK); // 文字色を白、文字背景色を黒に設定
  M5.Lcd.fillScreen(BG_COLOR);       // 背景塗りつぶし
}

void loop() {
  M5.update();
  M5.Beep.update();

  // ボタンAが押された時
  if (M5.BtnA.wasPressed()) {
    ringBeep(3000, 100); // ブザーを鳴らす
    btn_flg = !btn_flg;
  }

  // btn_flgがfalseの時
  if (!btn_flg) {
    // 運勢をランダムに表示
    int number = random(0, MAX_ARRAY);
    showUnsei(number);
  }
}

// ブザーを鳴らす関数
void ringBeep(int pwm, int delaytime) {
  M5.Beep.tone(pwm, delaytime);
}

// 運勢を表示する関数
void showUnsei(int number) {
  M5.Lcd.fillScreen(BG_COLOR); // 背景塗りつぶし

  // 運勢を取り出して表示
  _unsei unsei = OMIKUJI_ARRAY[number];
  printEfont(unsei.str, unsei.x, unsei.y, unsei.fontSize);
}

 特に難しいことはやってないですが一応少し解説すると、大吉~大凶までの7種類を構造体の配列として持ち、乱数で0~6を発生させて「M5」と書かれたボタンが押されたところで表示を止めるという感じです。何となくボタンが押された時にブザーで音を鳴らすようにもしています。


 以上がM5StickC Plusでおみくじを作ってみた備忘録です。

 簡単ですがフォントの導入と文字の表示、ボタン制御、ブザーで音を鳴らすということをまとめたものができたので良かったです。
 今回おみくじを作ってみてM5StickC Plusはかなり色々できて楽しいことがわかったので、また何か面白いものを作ってみたいですね。


・参考資料

【M5StickC Plus/Windows】WindowsでのM5StickC Plusの環境構築

 M5StickC Plusという割と安価で色々なものがついているマイコンがあります。以下のようにスイッチサイエンスや秋月電子通商などで購入できます。

 かなり前になんとなくM5 StickC Plusを購入していたのですが、特に触らずに放置していました。いい加減いじってみようと思ったのでとりあえずWindowsの環境で扱えるようにしたまでの備忘録です。

 基本的には公式にある以下のページ通りに進めていきます。

 では、始めます。


1:ESP32が使える環境を構築する
 M5StickC PlusはESP32をベースとしたものが使われているので、まずはESP32の開発環境を構築する必要があります。

 以下のダウンロードページを開きます。

 今回はWindowsインストーラを使ってインストールしたいので、右側にある「Win 7 and newer」をクリックします。
f:id:rikoubou:20210305150520p:plain

 寄付するかどうかの画面が表示されますが、ただダウンロードするだけなので「JUST DOWNLOAD」をクリックします。
f:id:rikoubou:20210305150558p:plain

 インストーラがダウンロードされるのでインストーラに従ってインストールします。
(※古いバージョンのArduno IDEがインストールされている場合は、予めアンインストールしておく必要があります)

 インストールが終わったらArduino IDEを起動させ、メニューから「ファイル」→「環境設定」を選択します。
f:id:rikoubou:20210305151024p:plain

 以下のような画面が表示されるので「追加のボードマネージャのURL」と書かれた右側にあるアイコンをクリックします。
f:id:rikoubou:20210305151317p:plain

 ダイアログが表示されるので以下のURLを入力して「OK」ボタンをクリックします。

f:id:rikoubou:20210305151642p:plain

「追加のボードマネージャのURL」に先ほど追加したURLが表示されているのを確認してから「OK」ボタンをクリックします。
f:id:rikoubou:20210305151859p:plain

 次にメニューの「ツール」→「ボード」→「ボードマネージャ」を選択します。
f:id:rikoubou:20210305152219p:plain

 ボードマネージャの画面になるので検索欄に「esp32」と入力して出てきたライブラリをインストールします。インストールが終わったら「閉じる」ボタンをクリックして閉じます。
f:id:rikoubou:20210305152619p:plain

 これでESP32関係のボードが追加されて扱えるようになっています。
f:id:rikoubou:20210305152819p:plain


2:M5StickC Plusのライブラリをインストールする
 M5Stack系列のものであれば1の手順だけでほぼ使えるようになりますが、M5StickC Plusの場合はまた別にライブラリを入れる必要があります。

 以下のページを開きます。

 右上あたりにある「Code」をクリックして出てきた中の「Download ZIP」をクリックしてライブラリのzipファイルをダウンロードします。
f:id:rikoubou:20210305153233p:plain

 zipファイルをダウンロード出来たらメニューの「スケッチ」→「ライブラリをインクルード」→「ZIP形式のライブラリをインストール」をクリックします。
f:id:rikoubou:20210305153500p:plain

 ダウンロードしたzipファイルを選択して「開く」ボタンをクリックします。
f:id:rikoubou:20210305153723p:plain

 これでM5StickC Plusの環境が整いました。


3:M5StickC Plusのサンプルスケッチを動かす
 実際にサンプルスケッチを書き込んで動かしてみます。

 メニューの「ファイル」→「スケッチ例」→「互換性なし」→「M5StickCPlus」→「Games」→「Dices」を選択します。
f:id:rikoubou:20210305154345p:plain

 Dicesのサンプルスケッチが開きます。
f:id:rikoubou:20210305154625p:plain

 この状態でM5StickC PlusをPCに接続してメニューの「ツール」→「ボード」で「M5Stick-C」を選択します。
f:id:rikoubou:20210305155011p:plain

 続いてシリアルポートでM5StickC Plusのポートを指定します。
f:id:rikoubou:20210305155105p:plain

 書き込む準備ができたので左上にある書き込みボタンをクリックしてスケッチを書き込みます。
f:id:rikoubou:20210305155247p:plain

 スケッチがコンパイルされてボードへの書き込みが終了すると、M5StickC Plusの画面に「SHAKE ME」と表示されるので本体を振ると2個のサイコロの目が表示されるようになります。


 以上がWindowsでのM5StickC Plusの環境構築になります。

 液晶や加速度センサなど多機能なやつなので、これを使って色々面白いものを作ってみたいです。


・参考資料