pdf-icon

Arduino入門

2. デバイス&サンプル

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

アクセサリー

6. アプリケーション

StopWatch M5PM1 & M5IOE1 電源管理

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

StopWatch は内部に M5PM1 + M5IOE1 を統合しており、ハードウェア回路と組み合わせることで多段階電源スイッチ設計を実現しています。異なる電源レベルのスイッチは、関連する周辺機器やインターフェースへの電源供給をそれぞれ制御します。ユーザーは動作要件に応じて電源有効レベルを切り替え、未使用の周辺機器をオフにすることで、デバイス全体の低消費電力化を実現できます。

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

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

注意事項
StopWatch の多段階電源スイッチ設計はカスケード構造ではありません。L1 ~ L3B スイッチの電源入力はすべて L0 (SYS_VBUS) レベルの電源元から供給され、前段の電源レベルから供給されるわけではありません。そのため、各電源レベルを個別に制御できます。上記のレベル分けは、周辺機器への給電と消費電力に基づいています。M5PM1 の起動後は L1、L2、L3A が自動的に有効になり(デフォルトで DCDC3V3_EN_PPLDO3V3_EN_PPCHG_EN_PP を有効化)、M5Unified の初期化処理中にさらに M5IOE1 が制御されて L3B が有効になり、他の周辺機器への給電や有効化ピンが利用可能になります。

L0

この電源レベルでは、バッテリーが M5PM1 への給電を維持します。バッテリー残量が尽きない限りこの電源レベルは保持され、M5PM1 は基本的なボタン操作による電源 ON/OFF をサポートします。

L1

このレベルでは IMU と RTC 周辺機器への給電を有効にします。このレベルの電源スイッチは M5PM1 の LDO3V3_EN_PP (PM_3V3_L1_EN) を使用しており、以下の API で制御できます。

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

同時に、IMU の INT1 割り込みピンと RTC のタイマー割り込みピンは M5PM1 の PYG4 に接続されています。関連レジスタを設定することで、IMU / RTC への給電を維持したまま M5PM1 をスリープ状態に移行できます。デバイスを反転させることで IMU 割り込み(アクティブ High)を発生させるか、RTC タイマー割り込み(アクティブ Low)を発生させることで M5PM1 をウェイクアップできます。以下の API で IMU / RTC (L1) の給電維持を実現できます。

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

L2 / L3A

この電源レベルでは、ESP32-S3、Grove インターフェース、M5IOE1 拡張チップ、ユーザーボタンのプルアップ電源、EXT.PORT インターフェースの 3V3_L2 出力、および CH442E スイッチチップへの給電が有効になります(このスイッチチップは EXT.PORT の MUX_IO_1/2 機能を UART0 / USB に切り替えるために使用されます)。

Grove 拡張インターフェースの入出力電源は、M5PM1 の EXT_5V_EN (BOOST5V_EN_PP) ピンを通じて制御する必要があります。

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

Grove EXT_5V インターフェースをオフにすると入力モードになり、この場合は外部 5V 入力電源が必要です。外部給電がない用途では、以下の API で EXT_5V 出力モードを再度有効にできます。

M5.Power.setExtOutput(true); // EXT_5V OUTPUT
// M5.Power.setExtOutput(false); // EXT_5V INPUT

L3B

L3B レベルでは、ディスプレイ、スピーカー、振動モーターなど、消費電力の比較的大きい周辺機器への給電または関連設定を制御します。このレベルは M5IOE1 IO 拡張チップによって制御され、一部の周辺機器を個別に制御できます。

M5IOE1 PYG1 PYG3 PYG9 PYG8 PYG10 PYG4 PYG5
Ext.Port Select PYB_MUX_CTR
Audio L3B PYB_AU_EN
Vibration Motor PYB_MT_PWM
3V3_L3B PYB_L3B_EN
Speaker AMP AW8737A PYB_SPK_EN
Touch PYB_TP_RST
AMOLED PYB_OLED_RST
  • PYG1(PYB_MUX_CTR): 背面拡張インターフェース MUX_IO_1/2USB / UART 機能切り替え
  • PYG3(PYB_AU_EN): ES8311 電源 + MIC 電源
  • PYG9(PYB_MT_PWM): 振動モーター PWM 信号
  • PYG8(PYB_L3B_EN): AMOLED ディスプレイ電源
  • PYG10(PYB_SPK_EN): AW8737A スピーカーアンプ有効化
  • PYG4(PYB_TP_RST): タッチリセット
  • PYG5(PYB_OLED_RST): ディスプレイリセット

個別の周辺機器電源制御は、以下の M5IOE1 API を使用して独立して行えます:

ioe1.pinMode(M5IOE1_PIN_1, OUTPUT);
ioe1.digitalWrite(M5IOE1_PIN_1, LOW);

2. M5PM1 スリープ

手動スリープ

M5PM1 はソフトウェアから手動でスリープ状態へ移行させることができ、デバイス全体の消費電力を低減できます。デフォルト構成では、直接スリープに入ると L0 電源レベルまで戻り、M5PM1 のみが通電状態になります。

pm1.shutdown();

一部の特殊な利用シーン(IMU ウェイクアップや ESP32-S3 SoC スリープなど)では、M5PM1 をスリープさせつつ、一部の周辺機器レベルへの給電を保持して、ウェイクアップソースまたは状態保持に利用できます。

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

I2C アイドルスリープ

M5PM1 は、I2C 通信のアイドル時間に基づく自動スリープをサポートしており、システム全体の消費電力を低減できます。スリープ移行後、ESP32-S3 と M5PM1 間の最初の通信は M5PM1 のウェイクアップに使われるため失敗し、有効な通信はその次のトランザクションから行われます。

m5pm1_err_t setI2cSleepTime(uint8_t seconds);

3. M5PM1 タイマー

M5PM1 はタイマー機能をサポートしており、カウントダウン終了後に電源 ON、電源 OFF、リセットなどの対応する動作を実行できます。

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;

タイマー電源ON / OFFサンプル

サンプル説明:デバイス起動後、Button A をクリックすると 10 秒後に電源 ON をトリガーするタイマーを設定し、Button B をクリックすると 10 秒後に M5PM1 の電源 OFF をトリガーします(電源ボタンをクリックすると再度起動できます)。

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
#include <M5Unified.h>
#include <M5PM1.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");
    } else {
        Serial.printf("PM1 initialization failed, error code: %d\n", err);
    }
    pm1.setSingleResetDisable(false);
    M5.Display.fillScreen(BLACK);
    M5.Display.setTextColor(WHITE);
    M5.Display.setTextFont(&fonts::FreeMonoBold12pt7b);
    M5.Display.setCursor(80, 150);
    M5.Display.println("Timer Power Test");
    M5.Display.println("    BtnA: After 10s ON");
    M5.Display.println("    BtnB: After 10s OFF");
}

void loop(void)
{
    M5.update();
    if (M5.BtnA.wasPressed()) {
        M5.Display.fillScreen(BLACK);
        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.fillScreen(BLACK);
        M5.Display.setCursor(40, 80);
        M5.Display.println("  After 10s");
        M5.Display.println("     Power OFF");
        delay(500);
        pm1.timerSet(10, M5PM1_TIM_ACTION_POWEROFF);
    }
} 

4. IMU ウェイクアップ

BMI270 ドライバライブラリ

IMU M5PM1 ウェイクアップ

電源を L1 モードに切り替えると、デバイス全体では IMU、RTC、M5PM1 のみが通電状態になります。IMU ウェイクアップ機能を設定すると、M5PM1 もスリープ状態に入りますが、IMU 動作を維持するために L1 出力電源 (3V3_L1_EN) は保持されます。

この状態でデバイスを反転または移動させると IMU ウェイクアップ信号が発生し、M5PM1 がウェイクアップして再起動します。

M5PM1 がウェイクアップすると、L0、L1、L2 の通電シーケンスが再実行され、ESP32-S3 も再初期化されます。

サンプル説明:デバイス起動後、Button A をクリックすると IMU 割り込みモードを設定し、M5PM1 の L1 電源保持を有効にしてスリープに入ります。デバイスを反転または移動させると 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
#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);

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

    // Check if sensor is connected and initialize
    // Address defaults to 0x68)
    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.setFont(&fonts::FreeMonoBold12pt7b);
    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();
    }
}

サンプル動作デモ:

IMU ESP32-S3 ウェイクアップ

M5PM1 の PYG1_IRQ ピンは、回路上 ESP32-S3 の G12 に接続されています。チェーン型ウェイクアップ信号を利用することで ESP32-S3 をウェイクアップできます。実装手順は以下の通りです。

  1. M5PM1 の PYG0 を入力モードに設定します。このピンは IMU の INT1 出力信号に接続されています。
  2. M5PM1 の PYG1_IRQ を IRQ 出力信号ピンに設定します。PYG0(IMU 割り込み信号)の状態が変化すると、PYG1_IRQ が割り込み信号を出力します。
  3. ESP32-S3 をスリープ状態に設定し、ウェイクアップピンを G12 に設定します。
  4. デバイスを反転または移動させると、IMU ウェイクアップ信号 -> M5PM1 PYG1_IRQ -> ESP32-S3 ウェイクアップの順で動作します。

サンプル説明:デバイス起動後、Button A をクリックすると IMU 割り込みモードを設定します。デバイスを反転または移動させると GPIO 割り込みハンドラがトリガーされます。再度 Button 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
#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);

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

    // Check if sensor is connected and initialize
    // Address defaults to 0x68)
    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.setFont(&fonts::FreeMonoBold12pt7b);
    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_HIGH;// active - high
        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_12, INPUT_PULLUP);
        attachInterrupt(GPIO_NUM_12, pm1_irq_handler, FALLING);

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

サンプル動作デモ:

5. RTC ウェイクアップ

RTC M5PM1 ウェイクアップ

電源を L1 モードに切り替えると、デバイス全体では IMU、RTC、M5PM1 のみが通電状態になります。RTC タイマーウェイクアップ機能を設定すると、M5PM1 はスリープ状態に入りつつ、RTC の動作を維持するために L1 出力電源 (3V3_L1_EN) を保持します。

この状態では、RTC タイマーウェイクアップを設定することで M5PM1 をウェイクアップさせ、ESP32-S3 を再び起動できます。

M5PM1 がウェイクアップすると、L0、L1、L2 の通電シーケンスが再実行され、ESP32-S3 も再初期化されます。

サンプル説明:デバイス起動後、Button A をクリックすると RTC タイマーウェイクアップを設定し、M5PM1 はスリープに入ります。5 秒後に 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
#include <M5Unified.h>
#include <M5PM1.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.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);
    }
    pm1.setSingleResetDisable(false);

    M5.Display.setFont(&fonts::FreeMonoBold12pt7b);
    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");
            delay(500);
            // Shutdown
            pm1.setLdoEnable(true);
            pm1.ldoSetPowerHold(true);
            pm1.setLedEnLevel(true);
            pm1.shutdown();
        } else {
            Serial.println("Failed to enable RTC IRQ");
        }
    }
}

サンプル動作デモ:

RTC ESP32-S3 ウェイクアップ

RTC ウェイクアップの設定手順は IMU ESP32-S3 ウェイクアップとほぼ同じです。違いは、RTC ウェイクアップでは IMU のように BMI270 割り込み設定が不要な点ですが、RTC IRQ -> M5PM1 GPIO -> PYG1_IRQ -> ESP32-S3 G12 というチェーンには同様に依存します。

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
#include <M5Unified.h>
#include <M5PM1.h>
#include "driver/rtc_io.h"

M5PM1 pm1;

void setup(void) {
  M5.begin();
  Serial.begin(115200);
  M5.Display.setFont(&fonts::FreeMonoBold12pt7b);
  M5.Display.clear();
  M5.Display.setCursor(40, 100);
  M5.Display.printf("RTC IRQ Test\n\n");
  M5.Display.println("     Press BtnA to Sleep");

  // 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);
  }
  pm1.setSingleResetDisable(false);
  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.setTimerIRQ(0);
        M5.Rtc.clearIRQ();
        pm1.irqClearGpioAll();
        pm1.irqClearSysAll();
        pm1.irqClearBtnAll();

        Serial.println("PM1 IRQ triggered");
        M5.Display.setCursor(40, 130);
        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 to wake up...");

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

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

サンプル動作デモ:

6. M5IOE1 周辺制御

M5IOE1 は独立した IO 拡張チップとして、L3B レベルの周辺機器への給電や関連設定の一部(ディスプレイ、スピーカー、振動モーターなど)を制御します。M5IOE1 API を通じて、これらの周辺機器を個別に制御し、より柔軟な電源管理を実現できます。

説明
現時点の M5Unified ライブラリではディスプレイ単独の初期化がまだサポートされていないため、以下のプログラムでは画面関連の IOE_TP_ENIOE_OLED_RSTIOE_L3B_EN ピンを制御していません。これらのピンを Low にしてから再度給電した場合、正常な表示とタッチ操作を行うにはディスプレイの再初期化が必要です。今後の M5Unified ライブラリ更新での対応をお待ちください。制御 API も同様で、例えば ioe1.digitalWrite(IOE_L3B_EN, LOW) を使用して L3B 電源スイッチを制御できます。
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
#include <M5Unified.h>
#include <M5PM1.h>
#include <M5IOE1.h>

M5PM1 pm1;
M5IOE1 ioe1;

#define IOE_MUX_EN   M5IOE1_PIN_1
#define IOE_AU_EN    M5IOE1_PIN_3
#define IOE_MT_PWM   M5IOE1_PIN_9
#define IOE_L3B_EN   M5IOE1_PIN_8
#define IOE_SPK_EN   M5IOE1_PIN_10
#define IOE_TP_EN    M5IOE1_PIN_4
#define IOE_OLED_RST M5IOE1_PIN_5

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

    // Initialize PM1
    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);

    // Initialize IOE1
    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);
    }
    ioe1.pinMode(IOE_MUX_EN, OUTPUT);
    ioe1.pinMode(IOE_AU_EN, OUTPUT);
    ioe1.pinMode(IOE_MT_PWM, OUTPUT);
    ioe1.pinMode(IOE_L3B_EN, OUTPUT);
    ioe1.pinMode(IOE_SPK_EN, OUTPUT);
    ioe1.pinMode(IOE_TP_EN, OUTPUT);
    ioe1.pinMode(IOE_OLED_RST, OUTPUT);
    
    ioe1.setPwmFrequency(2000);
    ioe1.setPwmDuty(M5IOE1_PWM_CH1, 0);   

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

void loop(void)
{
    M5.update();
    // MUX IO
    M5.Display.clear();
    M5.Display.setCursor(40, 100);
    M5.Display.printf("MUX IO set to UART0");
    ioe1.digitalWrite(IOE_MUX_EN, LOW);
    delay(1000);
    M5.Display.setCursor(40, 130);
    M5.Display.printf("MUX IO set to USB");
    ioe1.digitalWrite(IOE_MUX_EN, HIGH);
    delay(1000);

    // Speaker
    M5.Display.clear();
    M5.Display.setCursor(40, 100);
    M5.Display.printf("Speaker off");
    ioe1.digitalWrite(IOE_SPK_EN, LOW);
    M5.Speaker.tone(10000, 100);
    M5.Display.setCursor(40, 130);
    M5.Display.printf("No sound");
    delay(1000);
    M5.Display.setCursor(40, 160);
    M5.Display.printf("1s later hear sound");
    ioe1.digitalWrite(IOE_SPK_EN, HIGH);
    delay(1000);
    M5.Speaker.tone(4000, 200);
    delay(1000);

    // Vibration Motor
    M5.Display.clear();
    M5.Display.setCursor(40, 100);
    M5.Display.printf("Motor off");
    ioe1.setPwmDuty(M5IOE1_PWM_CH1, 0);
    M5.Display.setCursor(40, 130);
    M5.Display.printf("No vibration");
    delay(1000);
    M5.Display.setCursor(40, 160);
    M5.Display.printf("1s vibrate");
    ioe1.digitalWrite(IOE_MT_PWM, HIGH);
    ioe1.setPwmDuty(M5IOE1_PWM_CH1, 50);
    delay(1000);
    ioe1.setPwmDuty(M5IOE1_PWM_CH1, 0);
    delay(200);

    // Audio
    M5.Display.clear();
    M5.Display.setCursor(40, 100);
    M5.Display.printf("Audio off");
    ioe1.digitalWrite(IOE_AU_EN, LOW);
    M5.Speaker.end();
    M5.Speaker.begin();
    M5.Speaker.setVolume(128);
    M5.Speaker.tone(10000, 100);
    M5.Display.setCursor(40, 130);
    M5.Display.printf("No sound");
    delay(1000);
    M5.Display.setCursor(40, 160);
    M5.Display.printf("1s later hear sound");
    ioe1.digitalWrite(IOE_AU_EN, HIGH);
    M5.Speaker.end();
    M5.Speaker.begin();
    M5.Speaker.setVolume(128);
    delay(1000);
    M5.Speaker.tone(6000, 200);
    delay(1000);    
}

書き込み後、このプログラムは背面拡張インターフェース切り替え、スピーカー、振動モーター、およびオーディオアンプ制御の動作を順番にデモ表示します。

On This Page