PaperMono は M5PM1 と M5IOE1 を内蔵し、ハードウェア回路と組み合わせて多段電源スイッチを実現しています。各電源レベルは、対応する周辺機器やインターフェースへの電源供給を制御します。動作要件に応じて電源レベルを切り替え、使用していない周辺機器を停止することで、システム全体の消費電力を削減できます。
M5PM1 / M5IOE1 ドライバライブラリを使用すると、M5PM1 / M5IOE1 のピン機能を簡単に設定し、低消費電力復帰や周辺機器の電源切り替えに利用できます。
この電源レベルでは、バッテリーから M5PM1 と RTC への電源供給が維持されます。バッテリー残量がある限り、この電源レベルは常に保持されます。M5PM1 は、ボタンによる基本的な電源オン / オフ操作と RTC 割り込みによる復帰に対応します。
この電源レベルでは、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(); この電源レベルでは、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 レベルは、画面、タッチコントローラー、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 |
M5PM1 はプログラムから手動でスリープ状態に移行でき、システム全体の消費電力を削減できます。デフォルトでは、スリープを直接設定すると電源は L0 レベルに戻り、M5PM1 のみが給電された状態になります。
pm1.shutdown(); IMU による復帰や ESP32-S3 SoC のスリープなど、特定の用途では、M5PM1 をスリープ状態にしながら一部の電源レベルへの供給を維持し、復帰元や状態保持に使用できます。
このような用途では、M5PM1 がスリープモードに入る前に、必要な電源レベルのスイッチピン状態を設定し、状態保持を有効にする必要があります。
M5PM1 は、I2C 通信がアイドル状態のときに自動的にスリープへ移行するよう設定でき、システム全体の消費電力を削減できます。スリープ状態に入った後、ESP32-S3 から M5PM1 への最初の通信は M5PM1 の復帰に使用されるため失敗します。有効な通信は、復帰後の次の通信で完了します。
m5pm1_err_t setI2cSleepTime(uint8_t seconds); 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 の電源をオフにするタイマーを設定します。電源オフ後は、電源ボタンをクリックして再度電源を入れられます。
#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 秒後の電源オフ動作が設定され、カウントダウンが終了するまでデバイスは動作を続けます。電源オフ後は、電源ボタンで再度電源を入れられます。
電源を 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 の電源が再び入ります。
#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() から実行を開始します。
M5PM1 の PYG1_IRQ ピンは回路上で ESP32-S3 の G1 に接続されており、連鎖した復帰信号によって 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)
{
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() を使用してください。
電源を 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 の電源が再び入ります。
#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 の復帰手順は、IMU による復帰と同様です。RTC による復帰では BMI270 の割り込み設定は不要ですが、RTC IRQ → M5PM1 GPIO → PYG1_IRQ → ESP32-S3 G1 の信号経路を使用します。
#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() から再実行されます。
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 がすでに使用しています。変更する前に、対応する周辺機器が停止していることを確認してください。そうしないと、周辺機器が動作しなくなったり、再初期化が必要になったりする場合があります。
#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 の緑と青のチャンネル制御を順番に実演します。