pdf-icon

Arduino入門

2. デバイス&サンプル

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

アクセサリー

6. アプリケーション

StickS3 低電力設定

1. マルチレベル電源スイッチ設計

StickS3 は内部に M5PM1 を統合しており、ハードウェア回路と組み合わせることでマルチレベルの電源スイッチを実現しています。異なるレベルの電源スイッチは、関連する周辺機器やインターフェースへの電源供給を制御します。ユーザーは動作要件に応じて、異なるレベルの電源を有効にしたり、使用していない周辺機器への供給を停止したりすることで、デバイス全体の低消費電力を実現できます。

M5PM1 ドライバライブラリ

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

注意事項
StickS3 のマルチレベル電源スイッチ設計は直列構造ではありません。L1 ~ L3B スイッチの電源入力はいずれも L0 レベルのソースから供給されており、前段の電源から供給されているわけではありません。そのため、異なるレベルの電源スイッチを独立して制御することが可能です。上記の色分けは、周辺機器の電源供給と消費電力に基づいて区分されています。

M5PM1 の起動・通電後、L0、L1、L2 は自動的にオンになります(デフォルトで DCDC3V3_EN_PP, LDO3V3_EN_PP, CHG_EN_PP が有効になります)。M5Unified の初期化プロセスにおいて、さらに L3A、L3B がオンになり、他の周辺機器への電源供給が有効になります。

L0

この電源レベルでは、バッテリーから M5PM1 への電源供給が維持され、その他の周辺機器への供給は停止できます。バッテリー残量がある限り、この層の電源は維持され、M5PM1 は基本的なボタン操作による電源 ON/OFF をサポートします。

L1

IMU 周辺機器の電源を有効にします。このレベルの電源スイッチは M5PM1 の LDO3V3_EN_PP を使用しており、以下の API を通じてスイッチ制御が可能です。

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

また、IMU の INT1 中断ピンは M5PM1 の PYG4 に接続されています。関連するレジスタを設定することで、IMU への電源供給を維持したまま M5PM1 をスリープ状態にし、デバイスを振るなどの動作で IMU の中断をトリガーして M5PM1 をウェイクアップさせることができます。以下の API で IMU(L1)の電源維持を実現できます。

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

L2/L3A

この電源レベルでは、ESP32-S3、Grove インターフェース、Hat 拡張インターフェース、赤外線送受信、ボタンのプルアップ部分の電源が有効になります。

ESP32-S3 がスリープ状態のとき、電源は L2 レベルになります。ESP32-S3 が動作状態のとき、電源は L3A レベルになります。

ESP32-S3 は、M5PM1 をスリープ状態に制御することで、自身の電源を遮断(L2 -> L1/L0)できます。

そのうち、拡張インターフェースの入力 / 出力電源および赤外線回路の電源は、別途 M5PM1 の EXT_5V_EN (BOOST5V_EN_PP) ピンを使用してスイッチ制御を行う必要があります。

したがって、拡張インターフェースに外部センサーを接続する場合や、赤外線送受信機能を使用する前には、周辺機器が通電状態であることを確認してください。

EXT_5V_EN 入力電源に関する注意
デバイスの 5V 電源インターフェースは、DC 5V 出力 / 入力モードを設定可能です。インターフェースはデフォルトで入力モードになっており、このとき Grove インターフェース、上部 Hat2-Bus の EXT_5V、5VIN インターフェースから DC 5V 電源を入力できます。出力モードに設定されている場合は、USB または上部 Hat2-Bus の 5VIN からの入力のみが許可されます。他の出力インターフェースから電源を入力しないでください。デバイスが短絡し、破損する恐れがあります。

M5Unified のデフォルトの初期化では EXT_5V_EN がオフになります。この操作により、Grove、Hat EXT_5V インターフェース、IR TX/RX の電源が切断され、入力モードに切り替わります。この状態で IR TX/RX を正常に動作させるには、外部からの 5V 電源入力が必要です。外部電源がない使用シーンでは、以下の API を使用して EXT_5V 出力モードを再度有効にし、IR TX/RX の電源を復旧させることができます。

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

L3B

この電源レベルでは、すべての周辺機器への電源供給をオンにします。M5Unified のデフォルトの初期化で、このレベルの電源供給(LCD バックライト、MIC、SPK を含む)が有効になります。このレベルの電源スイッチは M5PM1 の PYG2 を使用しており、以下の API を通じてスイッチ制御が可能です。

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, false);

SPK AMP

デバイスの SPK パワーアンプスイッチは、M5PM1 の PYG3 ピンを通じて制御されます。この部分は M5Unified API または M5PM1 API を使用して、有効化および無効化が可能です。

注意:IR(赤外線)受信機能を使用する場合は、SPK パワーアンプをオフにする必要があります。

M5.Speaker.begin(); // SPKを初期化し、パワーアンプをオンにする
// M5.Speaker.end(); // SPKを解放し、パワーアンプをオフにする

or

pm1.gpioSetFunc(M5PM1_GPIO_NUM_3, M5PM1_GPIO_FUNC_GPIO);
pm1.gpioSetMode(M5PM1_GPIO_NUM_3, M5PM1_GPIO_MODE_OUTPUT);
pm1.gpioSetDrive(M5PM1_GPIO_NUM_3, M5PM1_GPIO_DRIVE_PUSHPULL);
pm1.gpioSetOutput(M5PM1_GPIO_NUM_3, true);  // SPKパワーアンプをオンにする
// pm1.gpioSetOutput(M5PM1_GPIO_NUM_3, false); // SPKパワーアンプをオフにする

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,  // 電源ON
                                       // Power on
    M5PM1_TIM_ACTION_POWEROFF = 0b100  // 電源OFF
                                       // Power off
} m5pm1_tim_action_t;

電源 ON/OFF タイマーの例

例の説明:デバイスの電源が入った後、ボタン A をクリックすると 10 秒タイマーを設定して再起動をトリガーし、ボタン 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 51 52 53 54 55
#include <M5Unified.h>
#include <M5PM1.h>
#include <Wire.h>

M5PM1 pm1;

void setup(void)
{
    M5.begin();
    M5.Display.setRotation(1);
    Serial.begin(115200);
    auto pin_num_sda = M5.getPin(m5::pin_name_t::in_i2c_sda);
    auto pin_num_scl = M5.getPin(m5::pin_name_t::in_i2c_scl);
    M5_LOGI("getPin: SDA:%u SCL:%u", pin_num_sda, pin_num_scl);
    Wire.end();
    Wire.begin(pin_num_sda, pin_num_scl, 100000U);

    // Initialize PM1
    m5pm1_err_t err = pm1.begin(&Wire, M5PM1_DEFAULT_ADDR, pin_num_sda, pin_num_scl, M5PM1_I2C_FREQ_100K);

    if (err == M5PM1_OK) {
        Serial.println("PM1 initialization successful");
    } else {
        Serial.printf("PM1 initialization failed, error code: %d\n", err);
    }
    M5.Display.fillScreen(BLACK);
    M5.Display.setTextSize(2);
    M5.Display.setTextColor(WHITE);
    M5.Display.setCursor(0, 10);
    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(0, 10);
        M5.Display.println("Shutdown");
        M5.Display.println("After 10s");
        M5.Display.println("Power ON");
        delay(1000);
        pm1.timerSet(10, M5PM1_TIM_ACTION_POWERON);
    }
    if (M5.BtnB.wasPressed()) {
        M5.Display.fillScreen(BLACK);
        M5.Display.setCursor(0, 10);
        M5.Display.println("After 10s");
        M5.Display.println("Power OFF");
        delay(1000);
        pm1.timerSet(10, M5PM1_TIM_ACTION_POWEROFF);
    }
}

4. IMU サンプルプログラム

BMI270 ドライバライブラリ

IMU M5PM1 ウェイクアップ

電源を L1 モードに切り替えると、デバイス全体で IMU と 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 86 87
#include <M5Unified.h>
#include <M5PM1.h>
#include <Wire.h>
#include "SparkFun_BMI270_Arduino_Library.h"

BMI270 imu;
M5PM1 pm1;

void setup(void)
{
    M5.begin();
    M5.Display.setRotation(1);
    Serial.begin(115200);
    auto pin_num_sda = M5.getPin(m5::pin_name_t::in_i2c_sda);
    auto pin_num_scl = M5.getPin(m5::pin_name_t::in_i2c_scl);
    M5_LOGI("getPin: SDA:%u SCL:%u", pin_num_sda, pin_num_scl);
    Wire.end();
    Wire.begin(pin_num_sda, pin_num_scl, 100000U);

    // Initialize PM1
    m5pm1_err_t err = pm1.begin(&Wire, M5PM1_DEFAULT_ADDR, pin_num_sda, pin_num_scl, 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) != 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::FreeMonoBold9pt7b);
    M5.Display.fillScreen(BLACK);
    M5.Display.setTextColor(WHITE);
    M5.Display.setCursor(0, 10);
    M5.Display.println("IMU Wakeup Test");
    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.fillScreen(BLACK);
        M5.Display.setCursor(0, 10);
        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 の G13 に接続されています。連鎖的なウェイクアップ信号を利用することで、ESP32-S3 のウェイクアップを実現できます。手順は以下の通りです:

  1. M5PM1 の PYG4 を入力モードに設定します。このピンは IMU の INT1 出力信号に接続されています。
  2. M5PM1 の PYG1_IRQ を IRQ 出力信号ピンとして設定します。PYG4(IMU からの中断信号が発生)ピンの状態が変化すると、PYG1_IRQ ピンが出力されます。
  3. ESP32-S3 をスリープ状態に設定し、ウェイクアップピンを G13 に設定します。
  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
#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();
    M5.Display.setRotation(1);
    Serial.begin(115200);
    auto pin_num_sda = M5.getPin(m5::pin_name_t::in_i2c_sda);
    auto pin_num_scl = M5.getPin(m5::pin_name_t::in_i2c_scl);
    M5_LOGI("getPin: SDA:%u SCL:%u", pin_num_sda, pin_num_scl);
    Wire.end();
    Wire.begin(pin_num_sda, pin_num_scl, 100000U);

    // Initialize PM1
    m5pm1_err_t err = pm1.begin(&Wire, M5PM1_DEFAULT_ADDR, pin_num_sda, pin_num_scl, 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);
    }

    // Check if sensor is connected and initialize
    // Address is optional (defaults to 0x68)
    while(imu.beginI2C(BMI2_I2C_PRIM_ADDR) != 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::FreeMonoBold9pt7b);
    M5.Display.fillScreen(BLACK);
    M5.Display.setTextColor(WHITE);
    M5.Display.setCursor(0, 10);
    M5.Display.println("IMU IRQ Test");
    M5.Display.println("Press BtnA to Sleep");
    M5.Display.println("Shake to wake up");
}

void ARDUINO_ISR_ATTR pm1_irq_handler()
{
    Serial.println("PM1 IRQ triggered");
    M5.Display.println("PM1 IRQ triggered");
}

void loop(void)
{
    M5.update();
    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;
        // 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.
        // 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(BLACK);
        M5.Display.setCursor(0, 10);
        M5.Display.println("Now Shake!");

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

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

デモ:

On This Page