StickS3 は内部に M5PM1 を統合しており、ハードウェア回路と組み合わせることでマルチレベルの電源スイッチを実現しています。異なるレベルの電源スイッチは、関連する周辺機器やインターフェースへの電源供給を制御します。ユーザーは動作要件に応じて、異なるレベルの電源を有効にしたり、使用していない周辺機器への供給を停止したりすることで、デバイス全体の低消費電力を実現できます。
M5PM1 ドライバライブラリを使用することで、M5PM1 のピン機能を非常に簡単に設定でき、低電力ウェイクアップや周辺機器の電源スイッチに使用できます。
M5PM1 の起動・通電後、L0、L1、L2 は自動的にオンになります(デフォルトで DCDC3V3_EN_PP, LDO3V3_EN_PP, CHG_EN_PP が有効になります)。M5Unified の初期化プロセスにおいて、さらに L3A、L3B がオンになり、他の周辺機器への電源供給が有効になります。
この電源レベルでは、バッテリーから M5PM1 への電源供給が維持され、その他の周辺機器への供給は停止できます。バッテリー残量がある限り、この層の電源は維持され、M5PM1 は基本的なボタン操作による電源 ON/OFF をサポートします。
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(); この電源レベルでは、ESP32-S3、Grove インターフェース、Hat 拡張インターフェース、赤外線送受信、ボタンのプルアップ部分の電源が有効になります。
ESP32-S3 がスリープ状態のとき、電源は L2 レベルになります。ESP32-S3 が動作状態のとき、電源は L3A レベルになります。
ESP32-S3 は、M5PM1 をスリープ状態に制御することで、自身の電源を遮断(L2 -> L1/L0)できます。
そのうち、拡張インターフェースの入力 / 出力電源および赤外線回路の電源は、別途 M5PM1 の EXT_5V_EN (BOOST5V_EN_PP) ピンを使用してスイッチ制御を行う必要があります。
したがって、拡張インターフェースに外部センサーを接続する場合や、赤外線送受信機能を使用する前には、周辺機器が通電状態であることを確認してください。
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 この電源レベルでは、すべての周辺機器への電源供給をオンにします。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 パワーアンプスイッチは、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パワーアンプをオフにする M5PM1 は、プログラムによって手動でスリープ状態に制御し、デバイス全体の消費電力を低減できます。デフォルトの状態では、スリープを設定すると L0 レベルの電源まで戻り、M5PM1 のみが通電を維持します。
pm1.shutdown(); 一部の特殊な使用シーン(例:IMU ウェイクアップや ESP32-S3 SoC スリープ)では、M5PM1 をスリープさせて消費電力を抑えつつ、ウェイクアップソースや状態保持のために一部のレベルの周辺機器への電源供給を維持することが可能です。
このような用途では、M5PM1 がスリープモードに入る前に、対応するレベルの電源スイッチピンの状態を設定し、状態保持(Power Hold)を有効にする必要があります。
M5PM1 は、I2C 通信が一定時間行われない場合に自動的にスリープ状態に移行する設定をサポートしており、デバイス全体の消費電力を低減できます。スリープ状態に移行した後、ESP32-S3 と M5PM1 の間の最初の通信は M5PM1 のウェイクアップに使用されるため、その通信自体は失敗します。有効な通信は、ウェイクアップ後の次の通信からとなります。
m5pm1_err_t setI2cSleepTime(uint8_t seconds); 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; 例の説明:デバイスの電源が入った後、ボタン A をクリックすると 10 秒タイマーを設定して再起動をトリガーし、ボタン B をクリックすると 10 秒後に M5PM1 の電源 OFF をトリガーします(電源ボタンをクリックすることで再度電源を入れることができます)。
#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);
}
}電源を 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 が再通電されます。
#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();
}
}デモ:
M5PM1 の PYG1_IRQ ピンは、回路上で ESP32-S3 の G13 に接続されています。連鎖的なウェイクアップ信号を利用することで、ESP32-S3 のウェイクアップを実現できます。手順は以下の通りです:
サンプルの説明:デバイスの電源が入った後、ボタン A をクリックして IMU 中断モードを設定します。デバイスをひっくり返したり動かしたりすることで、GPIO 中断処理関数がトリガーされます。再度ボタン A をクリックすると、M5PM1 の IRQ フラグビットがクリアされ、再テストが可能になります。
#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();
}
}デモ: