From 9ce35affe57513340af95d12e5aab6bf37c1af91 Mon Sep 17 00:00:00 2001 From: Tinyu Date: Fri, 17 Jul 2026 14:36:18 +0800 Subject: [PATCH 1/4] Add M5PM1 IRQ APIs --- src/utility/power/M5PM1_Class.cpp | 63 +++++++++++++++++++++++++++++++ src/utility/power/M5PM1_Class.hpp | 34 +++++++++++++++++ 2 files changed, 97 insertions(+) diff --git a/src/utility/power/M5PM1_Class.cpp b/src/utility/power/M5PM1_Class.cpp index be0d410..7848dbc 100644 --- a/src/utility/power/M5PM1_Class.cpp +++ b/src/utility/power/M5PM1_Class.cpp @@ -14,6 +14,7 @@ namespace m5 // M5PM1 registers static constexpr const uint8_t M5PM1_REG_DEVICE_ID = 0x00; static constexpr const uint8_t M5PM1_REG_PWR_SRC = 0x04; + static constexpr const uint8_t M5PM1_REG_WAKE_SRC = 0x05; static constexpr const uint8_t M5PM1_REG_PWR_CFG = 0x06; static constexpr const uint8_t M5PM1_REG_I2C_CFG = 0x09; static constexpr const uint8_t M5PM1_REG_WDT_CNT = 0x0A; @@ -22,12 +23,19 @@ namespace m5 static constexpr const uint8_t M5PM1_REG_GPIO_OUT = 0x11; static constexpr const uint8_t M5PM1_REG_GPIO_IN = 0x12; static constexpr const uint8_t M5PM1_REG_GPIO_DRV = 0x13; + static constexpr const uint8_t M5PM1_REG_GPIO_PUPD0 = 0x14; + static constexpr const uint8_t M5PM1_REG_GPIO_PUPD1 = 0x15; static constexpr const uint8_t M5PM1_REG_GPIO_FUNC0 = 0x16; static constexpr const uint8_t M5PM1_REG_GPIO_FUNC1 = 0x17; static constexpr const uint8_t M5PM1_REG_VBAT_L = 0x22; static constexpr const uint8_t M5PM1_REG_VIN_L = 0x24; static constexpr const uint8_t M5PM1_REG_5VOUT_L = 0x26; + static constexpr const uint8_t M5PM1_REG_IRQ_STATUS1 = 0x40; + static constexpr const uint8_t M5PM1_REG_IRQ_STATUS2 = 0x41; static constexpr const uint8_t M5PM1_REG_IRQ_STATUS3 = 0x42; + static constexpr const uint8_t M5PM1_REG_IRQ_MASK1 = 0x43; + static constexpr const uint8_t M5PM1_REG_IRQ_MASK2 = 0x44; + static constexpr const uint8_t M5PM1_REG_IRQ_MASK3 = 0x45; static constexpr const uint8_t M5PM1_PWR_CFG_CHG_EN = 1 << 0; static constexpr const uint8_t M5PM1_PWR_CFG_LDO_EN = 1 << 2; @@ -104,6 +112,18 @@ namespace m5 : bitOff(M5PM1_REG_GPIO_MODE, mask); } + bool M5PM1_Class::setGPIOPull(gpio_t pin, gpio_pull_t pull) + { + if (!is_valid_gpio(pin)) { return false; } + auto num = gpio_num(pin); + auto reg = num < 4 ? M5PM1_REG_GPIO_PUPD0 : M5PM1_REG_GPIO_PUPD1; + auto shift = static_cast((num < 4 ? num : num - 4) * 2); + std::uint8_t mask = 0x03 << shift; + std::uint8_t reg_val = readRegister8(reg); + reg_val = (reg_val & ~mask) | (static_cast(pull) << shift); + return writeRegister8(reg, reg_val); + } + bool M5PM1_Class::setGPIODrive(gpio_t pin, gpio_drive_t drive) { if (!is_valid_gpio(pin)) { return false; } @@ -131,6 +151,49 @@ namespace m5 return readRegister8(M5PM1_REG_GPIO_OUT) & (1 << gpio_num(pin)); } + bool M5PM1_Class::clearWakeSource(std::uint8_t mask) + { + auto src = readRegister8(M5PM1_REG_WAKE_SRC); + return writeRegister8(M5PM1_REG_WAKE_SRC, src & ~mask); + } + + bool M5PM1_Class::clearGPIOIRQStatus(void) + { + return writeRegister8(M5PM1_REG_IRQ_STATUS1, 0x00); + } + + bool M5PM1_Class::clearSystemIRQStatus(void) + { + return writeRegister8(M5PM1_REG_IRQ_STATUS2, 0x00); + } + + bool M5PM1_Class::clearButtonIRQStatus(void) + { + return writeRegister8(M5PM1_REG_IRQ_STATUS3, 0x00); + } + + bool M5PM1_Class::clearIRQStatus(void) + { + return clearGPIOIRQStatus() + && clearSystemIRQStatus() + && clearButtonIRQStatus(); + } + + bool M5PM1_Class::setGPIOIRQMaskBits(std::uint8_t mask) + { + return writeRegister8(M5PM1_REG_IRQ_MASK1, mask & 0x1F); + } + + bool M5PM1_Class::setSystemIRQMaskBits(std::uint8_t mask) + { + return writeRegister8(M5PM1_REG_IRQ_MASK2, mask & 0x3F); + } + + bool M5PM1_Class::setButtonIRQMaskBits(std::uint8_t mask) + { + return writeRegister8(M5PM1_REG_IRQ_MASK3, mask & 0x07); + } + bool M5PM1_Class::setBatteryCharge(bool enable) { return enable ? bitOn(M5PM1_REG_PWR_CFG, M5PM1_PWR_CFG_CHG_EN) diff --git a/src/utility/power/M5PM1_Class.hpp b/src/utility/power/M5PM1_Class.hpp index c70dd6e..9633fbb 100644 --- a/src/utility/power/M5PM1_Class.hpp +++ b/src/utility/power/M5PM1_Class.hpp @@ -42,6 +42,13 @@ namespace m5 , special = 0b11 }; + /// PM1 GPIO pull-up/pull-down setting. + enum gpio_pull_t : std::uint8_t + { pull_none = 0b00 + , pull_up = 0b01 + , pull_down = 0b10 + }; + /// PM1 GPIO output driver type. enum gpio_drive_t : std::uint8_t { push_pull = 0 @@ -76,6 +83,9 @@ namespace m5 /// set PM1 GPIO direction. bool setGPIOMode(gpio_t pin, gpio_mode_t mode); + /// set PM1 GPIO pull-up/pull-down setting. + bool setGPIOPull(gpio_t pin, gpio_pull_t pull); + /// set PM1 GPIO output driver type. bool setGPIODrive(gpio_t pin, gpio_drive_t drive); @@ -88,6 +98,30 @@ namespace m5 /// get PM1 GPIO output latch level, not the physical input level. bool getGPIOOutputLatch(gpio_t pin); + /// clear PM1 wake source bits selected by mask. + bool clearWakeSource(std::uint8_t mask = 0x7F); + + /// clear all PM1 GPIO IRQ status bits. + bool clearGPIOIRQStatus(void); + + /// clear all PM1 system IRQ status bits. + bool clearSystemIRQStatus(void); + + /// clear all PM1 button IRQ status bits. + bool clearButtonIRQStatus(void); + + /// clear all PM1 IRQ status bits. + bool clearIRQStatus(void); + + /// set PM1 GPIO IRQ mask register. bit=1 disables interrupt. + bool setGPIOIRQMaskBits(std::uint8_t mask); + + /// set PM1 system IRQ mask register. bit=1 disables interrupt. + bool setSystemIRQMaskBits(std::uint8_t mask); + + /// set PM1 button IRQ mask register. bit=1 disables interrupt. + bool setButtonIRQMaskBits(std::uint8_t mask); + /// set battery charge enable. /// @param enable true=enable / false=disable bool setBatteryCharge(bool enable); From d77ab53cb6f0686f0c84a99d0bf27f493eef24c8 Mon Sep 17 00:00:00 2001 From: Tinyu Date: Fri, 17 Jul 2026 14:39:29 +0800 Subject: [PATCH 2/4] Handle M5PM1 RTC IRQ state --- src/utility/Power_Class.cpp | 15 +++++++++++++++ src/utility/RTC_Class.cpp | 26 ++++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/utility/Power_Class.cpp b/src/utility/Power_Class.cpp index 4be4353..08f9466 100644 --- a/src/utility/Power_Class.cpp +++ b/src/utility/Power_Class.cpp @@ -258,6 +258,21 @@ namespace m5 case board_t::board_M5PaperMono: _rtcIntPin = GPIO_NUM_1; _pmic = pmic_t::pmic_m5pm1; + _wakeupPin = GPIO_NUM_1; // PY IQR + + M5pm1.clearWakeSource(); + M5pm1.clearIRQStatus(); + M5pm1.setGPIOIRQMaskBits(0x1E); // enable GPIO0 interrupt, disable other GPIO IRQ + + M5pm1.setGPIOFunction(M5PM1_Class::gpio0, M5PM1_Class::gpio); + M5pm1.setGPIOMode(M5PM1_Class::gpio0, M5PM1_Class::input); + + M5pm1.setGPIOMode(M5PM1_Class::gpio1, M5PM1_Class::output); + M5pm1.setGPIODrive(M5PM1_Class::gpio1, M5PM1_Class::push_pull); + M5pm1.setGPIOPull(M5PM1_Class::gpio1, M5PM1_Class::pull_up); + M5pm1.setGPIOOutput(M5PM1_Class::gpio1, true); + M5pm1.setGPIOFunction(M5PM1_Class::gpio1, M5PM1_Class::irq); + // M5PaperMono charging is controlled by the IP2316 charger (not PM1). // Enable IP2316 readout/control by driving IOE1 IO11 ("CHARGE READ") high. // IP2316 stays off the I2C bus while IO11 is low, and answers ~1.3ms after high diff --git a/src/utility/RTC_Class.cpp b/src/utility/RTC_Class.cpp index 11472a9..766fcf5 100644 --- a/src/utility/RTC_Class.cpp +++ b/src/utility/RTC_Class.cpp @@ -19,6 +19,20 @@ namespace m5 { +#if defined (CONFIG_IDF_TARGET_ESP32S3) + static void clear_m5pm1_rtc_irq(void) + { + if (M5.getBoard() == board_t::board_M5PaperMono + && M5.Power.getType() == Power_Class::pmic_t::pmic_m5pm1) + { + M5.Power.M5pm1.clearWakeSource(); + M5.Power.M5pm1.clearIRQStatus(); + } + } +#else + static void clear_m5pm1_rtc_irq(void) {} +#endif + bool RTC_Class::begin(I2C_Class* i2c, board_t board) { if (i2c) @@ -114,7 +128,10 @@ namespace m5 std::uint32_t RTC_Class::setTimerIRQ(std::uint32_t timer_msec) { - return _rtc_instance ? _rtc_instance->setTimerIRQ(timer_msec) : 0; + if (!_rtc_instance) { return 0; } + auto result = _rtc_instance->setTimerIRQ(timer_msec); + clear_m5pm1_rtc_irq(); + return result; } int RTC_Class::setAlarmIRQ(const tm* datetime) @@ -129,7 +146,10 @@ namespace m5 int RTC_Class::setAlarmIRQ(const rtc_date_t* date, const rtc_time_t* time) { - return _rtc_instance ? _rtc_instance->setAlarmIRQ(date, time) : -1; + if (!_rtc_instance) { return -1; } + auto result = _rtc_instance->setAlarmIRQ(date, time); + clear_m5pm1_rtc_irq(); + return result; } bool RTC_Class::getIRQstatus(void) @@ -141,12 +161,14 @@ namespace m5 { if (!_rtc_instance) { return; } _rtc_instance->clearIRQ(); + clear_m5pm1_rtc_irq(); } void RTC_Class::disableIRQ(void) { if (!_rtc_instance) { return; } _rtc_instance->disableIRQ(); + clear_m5pm1_rtc_irq(); } void RTC_Class::setSystemTimeFromRtc(struct timezone* tz) From 0b21eea68a7802f8d98d2eebeecdabb41e664a25 Mon Sep 17 00:00:00 2001 From: Tinyu Date: Fri, 17 Jul 2026 15:02:12 +0800 Subject: [PATCH 3/4] Control PaperMono IP2315 on demand --- src/utility/Power_Class.cpp | 59 ++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/src/utility/Power_Class.cpp b/src/utility/Power_Class.cpp index 08f9466..7a8c6e8 100644 --- a/src/utility/Power_Class.cpp +++ b/src/utility/Power_Class.cpp @@ -39,6 +39,29 @@ namespace m5 static constexpr uint8_t ip2315_i2c_addr = 0x75; // M5PaperMono USB fast-charger static constexpr int M5PaperS3_CHG_STAT_PIN = GPIO_NUM_4; + static void init_papermono_ip2315_access(void) + { + auto& ioe1 = M5.getIOExpander(0); + ioe1.setHighImpedance(M5IOE1_Class::gpio11, false); + ioe1.setDirection(M5IOE1_Class::gpio11, true); + ioe1.digitalWrite(M5IOE1_Class::gpio11, false); + } + + static void set_papermono_ip2315_enabled(bool enable) + { + M5.getIOExpander(0).digitalWrite(M5IOE1_Class::gpio11, enable); + } + + static bool wait_papermono_ip2315_ready(void) + { + m5gfx::delay(2); + for (int i = 0; i < 64; ++i) + { + if (M5.In_I2C.scanID(ip2315_i2c_addr, i2c_freq)) { return true; } + } + return false; + } + #elif defined (CONFIG_IDF_TARGET_ESP32C6) static constexpr int M5NanoC6_LED_PIN = GPIO_NUM_7; @@ -273,20 +296,8 @@ namespace m5 M5pm1.setGPIOOutput(M5PM1_Class::gpio1, true); M5pm1.setGPIOFunction(M5PM1_Class::gpio1, M5PM1_Class::irq); - // M5PaperMono charging is controlled by the IP2316 charger (not PM1). - // Enable IP2316 readout/control by driving IOE1 IO11 ("CHARGE READ") high. - // IP2316 stays off the I2C bus while IO11 is low, and answers ~1.3ms after high - // (measured), so polling its address is enough; no fixed startup delay is needed. - // IO11 = bit10 of the 16-bit GPIO regs = bit2 of the high byte (P14-P9). - { - auto& ioe1 = M5.getIOExpander(0); - ioe1.setHighImpedance(M5IOE1_Class::gpio11, false); - ioe1.setDirection(M5IOE1_Class::gpio11, true); - ioe1.digitalWrite(M5IOE1_Class::gpio11, true); - } - // Wait for the IP2316 to wake, then enable battery charging (SYS_CTL1 0x01 bit0 = EN_CHG). - for (int i = 0; i < 64 && !M5.In_I2C.scanID(ip2315_i2c_addr, i2c_freq); ++i) {} - M5.In_I2C.bitOn(ip2315_i2c_addr, 0x01, 1 << 0, i2c_freq); + // Keep IP2316 off the I2C bus until charge control/status is requested. + init_papermono_ip2315_access(); break; case board_t::board_M5Capsule: @@ -1628,10 +1639,13 @@ namespace m5 return; } // M5PaperMono: charging is controlled by the IP2316 charger, not PM1. - // IP2316 SYS_CTL1 (0x01) bit0 = EN_CHG. (IO11 was driven high in begin().) if (M5.getBoard() == board_t::board_M5PaperMono) { - if (enable) { M5.In_I2C.bitOn (ip2315_i2c_addr, 0x01, 1 << 0, i2c_freq); } - else { M5.In_I2C.bitOff(ip2315_i2c_addr, 0x01, 1 << 0, i2c_freq); } + set_papermono_ip2315_enabled(true); + if (wait_papermono_ip2315_ready()) { + if (enable) { M5.In_I2C.bitOn (ip2315_i2c_addr, 0x01, 1 << 0, i2c_freq); } + else { M5.In_I2C.bitOff(ip2315_i2c_addr, 0x01, 1 << 0, i2c_freq); } + } + set_papermono_ip2315_enabled(false); return; } M5pm1.setBatteryCharge(enable); @@ -1871,15 +1885,18 @@ namespace m5 { // Running from battery (no external power) -> not charging. if (M5pm1.getPowerSource() == M5PM1_Class::battery) { return is_charging_t::is_discharging; } - // External power present. The IP2316 charger (IO11 enabled in begin()) reports + // External power present. The IP2316 charger reports // its state in REG_CHG_STAT(0xC7): bit7 = charging in progress (measured: // 0x82 charging / 0x45 charge-complete / 0x00 charge-disabled). - if (M5.In_I2C.scanID(ip2315_i2c_addr, i2c_freq)) + set_papermono_ip2315_enabled(true); + is_charging_t res = is_charging_t::is_discharging; + if (wait_papermono_ip2315_ready()) { uint8_t chg_stat = M5.In_I2C.readRegister8(ip2315_i2c_addr, 0xC7, i2c_freq); - return (chg_stat & (1 << 7)) ? is_charging_t::is_charging : is_charging_t::is_discharging; + res = (chg_stat & (1 << 7)) ? is_charging_t::is_charging : is_charging_t::is_discharging; } - return is_charging_t::is_discharging; // fallback: charger not responding -> not charging + set_papermono_ip2315_enabled(false); + return res; } break; From bdad52ecb4ca6488861039675a94f1df233727d8 Mon Sep 17 00:00:00 2001 From: Tinyu Date: Fri, 17 Jul 2026 15:34:26 +0800 Subject: [PATCH 4/4] Update PaperMono LED IOE1 control --- src/utility/led/LED_PaperMono_Class.cpp | 55 ++++++++++--------------- 1 file changed, 21 insertions(+), 34 deletions(-) diff --git a/src/utility/led/LED_PaperMono_Class.cpp b/src/utility/led/LED_PaperMono_Class.cpp index 444d390..8650fdd 100644 --- a/src/utility/led/LED_PaperMono_Class.cpp +++ b/src/utility/led/LED_PaperMono_Class.cpp @@ -3,6 +3,7 @@ #include "LED_PaperMono_Class.hpp" #include "../../M5Unified.hpp" +#include "../M5IOE1_Class.hpp" #if defined (CONFIG_IDF_TARGET_ESP32S3) @@ -10,35 +11,35 @@ namespace m5 { // RGBLED_R = PMIC -> LED_EN_PP // RGBLED_G = PYB -> G8 -// RGBLED_B = PYB -> G2 +// RGBLED_B = PYB -> G9 static constexpr size_t led_count = 1; static constexpr uint8_t m5pm1_i2c_addr = 0x6E; - static constexpr uint8_t m5ioe1_i2c_addr = 0x4F; static constexpr uint32_t i2c_freq = 100000; + static constexpr auto ioe1_led_g_pin = M5IOE1_Class::gpio8; + static constexpr auto ioe1_led_b_pin = M5IOE1_Class::gpio9; bool LED_PaperMono_Class::begin(void) { // PM1 LED_EN (red), set PP mode and enable LED output M5.In_I2C.bitOff(m5pm1_i2c_addr, 0x13, 0x20, i2c_freq); - // IOE1 IO2 (blue), IO8 (green) output - M5.In_I2C.bitOn(m5ioe1_i2c_addr, 0x03, 0x82, i2c_freq); + auto& ioe1 = static_cast(M5.getIOExpander(0)); + ioe1.setDirection(ioe1_led_g_pin, true); + ioe1.setDirection(ioe1_led_b_pin, true); + ioe1.setHighImpedance(ioe1_led_g_pin, false); + ioe1.setHighImpedance(ioe1_led_b_pin, false); - // IOE1 IO2 (blue), IO8 (green) push-pull - M5.In_I2C.bitOff(m5ioe1_i2c_addr, 0x13, 0x82, i2c_freq); - - - { - setBrightness(_brightness); - return true; - } - return false; + setBrightness(_brightness); + return true; } void LED_PaperMono_Class::setColors(const RGBColor* values, size_t index, size_t length) { - if (index + length > led_count) { + if (index >= led_count) { + return; + } + if (length > led_count - index) { length = led_count - index; } std::copy(values, values + length, &_rgb_buffer + index); @@ -47,16 +48,13 @@ namespace m5 void LED_PaperMono_Class::setBrightness(const uint8_t brightness) { _brightness = brightness; - std::array br_buffer; - br_buffer.fill(brightness); - // writeRegister(0x80, br_buffer.data(), br_buffer.size()); } void LED_PaperMono_Class::display(void) { // RED = PMIC -> LED_EN_PP // GREEN = PYB -> G8 - // BLUE = PYB -> G2 + // BLUE = PYB -> G9 uint32_t br = _brightness + 1; br = br * br; @@ -73,23 +71,12 @@ namespace m5 M5.In_I2C.bitOn(m5pm1_i2c_addr, 0x06, 0x10, i2c_freq); } - if (g < 2048) { - M5.In_I2C.bitOff(m5ioe1_i2c_addr, 0x05, 0x80, i2c_freq); - } else { - M5.In_I2C.bitOn(m5ioe1_i2c_addr, 0x05, 0x80, i2c_freq); - } - - if (b < 2048) { - M5.In_I2C.bitOff(m5ioe1_i2c_addr, 0x05, 0x02, i2c_freq); - } else { - M5.In_I2C.bitOn(m5ioe1_i2c_addr, 0x05, 0x02, i2c_freq); - } + auto& ioe1 = static_cast(M5.getIOExpander(0)); + ioe1.digitalWrite(ioe1_led_g_pin, g >= 2048); + ioe1.digitalWrite(ioe1_led_b_pin, b >= 2048); - { // PWM2 (IO8) for green - if (g > 4095) g = 4095; - uint8_t data[2] = { uint8_t(g & 0xFF), uint8_t((g >> 8) | 0x80) }; - M5.In_I2C.writeRegister(m5ioe1_i2c_addr, 0x1D, data, sizeof(data), i2c_freq); - } + if (g > 4095) { g = 4095; } + ioe1.setPwmDuty(M5IOE1_Class::pwm_ch2, g, g > 0); } }