Arduino入門

2. デバイス&サンプル

5. 拡張モジュール&サンプル

アクセサリー

6. アプリケーション

CoreMatrix M5PM1 & M5IOE1 電源管理

1. 多段階電源スイッチ設計

CoreMatrix は Stamp Timer Power2(M5PM1)と M5IOE1 を内蔵し、ハードウェア回路と組み合わせて多段階の電源スイッチを実現しています。各段階は、コントローラーの動作状態と周辺機器・インターフェースへの給電状態に対応します。動作要件に応じてコントローラーの状態や電源イネーブルを切り替え、使用していない周辺機器を停止してシステム全体の消費電力を抑えられます。

M5PM1 / M5IOE1 ドライバーライブラリ

M5PM1 / M5IOE1 ドライバーライブラリを使用すると、低消費電力時のウェイクアップや周辺機器の電源制御に使う各ピンの機能を簡単に設定できます。

注意事項
CoreMatrix の多段電源スイッチ設計は直列構成ではありません。L1 ~ L3B スイッチの電源入力は、前段の電源ではなく、すべて L0 (SYS_VBUS) を供給元としています。このため、各電源レベルを独立して制御できます。各レベルは周辺機器の電源供給と消費電力に基づいて区分されています。M5PM1 の起動後、L1、L3A は自動的に有効になります。M5Unified の初期化処理では、さらに M5IOE1 を制御して L3B を有効にし、その他の周辺機器への電源供給またはイネーブル信号を有効にします。

L0

この電源段階では、バッテリーから M5PM1 への給電が維持されます。バッテリー残量がある限り、この段階の電源は維持され、M5PM1 は基本的なボタンによる電源オン / オフ操作をサポートします。

また、L0 ドメインには M5PM1 の EXT_5V ブースト出力(5V_INOUT)も含まれます。これは M5-Bus に 5V を供給し、変換後に M5BUS_3V3_L0 を生成します。この出力は M5PM1 の EXT_5V_EN で個別に制御され、デフォルトではオフで、M5Unified の初期化時に自動的にオンになります。L1 ~ L3B の段階的なスイッチ制御とは連動しません。

出力をオフにすると、5V_INOUT は外部 5V 入力として使用できます。出力をオンにする場合は、M5-Bus の 5V ピンから逆供給しないでください。

pm1.setBoostEnable(true);  // M5-Bus 5V + M5-Bus 3V3 ON
pm1.setBoostEnable(false); // M5-Bus 5V + M5-Bus 3V3 OFF

L1

この電源段階では、IMU、KEY1~KEY3 ボタンのプルアップ、および関連ロジックへの給電が有効になります。この段階の電源は M5PM1 の 3V3_L1 を使用し、以下の API でオン / オフを制御できます。

pm1.setLdoEnable(true); // L1 ON
pm1.setLdoEnable(false); // L1 OFF

また、BMI270 の INT1 は M5PM1 G4 に、KEY1~KEY3 はそれぞれ M5PM1 G0~G2 に接続されています。IMU またはボタンで M5PM1 をウェイクアップする場合は、L1 への給電を維持したうえで M5PM1 をスリープさせます。

pm1.setLdoEnable(true);
pm1.ldoSetPowerHold(true);
pm1.setLedEnLevel(true);
pm1.shutdown();

L3A

この電源段階では、ESP32-C61、M5IOE1、I2C プルアップ、およびコントローラー関連の周辺機器への給電が有効になります。この段階では M5PM1 の DC-DC 出力 3V3_L3A を使用し、以下の API でオン / オフを制御できます。

pm1.setDcdcEnable(true); // L3A ON
pm1.setDcdcEnable(false); // L3A OFF

L3B

L3B 段階では LED マトリクス、microSD、ブザー、Grove などの周辺機器への給電や関連設定を制御します。M5IOE1 I/O 拡張チップによって制御され、この段階に属する一部の周辺機器を個別に操作できます。

M5IOE1 PYG1 PYG2 PYG3 PYG4 PYG5 PYG8 PYG10 PYG11
microSD PYB_TF_EN PYB_TFCARD_DET
Buzzer PYB_BUZZER_EN PYB_BUZZER_PWM
Charge PYB_CHG_PROG PYB_CHG_STAT
LED Matrix PYB_LEDS_EN
Grove 5V PYB_GROVE_EN
  • PYB_TF_EN: microSD TF_3V3_L3B 電源イネーブル
  • PYB_BUZZER_EN: ブザー BUZZER_3V3_L3B 電源イネーブル
  • PYB_CHG_PROG: 充電電流設定の制御
  • PYB_LEDS_EN: LED マトリクス LEDS_3V3_L3B 電源イネーブル
  • PYB_GROVE_EN: Grove 5V 出力イネーブル
  • PYB_CHG_STAT: 充電状態入力。Low は充電中を示します
  • PYB_TFCARD_DET: microSD 検出。Low はカード検出を示します
  • PYB_BUZZER_PWM: ブザー PWM 駆動。チャンネル M5IOE1_PWM_CH3(インデックス 2)

周辺機器への給電は、以下の M5IOE1 API で個別に制御できます。

ioe1.pinMode(M5IOE1_PIN_4, OUTPUT);
ioe1.digitalWrite(M5IOE1_PIN_4, HIGH);

Grove インターフェースの 5V 出力は PYB_GROVE_EN(PYG5)で制御します。M5Unified の cfg.output_power はデフォルトで true のため、M5.begin() の実行後は Grove 5V がすでにオンになっています。以下の API で制御できます。

M5.Power.setExtOutput(true);  // 打开 Grove 5V
M5.Power.setExtOutput(false); // 关闭 Grove 5V

2. M5PM1 スリープ

手動スリープ

プログラムから M5PM1 を手動でスリープ状態にして、システム全体の消費電力を抑えられます。デフォルトでは、スリープを直接設定すると低消費電力の給電状態に戻り、M5PM1 のみが給電状態を維持します。

pm1.shutdown();

IMU やボタンのプルアップなど L1 の回路を維持する必要がある場合は、M5PM1 ドライバーライブラリで L1 の電源保持と対応するウェイクアップソースを設定してから、pm1.shutdown() を実行してください。

I2C アイドル時のスリープ

M5PM1 は I2C がアイドル状態のときに自動でスリープするよう設定できます。スリープに入った後、ESP32 からの最初の通信は M5PM1 の復帰に使われるため、有効な通信は次の通信で行われます。

m5pm1_err_t setI2cSleepTime(uint8_t seconds);

3. M5PM1 タイマー

M5PM1 はタイマー機能を設定できます。カウントダウン終了後に、電源オン、電源オフ、リセットなどの操作を実行できます。

m5pm1_err_t timerSet(uint32_t seconds, m5pm1_tim_action_t action);
typedef enum {
    M5PM1_TIM_ACTION_STOP = 0b000,     // 停止,无动作
                                       // Stop, no action
    M5PM1_TIM_ACTION_FLAG = 0b001,     // 仅设置标志
                                       // Set flag only
    M5PM1_TIM_ACTION_REBOOT = 0b010,   // 系统复位
                                       // System reboot
    M5PM1_TIM_ACTION_POWERON = 0b011,  // 开机
                                       // Power on
    M5PM1_TIM_ACTION_POWEROFF = 0b100  // 关机
                                       // Power off
} m5pm1_tim_action_t;

タイマーによる電源オン / 電源オフの例

例: KEY1 を押すと 3 秒後に自動で電源が入り、その直後に電源がオフになるよう設定されます。KEY2 を押すと 5 秒後に電源がオフになるよう設定されます。電源オフ後は PWR_BTN を 1 回押すと再び電源を入れられます。

cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
#include <M5Unified.h>
#include <M5PM1.h>

M5PM1 pm1;

static void drawState(uint8_t state)
{
  M5.Display.startWrite();
  M5.Display.fillScreen(TFT_BLACK);
  M5.Display.drawRect(0, 0, 16, 16, TFT_WHITE);

  if (state == 0) {
    M5.Display.fillRect(7, 7, 2, 2, TFT_WHITE);
  } else if (state == 1) {
    M5.Display.fillRect(3, 3, 4, 4, TFT_WHITE);
  } else {
    M5.Display.fillRect(9, 9, 4, 4, TFT_WHITE);
  }

  M5.Display.endWrite();
  M5.Display.display();
}

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

  M5.Display.setColorDepth(8);
  M5.Display.setRotation(1);
  M5.Display.setBrightness(128);

  const m5pm1_err_t err = pm1.begin(&M5.In_I2C, M5PM1_DEFAULT_ADDR, M5PM1_I2C_FREQ_100K);
  if (err != M5PM1_OK) {
    Serial.printf("M5PM1 init failed: %d\n", err);
    while (true) delay(1000);
  }

  Serial.println("KEY1: power on after 3s");
  Serial.println("KEY2: power off after 5s");
  drawState(0);
}

void loop()
{
  M5.update();

  if (M5.BtnA.wasClicked()) {
    Serial.println("Set power-on timer: 3s");
    drawState(1);
    delay(200);
    pm1.timerSet(3, M5PM1_TIM_ACTION_POWERON);
    pm1.shutdown();
  }

  if (M5.BtnB.wasClicked()) {
    Serial.println("Set power-off timer: 5s");
    drawState(2);
    pm1.timerSet(5, M5PM1_TIM_ACTION_POWEROFF);
  }

  delay(20);
}

4. IMU ウェイクアップ

BMI270 ドライバーライブラリ

IMU による M5PM1 のウェイクアップ

電源が L1 モードに切り替わると、CoreMatrix の IMU、KEY1~KEY3 ボタンのプルアップ、および M5PM1 が給電状態を維持します。IMU ウェイクアップ機能を設定すると、M5PM1 はスリープに入り、BMI270 の動作に必要な L1 出力電源(3V3_L1_EN)を維持します。

この状態でデバイスを裏返したり動かしたりすると、BMI270 の INT1 が Low を出力し、M5PM1 がウェイクアップして再起動します。

M5PM1 が復帰すると、L0、L1、L3A の電源投入シーケンスを再実行し、ESP32-C61 は初期化をやり直します。

例: 起動後にボタン A(KEY1)を 1 回押すと、IMU 割り込みモードと M5PM1 の L1 電源保持が設定され、M5PM1 はスリープに入ります。この状態でデバイスを裏返したり動かしたりすると M5PM1 がウェイクアップし、ESP32-C61 に再給電されます。

cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
#include <M5Unified.h>
#include <M5PM1.h>
#include <Wire.h>
#include "SparkFun_BMI270_Arduino_Library.h"

BMI270 imu;
M5PM1 pm1;

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

    Wire.setPins(M5.getPin(m5::pin_name_t::in_i2c_sda), M5.getPin(m5::pin_name_t::in_i2c_scl));

    // Initialize PM1
    m5pm1_err_t err = pm1.begin(&M5.In_I2C, M5PM1_DEFAULT_ADDR, M5PM1_I2C_FREQ_100K);

    if (err == M5PM1_OK) {
        Serial.println("PM1 initialization successful");
        pm1.gpioSetWakeEnable(M5PM1_GPIO_NUM_4, true);
        pm1.gpioSetWakeEdge(M5PM1_GPIO_NUM_4, M5PM1_GPIO_WAKE_FALLING);  // Falling edge
    } else {
        Serial.printf("PM1 initialization failed, error code: %d\n", err);
    }

    // Check if sensor is connected and initialize
    // Address defaults to 0x68
    while(imu.beginI2C(BMI2_I2C_PRIM_ADDR, Wire) != BMI2_OK)
    {
        Serial.println("Error: BMI270 not connected, check wiring and I2C address!");
        delay(1000);
    }
    imu.disableFeature(BMI2_ANY_MOTION);
    Serial.println("BMI270 initialization successful");

    M5.Display.setColorDepth(8);
    M5.Display.setRotation(1);
    M5.Display.setBrightness(128);
    M5.Display.fillScreen(TFT_BLACK);
    M5.Display.drawRect(0, 0, 16, 16, TFT_WHITE);
    M5.Display.fillRect(7, 7, 2, 2, TFT_WHITE);
    M5.Display.display();
}

void loop(void)
{
    M5.update();
    if (M5.BtnA.wasPressed()) {
        int8_t ret = imu.enableFeature(BMI2_ANY_MOTION);
        // Optional
        // bmi2_sens_config config;
        // config.type = BMI2_ANY_MOTION;
        // config.cfg.any_motion.threshold = 0xA0;  // 1LSB equals to 0.48mg. Default is 83mg. Lower is more sensitive
        // config.cfg.any_motion.duration = 0x0A;   // 1LSB equals 20ms. Default is 100ms.
        // ret |= imu.setConfig(config);
        //
        bmi2_int_pin_config intPinConfig;
        intPinConfig.pin_type = BMI2_INT1;
        intPinConfig.int_latch = BMI2_INT_NON_LATCH;
        intPinConfig.pin_cfg[0].lvl = BMI2_INT_ACTIVE_LOW;
        intPinConfig.pin_cfg[0].od = BMI2_INT_PUSH_PULL;
        intPinConfig.pin_cfg[0].output_en = BMI2_INT_OUTPUT_ENABLE;
        intPinConfig.pin_cfg[0].input_en = BMI2_INT_INPUT_DISABLE;
        ret |= imu.setInterruptPinConfig(intPinConfig);
        ret |= imu.mapInterruptToPin(BMI2_ANY_MOTION_INT, BMI2_INT1);
        if (!ret) {
            Serial.println("BMI270 AnyMotionInterrupt enabled successfully");
        } else {
            Serial.println("Failed to enable BMI270 AnyMotionInterrupt");
        }

        M5.Display.fillScreen(TFT_BLACK);
        M5.Display.drawRect(0, 0, 16, 16, TFT_WHITE);
        M5.Display.fillRect(5, 5, 6, 6, TFT_WHITE);
        M5.Display.display();
        delay(1000);
        // Shutdown
        pm1.setLdoEnable(true);
        pm1.ldoSetPowerHold(true);
        pm1.setLedEnLevel(true);
        pm1.shutdown();
    }
}

サンプル動作デモ:

IMU による ESP32-C61 のウェイクアップ

M5PM1 の G3 IRQ ピンは、回路上で ESP32-C61 の G2 に接続されており、連鎖的なウェイクアップ信号によって ESP32-C61 をウェイクアップできます。手順は次の通りです。

  1. M5PM1 G4 を入力モードに設定します。このピンは BMI270 の INT1 出力信号に接続されています。
  2. M5PM1 G3 を IRQ 出力信号ピンに設定します。G4(BMI270 の割り込み信号)の状態が変化すると、G3 IRQ ピンが割り込み信号を出力します。
  3. ESP32-C61 の G2 を立ち下がりエッジ割り込み入力に設定し、M5PM1 G3 IRQ を検出します。実際のディープスリープのコードは、サンプル内ではコメントアウトされたままです。
  4. デバイスを裏返したり動かしたりすると、BMI270 のウェイクアップ信号 -> M5PM1 G3 IRQ -> ESP32-C61 G2 の割り込みハンドラーが順にトリガーされ、CoreMatrix のマトリクス表示が変化します。

例: 起動後にボタン A(KEY1)を 1 回押すと、IMU 割り込みモードが設定され、ESP32-C61 G2 の立ち下がりエッジ割り込みが登録されます。デバイスを裏返したり動かしたりすると GPIO 割り込みハンドラーが起動し、マトリクスの表示が待機状態からトリガー済み状態に変わります。もう一度ボタン A を押すと M5PM1 IRQ フラグがクリアされ、再度テストできます。コード内でコメントアウトされているディープスリープ部分を有効にすると、動作後に ESP32-C61 が復帰し、setup() から再実行されます。

cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
#include <M5Unified.h>
#include <M5PM1.h>
#include <Wire.h>
#include "SparkFun_BMI270_Arduino_Library.h"
#include "driver/rtc_io.h"

BMI270 imu;
M5PM1 pm1;

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

    Wire.setPins(M5.getPin(m5::pin_name_t::in_i2c_sda), M5.getPin(m5::pin_name_t::in_i2c_scl));

    // Initialize PM1
    m5pm1_err_t err = pm1.begin(&M5.In_I2C, M5PM1_DEFAULT_ADDR, M5PM1_I2C_FREQ_100K);

    if (err == M5PM1_OK) {
        Serial.println("PM1 initialization successful");
        pm1.irqClearGpioAll();
        pm1.irqClearSysAll();
        pm1.irqClearBtnAll();

        pm1.irqSetGpioMaskAll(M5PM1_IRQ_MASK_ENABLE);
        pm1.irqSetSysMaskAll(M5PM1_IRQ_MASK_ENABLE);
        pm1.irqSetBtnMaskAll(M5PM1_IRQ_MASK_ENABLE);

        pm1.irqSetGpioMask(M5PM1_IRQ_GPIO4, M5PM1_IRQ_MASK_DISABLE);
        pm1.gpioSetMode(M5PM1_GPIO_NUM_4, M5PM1_GPIO_MODE_INPUT);
        pm1.gpioSetPull(M5PM1_GPIO_NUM_4, M5PM1_GPIO_PULL_UP);

        pm1.gpioSetMode(M5PM1_GPIO_NUM_3, M5PM1_GPIO_MODE_OUTPUT);
        pm1.gpioSetDrive(M5PM1_GPIO_NUM_3, M5PM1_GPIO_DRIVE_PUSHPULL);
        pm1.gpioSetPull(M5PM1_GPIO_NUM_3, M5PM1_GPIO_PULL_UP);
        pm1.gpioSetOutput(M5PM1_GPIO_NUM_3, true);
        pm1.gpioSetFunc(M5PM1_GPIO_NUM_3, M5PM1_GPIO_FUNC_IRQ);
    } else {
        Serial.printf("PM1 initialization failed, error code: %d\n", err);
    }

    // Check if sensor is connected and initialize
    // Address defaults to 0x68
    while(imu.beginI2C(BMI2_I2C_PRIM_ADDR, Wire) != BMI2_OK)
    {
        Serial.println("Error: BMI270 not connected, check wiring and I2C address!");
        delay(1000);
    }
    imu.disableFeature(BMI2_ANY_MOTION);
    Serial.println("BMI270 initialization successful");

    M5.Display.setColorDepth(8);
    M5.Display.setRotation(1);
    M5.Display.setBrightness(128);
    M5.Display.fillScreen(TFT_BLACK);
    M5.Display.drawRect(0, 0, 16, 16, TFT_WHITE);
    M5.Display.fillRect(7, 7, 2, 2, TFT_WHITE);
    M5.Display.display();
}

volatile bool pm1IrqTriggered = false;

void ARDUINO_ISR_ATTR pm1_irq_handler()
{
    pm1IrqTriggered = true;
}

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

    if (pm1IrqTriggered) {
        pm1IrqTriggered = false;

        uint16_t status = 0;
        imu.getInterruptStatus(&status);
        Serial.printf("BMI270 interrupt status: 0x%04X\n", status);

        M5.Display.fillScreen(TFT_BLACK);
        M5.Display.drawRect(0, 0, 16, 16, TFT_WHITE);
        M5.Display.fillRect(4, 4, 8, 8, TFT_WHITE);
        M5.Display.display();
    }

    if (M5.BtnA.wasPressed()) {
        pm1.irqClearGpioAll();
        pm1.irqClearSysAll();
        pm1.irqClearBtnAll();
        int8_t ret = imu.enableFeature(BMI2_ANY_MOTION);
        // Optional
        // bmi2_sens_config config;
        // config.type = BMI2_ANY_MOTION;
        // ret |= imu.getConfig(&config);
        // config.cfg.any_motion.threshold = 0xE0;  // 1LSB equals to 0.48mg. Default is 83mg. Lower is more sensitive
        // config.cfg.any_motion.duration = 0x0A;   // 1LSB equals 20ms. Default is 100ms.
        // ret |= imu.setConfig(config);

        bmi2_int_pin_config intPinConfig;
        intPinConfig.pin_type = BMI2_INT1;
        intPinConfig.int_latch = BMI2_INT_NON_LATCH;
        intPinConfig.pin_cfg[0].lvl = BMI2_INT_ACTIVE_LOW;  // Active low
        intPinConfig.pin_cfg[0].od = BMI2_INT_PUSH_PULL;
        intPinConfig.pin_cfg[0].output_en = BMI2_INT_OUTPUT_ENABLE;
        intPinConfig.pin_cfg[0].input_en = BMI2_INT_INPUT_DISABLE;
        ret |= imu.setInterruptPinConfig(intPinConfig);
        ret |= imu.mapInterruptToPin(BMI2_ANY_MOTION_INT, BMI2_INT1);
        if (!ret) {
            Serial.println("BMI270 AnyMotionInterrupt enabled successfully");
        } else {
            Serial.println("Failed to enable BMI270 AnyMotionInterrupt");
        }

        M5.Display.fillScreen(TFT_BLACK);
        M5.Display.drawRect(0, 0, 16, 16, TFT_WHITE);
        M5.Display.fillRect(5, 5, 6, 6, TFT_WHITE);
        M5.Display.display();

        // Choose either of the two pieces of code below.
        pinMode(GPIO_NUM_2, INPUT_PULLUP);
        attachInterrupt(GPIO_NUM_2, pm1_irq_handler, FALLING);

        // esp_sleep_enable_ext1_wakeup(1ULL << GPIO_NUM_2, ESP_EXT1_WAKEUP_ANY_LOW);
        // rtc_gpio_pullup_en(GPIO_NUM_2);
        // Serial.println("Going to sleep now");
        // esp_deep_sleep_start();
    }
}

サンプル動作デモ:

5. KEY ウェイクアップ

KEY による M5PM1 のウェイクアップ

電源が L1 モードに切り替わると、CoreMatrix の KEY1~KEY3 ボタンのプルアップと M5PM1 が給電状態を維持します。KEY3 ウェイクアップ機能を設定すると、M5PM1 はスリープに入り、ボタンのプルアップとウェイクアップロジックの動作に必要な L1 出力電源(3V3_L1_EN)を維持します。

この状態で KEY3 を押すと、M5PM1 G2 に立ち下がりエッジのウェイクアップ信号が発生し、M5PM1 が復帰して再起動します。

M5PM1 が復帰すると、L0、L1、L3A の電源投入シーケンスを再実行し、ESP32-C61 は初期化をやり直します。

例: 起動後にボタン B(KEY2)を 1 回押すと、KEY3 ウェイクアップと M5PM1 の L1 電源保持が設定され、M5PM1 はスリープに入ります。この状態で KEY3 を押すと M5PM1 がウェイクアップし、ESP32-C61 に再給電されます。

cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
#include <M5PM1.h>
#include <M5Unified.h>

M5PM1 pm1;

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

    if (pm1.begin(&M5.In_I2C, M5PM1_DEFAULT_ADDR, M5PM1_I2C_FREQ_100K) != M5PM1_OK) {
        Serial.println("PM1 initialization failed");
        while (true) {
            delay(1000);
        }
    }

    Serial.println("PM1 initialization successful");
    M5.Display.setColorDepth(8);
    M5.Display.setRotation(1);
    M5.Display.setBrightness(128);
    M5.Display.fillScreen(TFT_BLACK);
    M5.Display.drawRect(0, 0, 16, 16, TFT_WHITE);
    M5.Display.fillRect(7, 7, 2, 2, TFT_WHITE);
    M5.Display.display();

    pm1.gpioSetWakeEdge(M5PM1_GPIO_NUM_2, M5PM1_GPIO_WAKE_FALLING);
    pm1.gpioSetWakeEnable(M5PM1_GPIO_NUM_2, true);
    pm1.setLdoEnable(true);
    pm1.ldoSetPowerHold(true);
}

void loop(void)
{
    M5.update();
    if (M5.BtnB.wasPressed()) {
        M5.Display.fillScreen(TFT_BLACK);
        M5.Display.drawRect(0, 0, 16, 16, TFT_WHITE);
        M5.Display.fillRect(5, 5, 6, 6, TFT_WHITE);
        M5.Display.display();
        Serial.println("KEY3 wakeup enabled");
        delay(1000);
        // Shutdown
        pm1.setLedEnLevel(true);
        pm1.shutdown();
    }
}

サンプル動作デモ:

KEY による ESP32-C61 のウェイクアップ

M5PM1 の G3 IRQ ピンは、回路上で ESP32-C61 の G2 に接続されており、連鎖的なウェイクアップ信号によって ESP32-C61 をウェイクアップできます。手順は次の通りです。

  1. M5PM1 G2 を入力モードに設定します。このピンは KEY3 ボタンに接続されています。
  2. M5PM1 G3 を IRQ 出力信号ピンに設定します。G2(KEY3 の押下)の状態が変化すると、G3 IRQ ピンが割り込み信号を出力します。
  3. ESP32-C61 の G2 を立ち下がりエッジ割り込み入力に設定し、M5PM1 G3 IRQ を検出します。実際のディープスリープのコードは、サンプル内ではコメントアウトされたままです。
  4. KEY3 を押すと、ボタンのウェイクアップ信号 -> M5PM1 G3 IRQ -> ESP32-C61 G2 の割り込みハンドラーが順にトリガーされ、CoreMatrix のマトリクス表示が変化します。

例: 起動後にボタン B(KEY2)を 1 回押すと、KEY3 に対応する M5PM1 G2 割り込みが設定され、ESP32-C61 G2 の立ち下がりエッジ割り込みが登録されます。KEY3 を押すと GPIO 割り込みハンドラーが起動し、マトリクスの表示が待機状態からトリガー済み状態に変わります。もう一度ボタン B を押すと M5PM1 IRQ フラグがクリアされ、再度テストできます。コード内でコメントアウトされているディープスリープ部分を有効にすると、KEY3 を押した後に ESP32-C61 が復帰し、setup() から再実行されます。

cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
#include <M5Unified.h>
#include <M5PM1.h>
#include "driver/rtc_io.h"

M5PM1 pm1;

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

    // Initialize PM1
    m5pm1_err_t err = pm1.begin(&M5.In_I2C, M5PM1_DEFAULT_ADDR, M5PM1_I2C_FREQ_100K);

    if (err == M5PM1_OK) {
        Serial.println("PM1 initialization successful");
        pm1.irqClearGpioAll();
        pm1.irqClearSysAll();
        pm1.irqClearBtnAll();

        pm1.irqSetGpioMaskAll(M5PM1_IRQ_MASK_ENABLE);
        pm1.irqSetSysMaskAll(M5PM1_IRQ_MASK_ENABLE);
        pm1.irqSetBtnMaskAll(M5PM1_IRQ_MASK_ENABLE);

        pm1.irqSetGpioMask(M5PM1_IRQ_GPIO2, M5PM1_IRQ_MASK_DISABLE);
        pm1.gpioSetMode(M5PM1_GPIO_NUM_2, M5PM1_GPIO_MODE_INPUT);
        pm1.gpioSetPull(M5PM1_GPIO_NUM_2, M5PM1_GPIO_PULL_UP);

        pm1.gpioSetMode(M5PM1_GPIO_NUM_3, M5PM1_GPIO_MODE_OUTPUT);
        pm1.gpioSetDrive(M5PM1_GPIO_NUM_3, M5PM1_GPIO_DRIVE_PUSHPULL);
        pm1.gpioSetPull(M5PM1_GPIO_NUM_3, M5PM1_GPIO_PULL_UP);
        pm1.gpioSetOutput(M5PM1_GPIO_NUM_3, true);
        pm1.gpioSetFunc(M5PM1_GPIO_NUM_3, M5PM1_GPIO_FUNC_IRQ);
    } else {
        Serial.printf("PM1 initialization failed, error code: %d\n", err);
    }

    M5.Display.setColorDepth(8);
    M5.Display.setRotation(1);
    M5.Display.setBrightness(128);
    M5.Display.fillScreen(TFT_BLACK);
    M5.Display.drawRect(0, 0, 16, 16, TFT_WHITE);
    M5.Display.fillRect(7, 7, 2, 2, TFT_WHITE);
    M5.Display.display();
}

volatile bool pm1IrqTriggered = false;

void ARDUINO_ISR_ATTR pm1_irq_handler()
{
    pm1IrqTriggered = true;
}

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

    if (pm1IrqTriggered) {
        pm1IrqTriggered = false;
        Serial.println("PM1 IRQ triggered");

        M5.Display.fillScreen(TFT_BLACK);
        M5.Display.drawRect(0, 0, 16, 16, TFT_WHITE);
        M5.Display.fillRect(4, 4, 8, 8, TFT_WHITE);
        M5.Display.display();
    }

    if (M5.BtnB.wasPressed()) {
        pm1.irqClearGpioAll();
        pm1.irqClearSysAll();
        pm1.irqClearBtnAll();
        Serial.println("KEY3 IRQ enabled successfully");

        M5.Display.fillScreen(TFT_BLACK);
        M5.Display.drawRect(0, 0, 16, 16, TFT_WHITE);
        M5.Display.fillRect(5, 5, 6, 6, TFT_WHITE);
        M5.Display.display();

        // Choose either of the two pieces of code below.
        pinMode(GPIO_NUM_2, INPUT_PULLUP);
        attachInterrupt(GPIO_NUM_2, pm1_irq_handler, FALLING);

        // esp_sleep_enable_ext1_wakeup(1ULL << GPIO_NUM_2, ESP_EXT1_WAKEUP_ANY_LOW);
        // rtc_gpio_pullup_en(GPIO_NUM_2);
        // Serial.println("Going to sleep now");
        // esp_deep_sleep_start();
    }
}

サンプル動作デモ:

6. M5IOE1 周辺機器の制御

CoreMatrix の M5IOE1 は、L3B 段階の周辺機器の電源イネーブルと状態の読み取りを担当します。M5IOE1 API を使うと、LED マトリクス、microSD、ブザー、Grove 5V を個別に制御し、充電状態とカード挿入状態を読み取れます。

M5IOE1 ピン CoreMatrix の機能
PYG1 microSD 電源イネーブル
PYG2 ブザー電源イネーブル
PYG3 充電電流設定
PYG4 LED マトリクス電源イネーブル
PYG5 Grove 5V 出力イネーブル
PYG8 充電状態入力
PYG10 microSD 検出入力
PYG11 ブザー PWM 出力

以下の例では M5IOE1 を直接初期化し、LED マトリクス、microSD、Grove 5V の電源をオンにし、ブザーの電源をオフにして、充電状態と microSD 検出状態を読み取ります。

cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
#include <M5Unified.h>
#include <M5IOE1.h>

M5IOE1 ioe1;

#define IOE_TF_EN     M5IOE1_PIN_1
#define IOE_BUZZER_EN M5IOE1_PIN_2
#define IOE_LEDS_EN   M5IOE1_PIN_4
#define IOE_GROVE_EN  M5IOE1_PIN_5
#define IOE_CHG_STAT  M5IOE1_PIN_8
#define IOE_TF_DET    M5IOE1_PIN_10

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

  M5.Display.setColorDepth(8);
  M5.Display.setRotation(1);
  M5.Display.setBrightness(128);

  const m5ioe1_err_t err = ioe1.begin(&M5.In_I2C, M5IOE1_DEFAULT_ADDR_2, M5IOE1_I2C_FREQ_100K);
  if (err != M5IOE1_OK) {
    Serial.printf("M5IOE1 init failed: %d\n", err);
    while (true) delay(1000);
  }

  ioe1.pinMode(IOE_TF_EN, OUTPUT);
  ioe1.pinMode(IOE_BUZZER_EN, OUTPUT);
  ioe1.pinMode(IOE_LEDS_EN, OUTPUT);
  ioe1.pinMode(IOE_GROVE_EN, OUTPUT);
  ioe1.pinMode(IOE_CHG_STAT, INPUT);
  ioe1.pinMode(IOE_TF_DET, INPUT_PULLUP);

  ioe1.digitalWrite(IOE_TF_EN, HIGH);
  ioe1.digitalWrite(IOE_LEDS_EN, HIGH);
  ioe1.digitalWrite(IOE_GROVE_EN, HIGH);
  ioe1.digitalWrite(IOE_BUZZER_EN, LOW);

  Serial.println("CoreMatrix L3B rails enabled");
}

void loop()
{
  M5.update();

  const int chargeState = ioe1.digitalRead(IOE_CHG_STAT);
  const int tfState = ioe1.digitalRead(IOE_TF_DET);

  Serial.printf("CHG_STAT: %d, TF_DET: %d\n", chargeState, tfState);
  delay(1000);
}

書き込みに成功すると、プログラムは 1 秒ごとに CHG_STAT と TF_DET の状態をシリアルポートに出力します。CHG_STAT が Low の場合は充電中、TF_DET が Low の場合は microSD カードが挿入されていることを示します。

Page Tools
PDF
On This Page