Arduino入門

2. デバイス&サンプル

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

アクセサリー

6. アプリケーション

PaperMono M5PM1 & M5IOE1 電源管理

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

PaperMono は M5PM1 と M5IOE1 を内蔵し、ハードウェア回路と組み合わせて多段電源スイッチを実現しています。各電源レベルは、対応する周辺機器やインターフェースへの電源供給を制御します。動作要件に応じて電源レベルを切り替え、使用していない周辺機器を停止することで、システム全体の消費電力を削減できます。

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

M5PM1 / M5IOE1 ドライバライブラリを使用すると、M5PM1 / M5IOE1 のピン機能を簡単に設定し、低消費電力復帰や周辺機器の電源切り替えに利用できます。

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

L0

この電源レベルでは、バッテリーから M5PM1 と RTC への電源供給が維持されます。バッテリー残量がある限り、この電源レベルは常に保持されます。M5PM1 は、ボタンによる基本的な電源オン / オフ操作と RTC 割り込みによる復帰に対応します。

L1

この電源レベルでは、IMU への電源供給が新たに有効になります。このレベルでは M5PM1 の 3V3_L1_EN 電源スイッチを使用し、以下の API でオン / オフを制御できます。

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

また、IMU の INT1 割り込みピンは M5PM1 の PYG4 に接続されています。関連レジスタを設定すると、IMU への電源を維持したまま M5PM1 をスリープ状態にできます。デバイスを反転させることで IMU の割り込み(High アクティブ)が発生し、M5PM1 が復帰します。IMU (L1) の電源保持には、以下の API を使用します。

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

L2 \ L3A

この電源レベルでは、ESP32-S3、LoRa モジュール、NFC/RFID モジュール、M5IOE1 I/O エキスパンダー、ユーザーボタンのプルアップ、画面タッチ割り込み、ブザーの電源、RGB LED の赤チャンネル制御が新たに有効になります。このレベルでは M5PM1 の 3V3_L2_EN 電源スイッチを使用し、以下の API でオン / オフを制御できます。

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

ESP32-S3 がスリープ状態のとき、電源は L2 レベルです。ESP32-S3 が動作しているときは L3A レベルです。ESP32-S3 は M5PM1 をスリープ状態に制御することで、自身への電源供給を停止できます (L2 -> L1/L0)。

L3B

L3B レベルは、画面、タッチコントローラー、microSD 検出、PDM マイク、RGB LED など、消費電力が比較的大きい周辺機器への電源供給や関連設定を制御します。このレベルは M5IOE1 I/O エキスパンダーで制御され、一部の周辺機器を個別に制御できます。

M5IOE1 PYG1 PYG2 PYG3 PYG4_ADC2 PYG5 PYG6 PYG8 PYG9 PYG10 PYG12 PYG13 PYG14
microSD PYB_TF_DET PYB_TF_EN
LoRa PYB_LoRa_ANT_SW PYB_LoRa_RST
E-Paper PYB_EPD_EN PYB_EINK_RST
NFC / RFID PYB_NFC_EN
Touch PYB_TP_RST PYB_TP_EN
RGB LED PYB_LED_G PYB_LED_B
PDM PYB_PDM_EN
  • PYB_TF_DET: microSD 検出
  • PYB_TF_EN: microSD 電源
  • PYB_LoRa_ANT_SW: LoRa アンテナスイッチ
  • PYB_LoRa_RST: LoRa リセット
  • PYB_EPD_EN: 電子ペーパー電源
  • PYB_EINK_RST: 電子ペーパーリセット
  • PYB_NFC_EN: NFC / RFID の有効化とリセット
  • PYB_LED_G: RGB LED 緑チャンネル制御
  • PYB_LED_B: RGB LED 青チャンネル制御
  • PYB_PDM_EN: PDM 電源
  • PYB_TP_EN: タッチ電源
  • PYB_TP_RST: タッチリセット

2. M5PM1 スリープ

手動スリープ

M5PM1 はプログラムから手動でスリープ状態に移行でき、システム全体の消費電力を削減できます。デフォルトでは、スリープを直接設定すると電源は L0 レベルに戻り、M5PM1 のみが給電された状態になります。

pm1.shutdown();

IMU による復帰や ESP32-S3 SoC のスリープなど、特定の用途では、M5PM1 をスリープ状態にしながら一部の電源レベルへの供給を維持し、復帰元や状態保持に使用できます。

このような用途では、M5PM1 がスリープモードに入る前に、必要な電源レベルのスイッチピン状態を設定し、状態保持を有効にする必要があります。

I2C アイドルスリープ

M5PM1 は、I2C 通信がアイドル状態のときに自動的にスリープへ移行するよう設定でき、システム全体の消費電力を削減できます。スリープ状態に入った後、ESP32-S3 から M5PM1 への最初の通信は 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;

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

サンプルの説明: デバイスの電源を入れた後、ボタン A をクリックすると 10s 後に再起動するタイマーを設定します。ボタン B をクリックすると、10s 後に M5PM1 の電源をオフにするタイマーを設定します。電源オフ後は、電源ボタンをクリックして再度電源を入れられます。

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
#include <M5Unified.h>
#include <M5PM1.h>

M5PM1 pm1;

void setup()
{
    auto cfg = M5.config();
    cfg.clear_display = false;
    M5.begin(cfg);
    Serial.begin(115200);

    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);
        }
    }

    M5.Display.setEpdMode(epd_mode_t::epd_fast);
    M5.Display.setTextDatum(middle_center);
    M5.Display.setFont(&fonts::FreeSansBold18pt7b);
    M5.Display.drawString("BtnA: power on after 10s", M5.Display.width() / 2, 280);
    M5.Display.drawString("BtnB: power off after 10s", M5.Display.width() / 2, 340);
}

void loop(void)
{
    M5.update();
    if (M5.BtnA.wasPressed()) {
        M5.Display.clear();
        M5.Display.setCursor(40, 80);
        M5.Display.println("Shutdown");
        M5.Display.println("  After 10s");
        M5.Display.println("     Power ON");
        delay(500);
        pm1.timerSet(10, M5PM1_TIM_ACTION_POWERON);
        pm1.shutdown();
    }
    if (M5.BtnB.wasPressed()) {
        M5.Display.clear();
        M5.Display.setCursor(40, 80);
        M5.Display.println("  After 10s");
        M5.Display.println("     Power OFF");
        delay(500);
        pm1.timerSet(10, M5PM1_TIM_ACTION_POWEROFF);
    }
}

M5PM1 の初期化後、ボタン A を押すと 10 秒後の電源オン動作が設定され、直ちに shutdown() が呼び出されます。タイマーが満了するとデバイスの電源が再び入ります。ボタン B を押すと 10 秒後の電源オフ動作が設定され、カウントダウンが終了するまでデバイスは動作を続けます。電源オフ後は、電源ボタンで再度電源を入れられます。

4. IMU による復帰

BMI270 ドライバライブラリ

IMU による M5PM1 の復帰

電源を L1 Mode に切り替えると、IMU、RTC、M5PM1 のみが給電されます。IMU による復帰を設定すると、M5PM1 もスリープ状態に入り、IMU の動作を維持するために L1 の電源出力 (3V3_L1_EN) は保持されます。

この状態でデバイスを反転または移動すると IMU の復帰信号が発生し、M5PM1 が復帰して再起動します。

M5PM1 の復帰後、L0、L1、L2 の電源投入シーケンスが再実行され、ESP32-S3 も初期化を再実行します。

サンプルの説明: デバイスの電源を入れた後、ボタン A をクリックして IMU の割り込みモードと M5PM1 の L1 電源保持を設定すると、M5PM1 がスリープ状態に入ります。この状態でデバイスを反転または移動すると M5PM1 が復帰し、ESP32-S3 の電源が再び入ります。

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
#include <M5Unified.h>
#include <M5PM1.h>
#include <Wire.h>
#include "SparkFun_BMI270_Arduino_Library.h"

BMI270 imu;
M5PM1 pm1;

void setup(void)
{
    auto cfg = M5.config();
    cfg.clear_display = false;
    M5.begin(cfg);
    Serial.begin(115200);

    Wire1.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);
    }
    pm1.setSingleResetDisable(false);

    // Check if sensor is connected and initialize
    while(imu.beginI2C(BMI2_I2C_PRIM_ADDR, Wire1) != 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.setEpdMode(epd_mode_t::epd_fast);
    M5.Display.setFont(&fonts::FreeMonoBold18pt7b);
    M5.Display.clear();
    M5.Display.setCursor(40, 100);
    M5.Display.printf("IMU Wakeup Test\n\n");
    M5.Display.println("   Press BtnA to Sleep");
    M5.Display.println("   Shake to wake up");
}

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.clear();
        M5.Display.setCursor(40, 100);
        M5.Display.println("Power OFF");
        delay(1000);
        // Shutdown
        pm1.setLdoEnable(true);
        pm1.ldoSetPowerHold(true);
        pm1.setLedEnLevel(true);
        pm1.shutdown();
    }
} 

ボタン A を押すと、プログラムは BMI270 の任意動作検出を有効にし、割り込みを Low アクティブの INT1 にマッピングします。その後、M5PM1 の L1 電源を保持したままシステム全体の電源をオフにします。デバイスの反転または移動によって割り込みが発生すると、M5PM1 が各電源レベルを復元し、ESP32-S3 の電源が入り、setup() から実行を開始します。

IMU による ESP32-S3 の復帰

M5PM1 の PYG1_IRQ ピンは回路上で ESP32-S3 の G1 に接続されており、連鎖した復帰信号によって ESP32-S3 を復帰させることができます。手順は次のとおりです。

  1. M5PM1 PYG4 を入力モードに設定します。このピンは IMU の INT1 出力信号に接続されています。
  2. M5PM1 PYG4_IRQ を IRQ 出力信号に設定します。IMU の割り込みによって PYG4 の状態が変化すると、PYG1_IRQ ピンから割り込み信号が出力されます。
  3. ESP32-S3 をスリープ状態に設定し、復帰ピンを G1 に設定します。
  4. デバイスを反転または移動します: IMU 復帰信号を発生 -> M5PM1 PYG1_IRQ を発生 -> ESP32-S3 を復帰。

サンプルの説明: デバイスの電源を入れた後、ボタン A をクリックして IMU 割り込みモードを設定します。デバイスを反転または移動すると GPIO 割り込みハンドラーが実行されます。ボタン A をもう一度クリックすると M5PM1 の IRQ フラグがクリアされ、再度テストできます。

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
#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)
{
    auto cfg = M5.config();
    cfg.clear_display = false;
    M5.begin(cfg);
    Serial.begin(115200);

    Wire1.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_1, M5PM1_GPIO_MODE_OUTPUT);
        pm1.gpioSetDrive(M5PM1_GPIO_NUM_1, M5PM1_GPIO_DRIVE_PUSHPULL);
        pm1.gpioSetFunc(M5PM1_GPIO_NUM_1, M5PM1_GPIO_FUNC_IRQ);
    } else {
        Serial.printf("PM1 initialization failed, error code: %d\n", err);
    }
    pm1.setSingleResetDisable(false);

    // Check if sensor is connected and initialize
    while(imu.beginI2C(BMI2_I2C_PRIM_ADDR, Wire1) != 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.setEpdMode(epd_mode_t::epd_fast);
    M5.Display.setFont(&fonts::FreeMonoBold18pt7b);
    M5.Display.clear();
    M5.Display.setCursor(40, 100);
    M5.Display.printf("IMU IRQ Test\n\n");
    M5.Display.println("   Press BtnA to Sleep");
    M5.Display.println("   Shake to wake up");
}

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");

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

        M5.Display.setCursor(40, 130);
        M5.Display.println("PM1 IRQ triggered");
    }
    
    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.clear();
        M5.Display.setCursor(40, 100);
        M5.Display.println("Now Shake!");
        
        // Choose either of the two pieces of code below.
        pinMode(GPIO_NUM_1, INPUT_PULLUP);
        attachInterrupt(GPIO_NUM_1, pm1_irq_handler, FALLING);

        // esp_sleep_enable_ext0_wakeup(GPIO_NUM_1, 0);  // 0 = Low
        // rtc_gpio_pullup_en(GPIO_NUM_1);
        // Serial.println("Going to sleep now");
        // esp_deep_sleep_start();
    }
} 

このプログラムは BMI270 INT1 を M5PM1 GPIO4 経由で PYG1_IRQ に変換し、ESP32-S3 G1 に接続します。現在のコードでは、デフォルトで G1 に立ち下がりエッジ割り込みを登録し、動作を検出すると画面とシリアルモニターに割り込み状態を表示します。ディープスリープからの復帰を実際にテストするには、コード内でコメント化されている esp_sleep_enable_ext0_wakeup()esp_deep_sleep_start() を使用してください。

5. RTC による復帰

RTC による M5PM1 の復帰

電源を L1 Mode に切り替えると、IMU、RTC、M5PM1 のみが給電されます。RTC のタイマー復帰を設定すると、M5PM1 はスリープ状態に入り、RTC の動作を維持するために L1 の電源出力 (3V3_L1_EN) は保持されます。

この状態で RTC タイマーによる復帰を設定すると、M5PM1 が復帰し、ESP32-S3 の電源が再び入ります。

M5PM1 の復帰後、L0、L1、L2 の電源投入シーケンスが再実行され、ESP32-S3 も初期化を再実行します。

サンプルの説明: デバイスの電源を入れた後、ボタン A をクリックして RTC タイマーによる復帰を設定すると、M5PM1 がスリープ状態に入ります。5s 後に RTC による復帰が発生し、ESP32-S3 の電源が再び入ります。

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
#include <M5Unified.h>
#include <M5PM1.h>

M5PM1 pm1;

void setup(void)
{
    auto cfg = M5.config();
    cfg.clear_display = false;
    M5.begin(cfg);
    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.gpioSetWakeEnable(M5PM1_GPIO_NUM_0, true);
        pm1.gpioSetWakeEdge(M5PM1_GPIO_NUM_0, M5PM1_GPIO_WAKE_FALLING);  // Falling edge
    } else {
        Serial.printf("PM1 initialization failed, error code: %d\n", err);
    }

    M5.Display.setEpdMode(epd_mode_t::epd_fast);
    M5.Display.setFont(&fonts::FreeMonoBold18pt7b);
    M5.Display.clear();
    M5.Display.setCursor(40, 100);
    M5.Display.printf("RTC Wakeup Test\n\n");
    M5.Display.println("   Press BtnA to Sleep");
}

void loop(void)
{
    M5.update();
    if (M5.BtnA.wasPressed()) {
        M5.Rtc.clearIRQ();

        if (M5.Rtc.setTimerIRQ(5000)){// 5s later wakeup
            Serial.println("RTC IRQ enabled successfully");
            M5.Display.clear();
            M5.Display.setCursor(40, 100);
            M5.Display.printf("Power OFF");
            M5.Display.setCursor(40, 130);
            M5.Display.printf("5s later wakeup");
            // Shutdown
            pm1.setLdoEnable(true);
            pm1.ldoSetPowerHold(true);
            pm1.setLedEnLevel(true);
            delay(500);
            pm1.shutdown();
        } else {
            Serial.println("Failed to enable RTC IRQ");
        }
    }
}

ボタン A を押すと、プログラムは RTC 割り込みをクリアして 5 秒タイマーを設定し、M5PM1 の L1 電源を保持したままシステム全体の電源をオフにします。タイマーが満了すると、RTC は M5PM1 GPIO0 経由で立ち下がりエッジの復帰信号を発生します。M5PM1 が電源を復元し、ESP32-S3 が初期化を再実行します。

RTC による ESP32-S3 の復帰

RTC による ESP32-S3 の復帰手順は、IMU による復帰と同様です。RTC による復帰では BMI270 の割り込み設定は不要ですが、RTC IRQ → M5PM1 GPIO → PYG1_IRQ → ESP32-S3 G1 の信号経路を使用します。

注意
以下のサンプルを正常に動作させるには、M5Unified ライブラリのバージョン 0.2.21 以降が必要です。
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) {
  auto cfg = M5.config();
    cfg.clear_display = false;
    cfg.disable_rtc_irq = false;
    M5.begin(cfg);
    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_GPIO0, M5PM1_IRQ_MASK_DISABLE);
        pm1.gpioSetMode(M5PM1_GPIO_NUM_0, M5PM1_GPIO_MODE_INPUT);
        pm1.gpioSetPull(M5PM1_GPIO_NUM_0, M5PM1_GPIO_PULL_UP);

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

    M5.Display.setEpdMode(epd_mode_t::epd_fast);
    M5.Display.setFont(&fonts::FreeMonoBold18pt7b);
    M5.Display.clear();
    M5.Display.setCursor(40, 100);
    M5.Display.printf("RTC IRQ Test\n\n");
    M5.Display.println("   Press BtnA to Sleep");
    delay(200);
    M5.Rtc.clearIRQ();
    delay(200);
}

volatile bool pm1IrqTriggered = false;

void ARDUINO_ISR_ATTR pm1_irq_handler() {
    pm1IrqTriggered = true;
}

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

    if (pm1IrqTriggered) {
        pm1IrqTriggered = false;
        M5.Rtc.clearIRQ();
        pm1.irqClearGpioAll();
        pm1.irqClearSysAll();
        pm1.irqClearBtnAll();
        Serial.println("PM1 IRQ triggered");
        M5.Display.setCursor(40, 140);
        M5.Display.println("PM1 IRQ triggered");
    }

    if (M5.BtnA.wasPressed()) {
        if (M5.Rtc.setTimerIRQ(5000)){// Set Timer IRQ
            Serial.println("RTC IRQ enabled successfully");
            M5.Display.clear();
            M5.Display.setCursor(40, 100);
            M5.Display.printf("Wait for IRQ ...");

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

            // esp_sleep_enable_ext0_wakeup(GPIO_NUM_1, 0);  // 0 = Low
            // rtc_gpio_pullup_en(GPIO_NUM_1);
            // Serial.println("Going to sleep now");
            // esp_deep_sleep_start();
        } else {
            Serial.println("Failed to enable RTC IRQ");
        }
    }
}

ボタン A を押すと、プログラムは 5 秒後の RTC 割り込みを設定し、M5PM1 を介して GPIO0 の割り込みを ESP32-S3 G1 に転送します。現在のコードでは、デフォルトで G1 の立ち下がりエッジ割り込みを使用してトリガー結果を表示します。コメント化されているディープスリープ部分を有効にすると、RTC タイマーの満了時に ESP32-S3 が復帰し、setup() から再実行されます。

6. M5IOE1 周辺機器制御

PaperMono の M5IOE1 は L3B レベルの周辺機器を制御します。主な M5IOE1 ピンは次のとおりです。

M5IOE1 ピン PaperMono の機能
M5IOE1_PIN_2 LoRa アンテナスイッチ
M5IOE1_PIN_3 電子ペーパー電源
M5IOE1_PIN_4 NFC/RFID 有効化
M5IOE1_PIN_5 電子ペーパーリセット
M5IOE1_PIN_6 タッチリセット
M5IOE1_PIN_8 RGB LED 緑チャンネル
M5IOE1_PIN_9 RGB LED 青チャンネル
M5IOE1_PIN_10 LoRa リセット
M5IOE1_PIN_12 PDM マイク電源
M5IOE1_PIN_13 タッチ電源
M5IOE1_PIN_14 microSD 電源

以下のサンプルでは M5PM1 と M5IOE1 を直接初期化し、ディスプレイのフロントライト、LoRa アンテナスイッチとリセット、NFC 電源、RGB LED の緑と青のチャンネル制御を順番に実演します。電子ペーパー、タッチ、microSD、PDM に関連するピンは M5Unified がすでに使用しています。変更する前に、対応する周辺機器が停止していることを確認してください。そうしないと、周辺機器が動作しなくなったり、再初期化が必要になったりする場合があります。

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 129 130 131
#include <M5Unified.h>
#include <M5PM1.h>
#include <M5IOE1.h>

M5PM1 pm1;
M5IOE1 ioe1;

#define IOE_LORA_ANT_SW M5IOE1_PIN_2
#define IOE_LORA_RST    M5IOE1_PIN_10
#define IOE_NFC_EN      M5IOE1_PIN_4
#define IOE_LED_G       M5IOE1_PIN_8
#define IOE_LED_B       M5IOE1_PIN_9

void setup(void)
{
    auto cfg = M5.config();
    cfg.clear_display = false;
    M5.begin(cfg);
    Serial.begin(115200);

    // Initialize PM1.
    const m5pm1_err_t pm1_err =
        pm1.begin(&M5.In_I2C, M5PM1_DEFAULT_ADDR, M5PM1_I2C_FREQ_100K);
    if (pm1_err == M5PM1_OK) {
        Serial.println("PM1 initialization successful");
    } else {
        Serial.printf("PM1 initialization failed, error code: %d\n", pm1_err);
    }
    pm1.setSingleResetDisable(false);

    // Enable the PaperMono LoRa power rail through PM1 GPIO2.
    pm1.gpioSetFunc(M5PM1_GPIO_NUM_2, M5PM1_GPIO_FUNC_GPIO);
    pm1.gpioSetMode(M5PM1_GPIO_NUM_2, M5PM1_GPIO_MODE_OUTPUT);
    pm1.gpioSetDrive(M5PM1_GPIO_NUM_2, M5PM1_GPIO_DRIVE_PUSHPULL);
    pm1.gpioSetOutput(M5PM1_GPIO_NUM_2, true);

    // Initialize IOE1.
    const m5ioe1_err_t ioe1_err =
        ioe1.begin(&M5.In_I2C, M5IOE1_DEFAULT_ADDR, M5IOE1_I2C_FREQ_100K);
    if (ioe1_err == M5IOE1_OK) {
        Serial.println("IOE1 initialization successful");
    } else {
        Serial.printf("IOE1 initialization failed, error code: %d\n", ioe1_err);
        while (true) delay(1000);
    }

    ioe1.pinMode(IOE_LORA_ANT_SW, OUTPUT);
    ioe1.pinMode(IOE_LORA_RST, OUTPUT);
    ioe1.pinMode(IOE_NFC_EN, OUTPUT);
    ioe1.pinMode(IOE_LED_G, OUTPUT);
    ioe1.pinMode(IOE_LED_B, OUTPUT);

    // Default states: LoRa receive path, LoRa released from reset, NFC on, LEDs off.
    ioe1.digitalWrite(IOE_LORA_ANT_SW, LOW);
    ioe1.digitalWrite(IOE_LORA_RST, HIGH);
    ioe1.digitalWrite(IOE_NFC_EN, HIGH);
    ioe1.digitalWrite(IOE_LED_G, LOW);
    ioe1.digitalWrite(IOE_LED_B, LOW);

    M5.Display.setEpdMode(epd_mode_t::epd_fast);
    M5.Display.setFont(&fonts::FreeMonoBold18pt7b);
    M5.Display.clear();
    M5.Display.setCursor(40, 100);
    M5.Display.printf("IOE1 Power Test Begin\n\n");
    delay(1000);
}

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

    // Display frontlight.
    M5.Display.clear();
    M5.Display.setCursor(40, 100);
    M5.Display.printf("Frontlight OFF");
    M5.Display.setBrightness(0);
    delay(1000);
    M5.Display.setBrightness(255);
    M5.Display.setCursor(40, 140);
    M5.Display.printf("Frontlight ON");
    delay(1000);

    // LoRa antenna switch.
    M5.Display.clear();
    M5.Display.setCursor(40, 100);
    M5.Display.printf("LoRa antenna RX");
    ioe1.digitalWrite(IOE_LORA_ANT_SW, LOW);
    delay(1000);
    M5.Display.setCursor(40, 140);
    M5.Display.printf("LoRa antenna TX");
    ioe1.digitalWrite(IOE_LORA_ANT_SW, HIGH);
    delay(1000);

    // LoRa reset.
    M5.Display.clear();
    M5.Display.setCursor(40, 100);
    M5.Display.printf("LoRa reset LOW");
    ioe1.digitalWrite(IOE_LORA_RST, LOW);
    delay(100);
    M5.Display.setCursor(40, 140);
    M5.Display.printf("LoRa reset HIGH");
    ioe1.digitalWrite(IOE_LORA_RST, HIGH);
    delay(1000);

    // NFC power enable.
    M5.Display.clear();
    M5.Display.setCursor(40, 100);
    M5.Display.printf("NFC power OFF");
    ioe1.digitalWrite(IOE_NFC_EN, LOW);
    delay(1000);
    M5.Display.setCursor(40, 140);
    M5.Display.printf("NFC power ON");
    ioe1.digitalWrite(IOE_NFC_EN, HIGH);
    delay(1000);

    // RGB LED green and blue channels.
    M5.Display.clear();
    M5.Display.setCursor(40, 100);
    M5.Display.printf("Green LED ON");
    ioe1.digitalWrite(IOE_LED_G, HIGH);
    ioe1.digitalWrite(IOE_LED_B, LOW);
    delay(1000);
    M5.Display.setCursor(40, 140);
    M5.Display.printf("Blue LED ON");
    ioe1.digitalWrite(IOE_LED_G, LOW);
    ioe1.digitalWrite(IOE_LED_B, HIGH);
    delay(1000);
    ioe1.digitalWrite(IOE_LED_G, LOW);
    ioe1.digitalWrite(IOE_LED_B, LOW);
    delay(200);
}

書き込みに成功すると、プログラムは最初にディスプレイのフロントライトをオフ、オンの順に切り替え、その後 LoRa アンテナスイッチ、LoRa リセット、NFC 電源、RGB LED の緑と青のチャンネル制御を順番に実演します。

Page Tools
PDF
On This Page