StickS3 は内部に PY32 PMIC を統合しており、ハードウェア回路と組み合わせることで多段電源スイッチを実現しています。各電源レベルは、対応する周辺機器およびインターフェースの電源供給を制御します。ユーザーは動作要件に応じて異なる電源レベルを切り替え、未使用の周辺機器への電源供給を停止することで、システム全体の低消費電力化を実現できます。
M5PM1 を使用することで、PY32 PMIC のピン機能を非常に簡単に設定でき、低消費電力ウェイクアップや周辺機器の電源スイッチ制御に利用できます。
PMIC の起動および通電後、L0、L1、L2 は自動的に有効化されます(デフォルトで DCDC3V3_EN_PP、LDO3V3_EN_PP、CHG_EN_PP がオン)。M5Unified の初期化フローにおいて、さらに L3A、L3B が有効化され、その他の周辺機器への電源供給が行われます。
この電源レベルでは、バッテリーが PMIC への電源供給を維持し、その他の周辺機器への電源供給は停止可能です。バッテリー残量がある限り、この電源レベルは常に維持され、PMIC は基本的なボタンによる電源オン/オフ操作をサポートします。
IMU 周辺機器への電源供給を有効にします。このレベルの電源スイッチは PMIC の LDO3V3_EN_PP を使用し、以下の API でオン/オフ制御が可能です。
pm1.setLdoEnable(true); // L1 ON
pm1.setLdoEnable(false); // L1 OFF 同時に、IMU の INT1 割り込みピンは PMIC の PYG4 に接続されています。関連レジスタを設定することで、IMU への電源供給を維持したまま PMIC をスリープ状態に移行し、デバイスを反転させることで IMU 割り込みをトリガーして PMIC をウェイクアップできます。以下の API により、IMU(L1)電源保持を設定できます。
pm1.setLdoEnable(true);
pm1.ldoSetPowerHold(true);
pm1.setLedEnLevel(true);
pm1.shutdown(); この電源レベルでは、ESP32-S3、Grove インターフェース、Hat 拡張インターフェース、赤外線送受信、ボタンのプルアップ回路への電源供給が有効になります。
ESP32-S3 がスリープ状態のときは電源は L2 レベル、動作状態のときは L3A レベルとなります。
ESP32-S3 は PMIC をスリープ状態に制御することで、自身の電源供給を遮断(L2→L1/L0)することができます。
拡張インターフェースの入出力電源および赤外線回路の電源供給は、PMIC の EXT_5V_EN(BOOST5V_EN_PP)ピンを使用して別途制御する必要があります。
そのため、拡張インターフェースに外部センサーを接続する場合や赤外線送受信機能を使用する前に、周辺機器が通電状態であることを必ず確認してください。
M5Unified のデフォルト初期化では EXT_5V_EN は無効化され、Grove、Hat EXT_5V インターフェースおよび IR TX/RX の電源がオフになり、入力モードに切り替わります。この状態では外部 5V 電源入力が必要であり、IR TX/RX は外部電源がないと正常に動作しません。外部電源を使用しない場合は、以下の API により EXT_5V 出力モードを再度有効化し、IR TX/RX への電源供給を復旧できます。
M5.Power.setExtOutput(true); // EXT_5V OUTPUT
// M5.Power.setExtOutput(false); // EXT_5V INPUT この電源レベルでは、すべての周辺機器への電源供給が有効になります。M5Unified のデフォルト初期化ではこのレベルが有効化され、LCD バックライト、MIC、SPK が含まれます。この電源スイッチは PMIC の 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); デバイスのスピーカーアンプのスイッチは、PMIC の PYG3 ピンによって制御されます。この部分は M5Unified API または M5PM1 API により有効化/無効化が可能です。
M5.Speaker.begin();
// M5.Speaker.end(); または
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); PMIC はプログラムによって手動でスリープ状態に移行でき、システム全体の消費電力を低減できます。デフォルト状態では、スリープを設定すると電源は L0 レベルに戻り、この時点では PMIC のみが給電されます。
pm1.shutdown(); 一部の特殊な使用シナリオ(例:IMU ウェイクアップ、ESP32-S3 SoC スリープ)では、PMIC をスリープ状態にしつつ、特定の電源レベルの周辺機器への電源供給を維持し、ウェイクアップ源や状態保持に使用することができます。
このようなアプリケーションでは、PMIC をスリープモードに移行する前に、対応する電源レベルのスイッチピン状態を設定し、状態保持を有効化する必要があります。
PMIC は I2C 通信がアイドル状態のときに自動的にスリープへ移行する設定をサポートしており、システム全体の消費電力を低減できます。スリープ状態に入った後、ESP32-S3 と PMIC の最初の通信は PMIC のウェイクアップに使用されるため通信は失敗し、有効な通信はウェイクアップ後の次回通信となります。
m5pm1_err_t setI2cSleepTime(uint8_t seconds); PMIC はタイマー機能の設定をサポートしており、カウントダウン終了後に電源投入、電源遮断、リセットなどの対応する操作を実行できます。
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 を押すと 10 秒タイマーを設定してリブートをトリガーします。ボタン B を押すと、10 秒後に PMIC がシャットダウンするように設定します(電源ボタンを押すことでデバイスを再起動できます)。
#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);
}
}サンプルプログラムの PlatformIO ソースコードプロジェクトファイル:
電源を L1 モードに切り替えると、システム全体では IMU と PMIC のみが給電状態になります。IMU ウェイクアップ機能を設定後、PMIC もスリープ状態に入り、同時に L1 の出力電源(3V3_L1_EN)を維持して IMU の動作を継続します。
この状態でデバイスを反転または移動させると、IMU のウェイクアップ信号がトリガーされ、PMIC がウェイクアップして再起動します。
PMIC がウェイクアップすると、L0、L1、L2 の通電シーケンスが再実行され、ESP32-S3 は再度初期化されます。
サンプル説明:デバイス起動後、ボタン A を単クリックすると IMU 割り込みモードと PMIC L1 電源保持が設定され、PMIC がスリープ状態に入ります。この状態でデバイスを反転または移動させると PMIC がウェイクアップし、ESP32-S3 が再起動します。
#include <M5Unified.h>
#include <M5PM1.h>
#include <Wire.h>
#include "./bmi270/src/bmi270.h"
static BMI270_Class* bmi270 = nullptr;
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);
}
// Initialize BMI270
bmi270 = new BMI270_Class();
bmi270->setWire(&Wire);
if (bmi270->init()) {
Serial.println("BMI270 initialization successful");
M5.Display.fillScreen(BLACK);
M5.Display.setTextSize(2);
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");
} else {
Serial.println("BMI270 initialization failed");
delete bmi270;
bmi270 = nullptr;
}
}
void loop(void)
{
M5.update();
if (M5.BtnA.wasPressed()) {
// Configure BMI270 any motion interrupt
if (bmi270 != nullptr) {
if (bmi270->enableAnyMotionInterrupt(0xA0, 0x0A)) {
Serial.println("BMI270 AnyMotionInterrupt enabled successfully");
} else {
Serial.println("Failed to enable BMI270 AnyMotionInterrupt");
}
} else {
Serial.println("BMI270 not initialized");
}
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();
}
}PMIC の PYG1_IRQ ピンは回路上で ESP32-S3 の G13 に接続されており、チェーン型のウェイクアップ信号によって ESP32-S3 をウェイクアップできます。手順は以下のとおりです。
サンプル説明:デバイス起動後、ボタン A を単クリックして IMU 割り込みモードを設定します。デバイスを反転または移動すると GPIO 割り込みハンドラがトリガーされます。再度ボタン A を単クリックすると PMIC の IRQ フラグがクリアされ、再テストが可能です。
#include <M5Unified.h>
#include <M5PM1.h>
#include <Wire.h>
#include "./bmi270/src/bmi270.h"
#include "driver/rtc_io.h"
static BMI270_Class* bmi270 = nullptr;
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);
// 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);
}
// Initialize BMI270
bmi270 = new BMI270_Class();
bmi270->setWire(&Wire);
if (bmi270->init()) {
Serial.println("BMI270 initialization successful");
M5.Display.fillScreen(BLACK);
M5.Display.setTextSize(2);
M5.Display.setTextColor(WHITE);
M5.Display.setCursor(0, 10);
M5.Display.println("IMU IRQ Test");
M5.Display.println("Press BtnA to Setup");
M5.Display.println("Then Shake");
} else {
Serial.println("BMI270 initialization failed");
delete bmi270;
bmi270 = nullptr;
}
}
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();
// Configure BMI270 any motion interrupt
if (bmi270 != nullptr) {
if (bmi270->enableAnyMotionInterrupt(0xA0, 0x0A)) {
Serial.println("BMI270 AnyMotionInterrupt enabled successfully");
} else {
Serial.println("Failed to enable BMI270 AnyMotionInterrupt");
}
} else {
Serial.println("BMI270 not initialized");
}
M5.Display.fillScreen(BLACK);
M5.Display.setCursor(0, 10);
M5.Display.println("Now Shake!");
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();
}
}