M5Stack M5StickC Plus2動作確認 その2 基本機能

概要

前回は画面に特化しましたが、今回はM5StickC Plus2が搭載している機能の呼び出し方メインにまとめてみました。

LED

#include <M5Unified.h>

void setup(void) {
  auto cfg = M5.config();
  M5.begin(cfg);
  M5.delay(500); // Serial出力用のウエイト

  // 画面上にログを表示する
  M5.setLogDisplayIndex(0);
  M5.Display.setTextSize(4);
}

void loop(void) {
  static uint8_t led = 0;
  M5.delay(1);
  M5.update();
  if (M5.BtnA.isPressed()) {
    led++;
    M5.Display.setCursor(0, 0);
    M5.Log.printf("%3d\n", led);
    M5.Power.setLed(led); // 0:OFF 1-255:明るさ指定
    M5.delay(10);
  }
}

こちらはM5.Power.setLed()で明るさ指定をするだけです。ただしIRも同時に点灯してしまうので注意してください。点灯しっぱなしにすると本体が思ったより熱くなったので注意してください。

IR送信

#include <IRsend.h>  // IRremoteESP8266
#include <M5Unified.h>

IRsend *irsend;

void setup(void) {
  auto cfg = M5.config();
  M5.begin(cfg);
  M5.delay(500); // Serial出力用のウエイト

  // 画面上にログを表示する
  M5.setLogDisplayIndex(0);

  // IRピン設定
  uint8_t irPin = GPIO_NUM_MAX;
  bool irInverted = false;
  switch (M5.getBoard()) {
    case m5::board_t::board_M5StickCPlus:
      irPin = GPIO_NUM_9;
      irInverted = true; // PlusはLowで点灯するので反転
      break;
    case m5::board_t::board_M5StickCPlus2:
      irPin = GPIO_NUM_19;
      break;
  }
  M5.Log.printf("IR irPin = %d, irInverted = %d\n", irPin, irInverted);
  if (irPin != GPIO_NUM_MAX) {
    irsend = new IRsend(irPin, irInverted);
  }
}

void loop(void) {
  M5.delay(1);
  M5.update();
  if (M5.BtnA.wasPressed()) {
    // 送信
    if (irsend) {
      uint64_t send = irsend->encodeNEC(0x0000, 22);
      irsend->sendNEC(send);
      M5.Log.println("IR Send");
    }
  }
}

M5UnifiedはIR送信をサポートしていませんので、別ライブラリのIRremoteESP8266を利用して送信するのが一般的だと思います。

RTCセット

#define WIFI_SSID     "WIFI_SSID"
#define WIFI_PASSWORD "WIFI_PASSWORD"
#define NTP_TIMEZONE  "JST-9"           // https://github.com/esp8266/Arduino/blob/master/cores/esp8266/TZ.h
#define NTP_SERVER1   "0.pool.ntp.org"
#define NTP_SERVER2   "1.pool.ntp.org"
#define NTP_SERVER3   "2.pool.ntp.org"

#include <WiFi.h>
#include <sntp.h>
#include <M5Unified.h>

void setup(void) {
  auto cfg = M5.config();
  M5.begin(cfg);
  M5.delay(500); // Serial出力用のウエイト

  // 画面上にログを表示する
  M5.setLogDisplayIndex(0);

  // 非同期でNTPと時間合わせをする
  configTzTime(NTP_TIMEZONE, NTP_SERVER1, NTP_SERVER2, NTP_SERVER3);

  // WiFiに接続する
  M5.Log.print("WiFi:");
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);

  // WiFi接続待ち
  for (int i = 20; i && WiFi.status() != WL_CONNECTED; --i) {
    M5.Log.print(".");
    M5.delay(500);
  }
  M5.Log.println();

  if (WiFi.status() == WL_CONNECTED) {
    // WiFi接続完了
    M5.Log.println("WiFi Connected.");
    M5.Log.print("NTP:");

    while (sntp_get_sync_status() != SNTP_SYNC_STATUS_COMPLETED) {
      // NTP同期待ち
      M5.Log.print(".");
      delay(1000);
    }
    M5.Log.println("NTP Connected.");

    // ぴったり秒開始時点まで待機
    time_t t = time(nullptr) + 1;  // 1秒後を設定
    while (t > time(nullptr)) {
      // 時間待機
    }

    // RTCに時間を設定する
    M5.Rtc.setDateTime(localtime(&t));
  } else {
    // WiFi接続失敗
    M5.Log.println("WiFi none...");
  }

  M5.Display.clear();
}

void loop(void) {
  static constexpr const char* const wd[7] = { "Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat" };
  delay(500);

  // RTC時刻取得
  m5::rtc_datetime_t dt = M5.Rtc.getDateTime();
  M5.Display.setCursor(0, 0);
  M5.Log.printf("RTC  :%04d/%02d/%02d (%s)  %02d:%02d:%02d\r\n", 
    dt.date.year, dt.date.month, dt.date.date, wd[dt.date.weekDay],
    dt.time.hours, dt.time.minutes, dt.time.seconds);
}

Arduino 1.0.6以降はNTP同期を定期的に実行するSNTPが利用できるようになっています。

https://github.com/esp8266/Arduino/blob/master/cores/esp8266/TZ.h

タイムゾーンは上記などを参考にして設定してください。日本は「JST-9」で中国は「CST-8」などになります。また、RTCに保存する時刻にはタイムゾーンの設定ができないので、タイムゾーン無しのローカルタイムを設定しています。

RTC呼び出し

#include <M5Unified.h>

void setup(void) {
  auto cfg = M5.config();
  M5.begin(cfg);
  M5.delay(500);  // Serial出力用のウエイト

  // 画面上にログを表示する
  M5.setLogDisplayIndex(0);
}

void loop(void) {
  static constexpr const char* const wd[7] = { "Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat" };
  delay(500);

  // RTC時刻取得
  m5::rtc_datetime_t dt = M5.Rtc.getDateTime();
  M5.Display.setCursor(0, 0);
  M5.Log.printf("RTC  :%04d/%02d/%02d (%s)  %02d:%02d:%02d\n",
                dt.date.year, dt.date.month, dt.date.date, wd[dt.date.weekDay],
                dt.time.hours, dt.time.minutes, dt.time.seconds);

  // 起動時にESP32本体にRTCの時刻が自動設定されている
  auto t = time(nullptr);
  auto tm = localtime(&t);
  M5.Log.printf("ESP32:%04d/%02d/%02d (%s)  %02d:%02d:%02d\r\n",
                tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, wd[tm->tm_wday],
                tm->tm_hour, tm->tm_min, tm->tm_sec);
}

起動時に自動的にRTCの時刻をESP32本体に設定してくれています。そのためRTCから読み出すよりは本体のローカルタイムを取得する方法でもよいと思います。

ただし、NTP取得時に設定したタイムゾーンは反映されていませんので、内部的にはGMT0のタイムゾーン設定無しですが、設定されている時刻は日本などのローカル時間になります。

通常はこの設定で問題ないと思いますが、時差を厳密に処理したい場合にはNTPへのタイムゾーンをやめて、すべてGMT0で処理した方がよいと思います。

IMU

#include <M5Unified.h>

void setup(void) {
  auto cfg = M5.config();
  M5.begin(cfg);
  M5.delay(500);  // Serial出力用のウエイト

  // 画面上にログを表示する
  M5.setLogDisplayIndex(0);
}

void loop(void) {
  M5.delay(1);
  M5.update();

  auto imu_update = M5.Imu.update();
  if (imu_update) {
    m5::IMU_Class::imu_data_t data = M5.Imu.getImuData();
    M5.Display.setCursor(0, 0);
    M5.Log.printf("accel x = %10.5f\n", data.accel.x);
    M5.Log.printf("accel y = %10.5f\n", data.accel.y);
    M5.Log.printf("accel z = %10.5f\n", data.accel.z);
    M5.Log.printf("gyro  x = %10.5f\n", data.gyro.x);
    M5.Log.printf("gyro  y = %10.5f\n", data.gyro.y);
    M5.Log.printf("gyro  z = %10.5f\n", data.gyro.z);
    M5.Log.printf("mag   x = %10.5f\n", data.mag.x); // Plus2では取得できない
    M5.Log.printf("mag   y = %10.5f\n", data.mag.y); // Plus2では取得できない
    M5.Log.printf("mag   z = %10.5f\n", data.mag.z); // Plus2では取得できない
  }
}

従来どおりに取得可能です。搭載されているIMUは6軸ですので、方位の取得はできません。

ボタン

#include <M5Unified.h>

void setup(void) {
  auto cfg = M5.config();
  M5.begin(cfg);
  M5.delay(500); // Serial出力用のウエイト

  // 画面上にログを表示する
  M5.setLogDisplayIndex(0);
  M5.Display.setTextScroll(true);
}

void loop(void) {
  M5.delay(1);
  M5.update();

  if(M5.BtnA.wasPressed()){
    // 真ん中のボタンを押して離した
    M5.Log.println("BtnA wasPressed");
  }
  if(M5.BtnB.wasPressed()){
    // 右側のボタンを押して離した
    M5.Log.println("BtnB wasPressed");
  }
  if(M5.BtnPWR.wasPressed()){
    // 左側のボタンを押して離した
    // このボタンは6秒以上押すと電源オフになるので注意する
    M5.Log.println("BtnPWR wasPressed");
  }
}

こちらも従来と変わりません。左側のボタンは電源と共通ですので6秒以上押すと電源オフになるので注意してください。また、Plusだとショートとロングプレスしか判定できませんでしたが、Plus2の場合には通常のボタンと同じように利用可能です。ただし、非常に押しにくいので多用するのはおすすめしません。

ブザー

#include <M5Unified.h>

void setup(void) {
  auto cfg = M5.config();
  M5.begin(cfg);
  M5.delay(500);  // Serial出力用のウエイト

  // 画面上にログを表示する
  M5.setLogDisplayIndex(0);
  M5.Display.setTextScroll(true);
}

void loop(void) {
  M5.delay(1);
  M5.update();

  if (M5.BtnA.wasPressed()) {
    // 真ん中のボタンを押して離した
    M5.Speaker.tone(2000, 1000);  // 2000Hzを1000ms(1秒)
    M5.Log.println("BtnA TONE(2000Hz)");
  }
  if (M5.BtnB.wasPressed()) {
    // 右側のボタンを押して離した
    M5.Speaker.tone(4000, 1000);  // 4000Hzを1000ms(1秒)
    M5.Log.println("BtnB TONE(4000Hz)");
  }
  if (M5.BtnPWR.wasPressed()) {
    // 左側のボタンを押して離した
    for (int i = 0; i < 4; i++) {
      int frequency = 523.251 * pow(2, i);
      M5.Speaker.tone(frequency, 500);
      M5.Log.printf("BtnPWR TONE(C%d %dHz)\n", 5 + i, frequency);
      M5.delay(500);

      frequency = 587.330 * pow(2, i);
      M5.Speaker.tone(frequency, 500);
      M5.Log.printf("BtnPWR TONE(D%d %dHz)\n", 5 + i, frequency);
      M5.delay(500);

      frequency = 659.255 * pow(2, i);
      M5.Speaker.tone(frequency, 500);
      M5.Log.printf("BtnPWR TONE(E%d %dHz)\n", 5 + i, frequency);
      M5.delay(500);

      M5.delay(500);
    }
  }
}

M5.Speaker.tone()関数で音を出すことが可能です。しかしながらPlus2のブザーではあまり音質はよくありません。内部的にはきれいなサイン波をミキサー処理により出力している処理になっています。複数音を同時に再生することができるのですが、低い周波数はあまりきれいに聞こえません。

ブザー(Arduino標準tone関数)

#include <M5Unified.h>

void setup(void) {
  auto cfg = M5.config();
  M5.begin(cfg);
  M5.delay(500);  // Serial出力用のウエイト

  // 画面上にログを表示する
  M5.setLogDisplayIndex(0);
  M5.Display.setTextScroll(true);
}

void loop(void) {
  M5.delay(1);
  M5.update();

  if (M5.BtnA.wasPressed()) {
    // 真ん中のボタンを押して離した
    tone(GPIO_NUM_2, 2000, 1000);  // 2000Hzを1000ms(1秒)
    M5.Log.println("BtnA TONE(2000Hz)");
  }
  if (M5.BtnB.wasPressed()) {
    // 右側のボタンを押して離した
    tone(GPIO_NUM_2, 4000, 1000);  // 4000Hzを1000ms(1秒)
    M5.Log.println("BtnB TONE(4000Hz)");
  }
  if (M5.BtnPWR.wasPressed()) {
    // 左側のボタンを押して離した
    for (int i = 0; i < 4; i++) {
      int frequency = 523.251 * pow(2, i);
      tone(GPIO_NUM_2, frequency, 500);
      M5.Log.printf("BtnPWR TONE(C%d %dHz)\n", 5 + i, frequency);

      frequency = 587.330 * pow(2, i);
      tone(GPIO_NUM_2, frequency, 500);
      M5.Log.printf("BtnPWR TONE(D%d %dHz)\n", 5 + i, frequency);

      frequency = 659.255 * pow(2, i);
      tone(GPIO_NUM_2, frequency, 500);
      M5.Log.printf("BtnPWR TONE(E%d %dHz)\n", 5 + i, frequency);

      // 休符は周波数0で指定
      tone(GPIO_NUM_2, 0, 500);
    }
  }
}

Arduino標準のtone()関数を利用したサンプルです。単音しか鳴らすことができませんが、ブザーの場合にはこちらの方が音質がよいです。また、128個までの音を登録でき、順番に再生してくれます。複数音を登録すれば、曲などもかんたんに再生できるのですが現在途中で止めることができませんので注意してください。

マイク

#include <M5Unified.h>

static constexpr const size_t record_samplerate = 16000;  // 録音サンプリングレート
static constexpr const size_t record_length = 240;        // 録音数
static int16_t *rec_data;                                 // 録音データ

void setup(void) {
  auto cfg = M5.config();
  M5.begin(cfg);

  // 画面上にログを表示する
  M5.setLogDisplayIndex(0);

  // 画面を横にする
  M5.Display.setRotation(1);

  // 録音用メモリ確保
  rec_data = (typeof(rec_data))heap_caps_malloc(record_length * sizeof(int16_t), MALLOC_CAP_8BIT);
  memset(rec_data, 0, record_length * sizeof(int16_t));

  // マイク開始
  M5.Mic.begin();
}

void loop(void) {
  M5.delay(1);
  M5.update();

  if (M5.Mic.isEnabled()) {
    int16_t max = 0;

    // 描画開始
    M5.Display.startWrite();

    // 録音できているか確認
    if (M5.Mic.record(rec_data, record_length, record_samplerate)) {
      // 画面横幅より長いデータは横幅までに制限
      int32_t w = M5.Display.width();
      if (w > record_length - 1) {
        w = record_length - 1;
      }

      // 画面上に描画
      for (int32_t x = 0; x < w; ++x) {
        static int16_t prev_y[record_length];  // 前回座標を保存
        static int16_t prev_h[record_length];  // 前回座標を保存

        // 前回の線を消す
        M5.Display.writeFastVLine(x, prev_y[x], prev_h[x], TFT_BLACK);

        // 音の大きさをshiftで小さくして、画面上の高さに変換
        static constexpr int shift = 6;
        int32_t y1 = (rec_data[x] >> shift);
        int32_t y2 = (rec_data[x + 1] >> shift);
        if (y1 > y2) {
          int32_t tmp = y1;
          y1 = y2;
          y2 = tmp;
        }

        // 画面中央からの相対座標に変換
        int32_t y = (M5.Display.height() >> 1) + y1;
        int32_t h = (M5.Display.height() >> 1) + y2 + 1 - y;
        prev_y[x] = y;
        prev_h[x] = h;

        // 描画
        M5.Display.writeFastVLine(x, y, h, TFT_WHITE);

        // 最大音量確認
        if (max < abs(rec_data[x])) {
          max = abs(rec_data[x]);
        }
      }
    }

    // 最大音量表示
    M5.Display.setCursor(0, 0);
    M5.Log.printf("MAX = %5d\n", max);

    // 描画終わり
    M5.Display.endWrite();
  }
}

環境音センサーとして利用した例になります。一定以上大きな音を検知するなどの場合には比較的かんたんに利用可能です。

シリアルプロッタなどで確認をすると、音の大きさに応じて数値が取れているのがわかると思います。

バッテリー残量取得

#include <M5Unified.h>

void setup(void) {
  auto cfg = M5.config();
  M5.begin(cfg);
  M5.delay(500); // Serial出力用のウエイト

  // 画面上にログを表示する
  M5.setLogDisplayIndex(0);
  M5.Display.setTextScroll(true);

  // 画面を横にする
  M5.Display.setRotation(1);

  // 消費電力アップのためにLEDを付ける
  M5.Power.setLed(255);
}

void loop(void) {
  M5.delay(1000);
  M5.update();

  M5.Log.printf("Battery Level = %3d %%, Voltage = %4d mV\n", M5.Power.getBatteryLevel(), M5.Power.getBatteryVoltage());
}

AXP192がなくなったことで、充電中の取得が取れなくなっています。バッテリー残量は従来どおり取得が可能でした。

電源オフ

#include <M5Unified.h>

void setup(void) {
  auto cfg = M5.config();
  M5.begin(cfg);
  M5.Display.clear(TFT_RED);
}

void loop(void) {
  M5.delay(1);
  M5.update();
  if (M5.BtnA.wasPressed()) {
    M5.Power.powerOff();
  }
}

電源オフも従来と同じ方法です。ただし、USB接続中には電源が切れなくなっているか、おなしな状態になります。そして、USB接続中かを判定することができないので非常に面倒です。

秒タイマー起動

#include <M5Unified.h>

void setup(void) {
  auto cfg = M5.config();
  M5.begin(cfg);
  M5.Display.clear(TFT_RED);
}

void loop(void) {
  M5.delay(1);
  M5.update();
  if (M5.BtnA.wasPressed()) {
    // 5秒間スリープ
    M5.Power.timerSleep(5);

    // ここは実行されずにリセットされて起動する
    M5.Display.clear(TFT_BLUE);
  }
}

秒数指定して電源オフにしてスリープするモードです。こちらもUSB接続中は電源オフにならないので、擬似的にESP32側のスリープをしている場合があります。

時間指定起動

#include <M5Unified.h>

void setup(void) {
  auto cfg = M5.config();
  M5.begin(cfg);
  M5.Display.clear(TFT_RED);
  M5.delay(500); // Serial出力用のウエイト

  // 画面上にログを表示する
  M5.setLogDisplayIndex(0);
}

void loop(void) {
  M5.delay(1);
  M5.update();

  m5::rtc_datetime_t dt = M5.Rtc.getDateTime();
  M5.Display.setCursor(0, 0);
  M5.Log.printf("%04d/%02d/%02d %02d:%02d:%02d\n", 
    dt.date.year, dt.date.month, dt.date.date,
    dt.time.hours, dt.time.minutes, dt.time.seconds);

  if (M5.BtnA.wasPressed()) {
    m5::rtc_time_t time;

    // 時間指定なし
    time.hours = -1; // -1だと時間指定無し

    // 1分後をセット(毎時0分に起動の場合には0を指定)
    time.minutes = (dt.time.minutes + 1) % 60;

    // 現在時刻と起動時間表示
    M5.Log.printf("NOW   %02d:%02d:%02d\n", dt.time.hours, dt.time.minutes, dt.time.seconds);
    M5.Log.printf("TIMER %02d:%02d:00\n", time.hours, time.minutes);

    // USB接続中の場合にはタイマーが動作せずに、電源ボタンで復帰する
    esp_sleep_enable_ext0_wakeup(GPIO_NUM_35, false);

    // 指定時間までスリープ
    M5.Power.timerSleep(time);

    // ここは実行されずにリセットされて起動する
    M5.Display.clear(TFT_BLUE);
  }
}

RTCの時刻設定で起動することも可能です。ただし、毎時0分に起動などはかんたんなのですが、5分間隔に正確に起動する場合などはちょっと指定が面倒です。

USB接続時には擬似的にスリープしますが、RTCの通知を受け取れないために復帰することができません。基本的に非常に使いにくいモードのために、Plus2では使わないほうがよいと思います。

まとめ

いろいろ機能を確認してみましたが、USB接続中には電源オフにならない仕様のため、RTC通知まわりがかなりわかりにくいです。PlusだとRTCの通知がGPIOで取得可能だったため、スリープからの復帰などに利用可能でしたがPlus2だと完全にアクセスできない場所に分離されています。

RTCの通知か、USB給電中かを判定できればもう少しきれいな動作になりそうだったのですが残念です。ただUSB給電中かは継続的にバッテリー電圧を監視することで、擬似的には判定可能なのでもう少し研究してみたいと思います。

コメント

  1. 匿名 より:

    こんにちは。tone()を使う「Arduino標準のtone()関数を利用したサンプル」を試してみましたがtone()が定義されていないというコンパイルエラーになります。#include を追加しても駄目でした。何か足りないのでしょうか?

    • たなかまさゆき より:

      Arduino IDEを使っているのであればESP32のボードマネージャのバージョンが古いのではないでしょうか?

      標準のesp32 by Espressifだと2.0.14が最新です
      M5Stack版だと2.1.1が最新になります

      どちらも2.0.3以降であれば利用可能なはずです
      ボードマネージャーからバージョンアップできないか試してみてください

  2. 匿名 より:

    なんとその通りでした! Updateして動きました。はいarduino環境でした。ありがとうございました。いまPLUS2に移植作業をしていてブザー音が良く聞こえなくなってしまい困っていたのですが助かりました。ありがとうございます。

  3. 匿名 より:

    たびたびすみません質問した者です。新たな問題が発生しました。esp32 by Espressifをボードマネージャで出て来る最新の2.0.11にしたらtone()が使えるようになったのですが、M5StickC PLUS2が特定の2つほどのルーチン(1つのプログラム内)で異常に遅くなりました。遅すぎてキー入力がうまく拾えないです。tone()は無関係のようです(書き換えて使わなくしても遅いままです)。そしてesp32 by Espressifを1.0.6に戻したら元通り速くなりました。しかしtone()は使えません。遅い原因はわかりません。何らかの命令によって遅くなるような気がしますが特定できていません。2.0.14はボードマネージャで出てこないようです。

    • たなかまさゆき より:

      んー、なかなか難しいですね

      https://lang-ship.com/blog/work/m5stickc-esp32-arduino-l05-buzzer/#toc7

      上記のPWMを自分で使う方法であれば古いバージョンで音がなれせると思います

      #define SPEAKER_PIN GPIO_NUM_2
      #define SPEAKER_CH 1

      void setup()
      {
      // 初期化
      pinMode(SPEAKER_PIN, OUTPUT);
      ledcSetup(SPEAKER_CH, 12000, 8);
      ledcAttachPin(SPEAKER_PIN, SPEAKER_CH);
      }
      void loop()
      {
      // 1200Hzの周波数を出力
      ledcWriteTone(SPEAKER_CH, 1200);

      // 0.5秒待機
      delay(500);

      // 音を止める
      ledcWriteTone(SPEAKER_CH, 0);

      // 音の間隔を1秒あける
      delay(1000);
      }

      • 匿名 より:

        ありがとうございます、いろいろやってみます。esp32 by Espressifを2.0.14にしても駄目でした。そしてライブラリ周りを色々やっていたら、1.0.6に戻しても遅いままになってしまいました。ちょっとはまっています。すべての動作が遅いわけではないのでよくわかりません。ライブラリの仕様変更で特定の命令が遅くなったのかもしれません。何かわかりましたらお知らせします。

  4. 匿名 より:

    追伸:少しわかりました。M5.BtnA.wasPressed() が全く機能しないようです。コンパイルエラーは出ません。M5.BtnA.isPressed()は機能します。しかしキーバッファは見ないので、速いループでM5.BtnA.isPressed()を頻繁に呼び出していないと拾えません。また、取りこぼしが出ます。ヘッダは でもでも同じでした(どちらか1つ)。シンプルなコードを作ってテストしました。

  5. 匿名 より:

    詳細がわかりました。M5.update()を2回連続して実行すると、その後のM5.BtnA.wasPressed()が全く反応しなくなります。ずっと押していても拾わなくなります。M5.BtnA.isPressed()は機能します(バッファリングはしません)。サブルーチンに飛んで再びM5.update()を実行する場合があるので拾えなかったようです。M5.update()の2回連続、を避けるとwasPressed()はisPressed()と同じくバッファリングなしでなら動作します。よってキーバッファを利用したセンスができなくなりました。

    • たなかまさゆき より:

      M5.update()の中でボタンの状態取得をしています
      そのときに前回からの差分を判定してwasPressed系の取得をしていますので、2回連続実行すると差分がなくなって常にfalseになってしまいます

      M5.update()はloopの先頭で1度のみ呼び出すことをおすすめします
      たしかM5Unified.hではなくて、古いライブラリの場合にはM5.BtnA.wasPressed()を呼び出すとフラグがfalseになる仕様でしたが、M5Unified.hだとM5.update()で処理していますので若干動きが異なるはずです

  6. 匿名 より:

    ledcWriteTone()でも音が鳴りました、ありがとうございました。