From c938849ee078257ba6fa554d63e264d2f9266315 Mon Sep 17 00:00:00 2001 From: luoweiyuan Date: Tue, 7 Jul 2026 17:18:00 +0800 Subject: [PATCH 01/16] Add M5StampS3Mini board support --- src/M5Unified.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/M5Unified.cpp b/src/M5Unified.cpp index 85483f0..09a5bb3 100644 --- a/src/M5Unified.cpp +++ b/src/M5Unified.cpp @@ -1492,21 +1492,29 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { case 1: // EFUSE_PKG_VERSION_ESP32S3PICO: // LGA56 if (board == board_t::board_unknown) { - /// AtomEchoS3R ? - if(_detect_i2c_device(45, 0, 0x18)) { - board = board_t::board_M5AtomVoiceS3R; + /// AtomS3RExt / AtomS3RCam have a BMI270 on the internal I2C bus. + if (_detect_i2c_device(45, 0, 0x68) + || _detect_i2c_device(45, 0, 0x69)) { + board = board_t::board_M5AtomS3RExt; } /// Stamp-S3Bat ? else if (_detect_i2c_device(48, 47, 0x6E)) { board = board_t::board_M5StampS3Bat; } + /// AtomEchoS3R ? + else if(_detect_i2c_device(45, 0, 0x18)) { + board = board_t::board_M5AtomVoiceS3R; + } + /// StampS3Mini has no other onboard device that can identify it. + else { + board = board_t::board_M5StampS3Mini; + } } - if (board == board_t::board_unknown) + if (board == board_t::board_M5AtomS3RExt) { /// AtomS3RCam or AtomS3RExt ? // Cam = GC0308 = I2C 7bit addr = 0x21 // CamM12 = OV3660 = I2C 7bit addr = 0x3C - board = board_t::board_M5AtomS3RExt; m5gfx::gpio_lo(GPIO_NUM_18); m5gfx::pinMode(GPIO_NUM_18, m5gfx::pin_mode_t::output); m5gfx::gpio::pin_backup_t pin_backup[] = { GPIO_NUM_9, GPIO_NUM_12, GPIO_NUM_21 }; From 8fb505423e0c9be746278f8b6fc7bc065421a557 Mon Sep 17 00:00:00 2001 From: lovyan03 <42724151+lovyan03@users.noreply.github.com> Date: Wed, 15 Jul 2026 16:04:56 +0900 Subject: [PATCH 02/16] Update DAC volume and speaker configuration for StopWatch --- src/M5Unified.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/M5Unified.cpp b/src/M5Unified.cpp index 09a5bb3..17f4ff5 100644 --- a/src/M5Unified.cpp +++ b/src/M5Unified.cpp @@ -593,7 +593,7 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { 2, 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry 2, 0x12, 0x00, // 0x12 SYSTEM/ power-up DAC - NOT default 2, 0x13, 0x10, // 0x13 SYSTEM/ Enable output to HP drive - NOT default - 2, 0x32, 0xBF, // 0x32 DAC/ DAC volume (0xBF == ±0 dB ) + 2, 0x32, 0xEF, // 0x32 DAC/ DAC volume (0xBF == ±0 dB ) 2, 0x37, 0x08, // 0x37 DAC/ Bypass DAC equalizer - NOT default 0 }; @@ -2444,7 +2444,7 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { spk_cfg.pin_ws = GPIO_NUM_15; spk_cfg.pin_data_out = GPIO_NUM_21; spk_cfg.i2s_port = I2S_NUM_0; - spk_cfg.magnification = 1; + spk_cfg.magnification = 4; spk_cfg.sample_rate = 44100; spk_cfg.stereo = true; spk_cfg.buzzer = false; From 2b8b5cca9acbc136798068801000c20a4aa10df0 Mon Sep 17 00:00:00 2001 From: lovyan03 <42724151+lovyan03@users.noreply.github.com> Date: Wed, 15 Jul 2026 16:23:41 +0900 Subject: [PATCH 03/16] Add speaker configuration for M5PaperMono --- src/M5Unified.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/M5Unified.cpp b/src/M5Unified.cpp index 17f4ff5..40667cd 100644 --- a/src/M5Unified.cpp +++ b/src/M5Unified.cpp @@ -2421,6 +2421,15 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { } break; + case board_t::board_M5PaperMono: + if (cfg.internal_spk) + { + spk_cfg.pin_data_out = GPIO_NUM_42; + spk_cfg.buzzer = true; + spk_cfg.magnification = 48; + } + break; + case board_t::board_M5PaperColor: if (cfg.internal_spk) { From 5119c4260e85e119c88002d705805836c1da7244 Mon Sep 17 00:00:00 2001 From: lovyan03 <42724151+lovyan03@users.noreply.github.com> Date: Thu, 16 Jul 2026 13:34:33 +0900 Subject: [PATCH 04/16] Add dependencies section for m5gfx in idf_component.yml --- idf_component.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/idf_component.yml b/idf_component.yml index 1a2ad0b..48f4f60 100644 --- a/idf_component.yml +++ b/idf_component.yml @@ -3,3 +3,6 @@ issues: https://github.com/m5stack/M5Unified/issues repository: https://github.com/m5stack/M5Unified.git url: https://github.com/m5stack/M5Unified.git version: 0.2.18 +dependencies: + m5stack/m5gfx: + version: ">=0.2.25" From 5f39248d87474bdf7434ce6c6c226456cfccb9c6 Mon Sep 17 00:00:00 2001 From: lovyan03 <42724151+lovyan03@users.noreply.github.com> Date: Thu, 16 Jul 2026 14:44:27 +0900 Subject: [PATCH 05/16] Fix phantom button presses when the IO expander read fails readRegister8() returns 0 when the I2C transaction fails, and the active-low decoding misinterprets it as all buttons pressed. Check the result of readRegister() instead and leave the buttons released when the read fails. (StampPLC, UnitC6L, Nesso N1) Fixes #262 --- src/M5Unified.cpp | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/M5Unified.cpp b/src/M5Unified.cpp index 40667cd..a73b0a4 100644 --- a/src/M5Unified.cpp +++ b/src/M5Unified.cpp @@ -2884,11 +2884,13 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { case board_t::board_M5StampPLC: { use_rawstate_bits = 0b00111; - auto value = _io_expander[0]->readRegister8(0x0F); - btn_rawstate_bits = (!(value & 0b100) ? 0b00001 : 0) // BtnA - | (!(value & 0b010) ? 0b00010 : 0) // BtnB - | (!(value & 0b001) ? 0b00100 : 0) // BtnC - ; + uint8_t value = 0xFF; + if (_io_expander[0]->readRegister(0x0F, &value, 1)) { + btn_rawstate_bits = (!(value & 0b100) ? 0b00001 : 0) // BtnA + | (!(value & 0b010) ? 0b00010 : 0) // BtnB + | (!(value & 0b001) ? 0b00100 : 0) // BtnC + ; + } } break; @@ -2969,18 +2971,22 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { case board_t::board_M5UnitC6L: { use_rawstate_bits = 0b00001; - auto value = _io_expander[0]->readRegister8(0x0F); - btn_rawstate_bits = (!(value & 0b001) ? 0b00001 : 0); // BtnA + uint8_t value = 0xFF; + if (_io_expander[0]->readRegister(0x0F, &value, 1)) { + btn_rawstate_bits = (!(value & 0b001) ? 0b00001 : 0); // BtnA + } } break; case board_t::board_ArduinoNessoN1: { use_rawstate_bits = 0b00011; - auto value = _io_expander[0]->readRegister8(0x0F); - btn_rawstate_bits = (!(value & 0b001) ? 0b00001 : 0) // BtnA - | (!(value & 0b010) ? 0b00010 : 0) // BtnB - ; + uint8_t value = 0xFF; + if (_io_expander[0]->readRegister(0x0F, &value, 1)) { + btn_rawstate_bits = (!(value & 0b001) ? 0b00001 : 0) // BtnA + | (!(value & 0b010) ? 0b00010 : 0) // BtnB + ; + } } break; From 319375beeddbd3796b87b6d17aea103da692ec8c Mon Sep 17 00:00:00 2001 From: ainyan03 Date: Thu, 16 Jul 2026 14:54:25 +0900 Subject: [PATCH 06/16] bump version to 0.2.19 --- idf_component.yml | 2 +- library.json | 2 +- library.properties | 2 +- src/gitTagVersion.h | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/idf_component.yml b/idf_component.yml index 48f4f60..8e0f388 100644 --- a/idf_component.yml +++ b/idf_component.yml @@ -2,7 +2,7 @@ description: Unified library for M5Stack series issues: https://github.com/m5stack/M5Unified/issues repository: https://github.com/m5stack/M5Unified.git url: https://github.com/m5stack/M5Unified.git -version: 0.2.18 +version: 0.2.19 dependencies: m5stack/m5gfx: version: ">=0.2.25" diff --git a/library.json b/library.json index 36f896a..af213b7 100644 --- a/library.json +++ b/library.json @@ -16,7 +16,7 @@ "version": ">=0.2.25" } ], - "version": "0.2.18", + "version": "0.2.19", "frameworks": ["arduino", "espidf", "*"], "platforms": ["espressif32", "native"], "headers": "M5Unified.h" diff --git a/library.properties b/library.properties index 2cb59ab..5ae79f7 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=M5Unified -version=0.2.18 +version=0.2.19 author=M5Stack maintainer=M5Stack sentence=Unified library for M5Stack series diff --git a/src/gitTagVersion.h b/src/gitTagVersion.h index 577774c..4afb62c 100644 --- a/src/gitTagVersion.h +++ b/src/gitTagVersion.h @@ -1,4 +1,4 @@ #define M5UNIFIED_VERSION_MAJOR 0 #define M5UNIFIED_VERSION_MINOR 2 -#define M5UNIFIED_VERSION_PATCH 18 +#define M5UNIFIED_VERSION_PATCH 19 #define M5UNIFIED_VERSION F( M5UNIFIED_VERSION_MAJOR "." M5UNIFIED_VERSION_MINOR "." M5UNIFIED_VERSION_PATCH ) From 09a1c135de2f7a8590ef991a986a5e27b5f3ef4d Mon Sep 17 00:00:00 2001 From: Tinyu Date: Thu, 16 Jul 2026 16:19:30 +0800 Subject: [PATCH 07/16] Add M5IOE1 GPIO and PWM APIs. --- src/M5Unified.cpp | 47 +++++++----- src/utility/M5IOE1_Class.cpp | 139 +++++++++++++++++++++++++++++++++++ src/utility/M5IOE1_Class.hpp | 78 ++++++++++++++++++++ src/utility/Power_Class.cpp | 45 +++++------- 4 files changed, 266 insertions(+), 43 deletions(-) create mode 100644 src/utility/M5IOE1_Class.cpp create mode 100644 src/utility/M5IOE1_Class.hpp diff --git a/src/M5Unified.cpp b/src/M5Unified.cpp index a73b0a4..dd8f1ff 100644 --- a/src/M5Unified.cpp +++ b/src/M5Unified.cpp @@ -5,6 +5,7 @@ #include "M5Unified.hpp" #include "utility/PI4IOE5V6408_Class.hpp" +#include "utility/M5IOE1_Class.hpp" #if !defined (M5UNIFIED_PC_BUILD) #include @@ -428,7 +429,6 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { static constexpr uint8_t es8388_i2c_addr = 0x10; static constexpr uint8_t pi4io1_i2c_addr = 0x43; static constexpr uint8_t m5pm1_i2c_addr = 0x6E; - static constexpr uint8_t m5ioe1_i2c_addr = 0x4F; #if defined (CONFIG_IDF_TARGET_ESP32S3) static constexpr uint8_t aw88298_i2c_addr = 0x36; static constexpr uint8_t aw9523_i2c_addr = 0x58; @@ -599,15 +599,17 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { }; if (enabled) { - self->In_I2C.bitOn(m5ioe1_i2c_addr, 0x05, 0b00000100, 100000); // Enable Audio Power (M5IOE1_G3) + auto& ioe1 = self->getIOExpander(0); + ioe1.digitalWrite(M5IOE1_Class::gpio3, true); // Enable Audio Power (M5IOE1_G3) self->delay(10); in_i2c_bulk_write(es8311_i2c_addr0, enabled_bulk_data, 100000, 3); - self->In_I2C.bitOn(m5ioe1_i2c_addr, 0x06, 0b00000010, 100000); // Enable PA (M5IOE1_G10) + ioe1.digitalWrite(M5IOE1_Class::gpio10, true); // Enable PA (M5IOE1_G10) } else { - self->In_I2C.bitOff(m5ioe1_i2c_addr, 0x06, 0b00000010, 100000); // Disable PA (M5IOE1_G10) - self->In_I2C.bitOff(m5ioe1_i2c_addr, 0x05, 0b00000100, 100000); // Disable Audio Power (M5IOE1_G3) + auto& ioe1 = self->getIOExpander(0); + ioe1.digitalWrite(M5IOE1_Class::gpio10, false); // Disable PA (M5IOE1_G10) + ioe1.digitalWrite(M5IOE1_Class::gpio3, false); // Disable Audio Power (M5IOE1_G3) } #endif return true; @@ -1041,7 +1043,7 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { }; if (enabled) { - self->In_I2C.bitOn(m5ioe1_i2c_addr, 0x05, 0b00000100, 100000); // Enable Audio Power (M5IOE1_G3) + self->getIOExpander(0).digitalWrite(M5IOE1_Class::gpio3, true); // Enable Audio Power (M5IOE1_G3) self->delay(5); } m5gfx::i2c::i2c_temporary_switcher_t backup_i2c_setting(1, GPIO_NUM_47, GPIO_NUM_48); @@ -1730,6 +1732,14 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { _io_expander[0].reset(ioexp); } break; + case board_t::board_M5PaperMono: + case board_t::board_M5StopWatch: + { + auto ioexp = new M5IOE1_Class; + ioexp->begin(); + _io_expander[0].reset(ioexp); + } + break; #endif default: break; @@ -1999,20 +2009,23 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { case board_t::board_M5StopWatch: m5gfx::pinMode(GPIO_NUM_1, m5gfx::pin_mode_t::input); m5gfx::pinMode(GPIO_NUM_2, m5gfx::pin_mode_t::input); - // M5IOE1@0x4F: display power M5IOE1_G8 + // M5IOE1 PIN4: display power { - this->In_I2C.writeRegister8(m5ioe1_i2c_addr, 0x23, 0x00, 100000); - this->In_I2C.bitOff(m5ioe1_i2c_addr, 0x13, 0b00001000, 100000); - this->In_I2C.bitOn(m5ioe1_i2c_addr, 0x03, 0b00001000, 100000); - this->In_I2C.bitOn(m5ioe1_i2c_addr, 0x05, 0b00001000, 100000); + auto& ioe1 = getIOExpander(0); + ioe1.setHighImpedance(M5IOE1_Class::gpio4, false); + ioe1.setDirection(M5IOE1_Class::gpio4, true); + ioe1.digitalWrite(M5IOE1_Class::gpio4, true); } // M5IOE1_G3 codec power / M5IOE1_G10 PA - this->In_I2C.bitOff(m5ioe1_i2c_addr, 0x13, 0b00000100, 100000); - this->In_I2C.bitOff(m5ioe1_i2c_addr, 0x14, 0b00000010, 100000); - this->In_I2C.bitOn(m5ioe1_i2c_addr, 0x03, 0b00000100, 100000); - this->In_I2C.bitOn(m5ioe1_i2c_addr, 0x04, 0b00000010, 100000); - this->In_I2C.bitOff(m5ioe1_i2c_addr, 0x05, 0b00000100, 100000); - this->In_I2C.bitOff(m5ioe1_i2c_addr, 0x06, 0b00000010, 100000); + { + auto& ioe1 = getIOExpander(0); + ioe1.setHighImpedance(M5IOE1_Class::gpio3, false); + ioe1.setHighImpedance(M5IOE1_Class::gpio10, false); + ioe1.setDirection(M5IOE1_Class::gpio3, true); + ioe1.setDirection(M5IOE1_Class::gpio10, true); + ioe1.digitalWrite(M5IOE1_Class::gpio3, false); + ioe1.digitalWrite(M5IOE1_Class::gpio10, false); + } break; #elif defined (CONFIG_IDF_TARGET_ESP32P4) diff --git a/src/utility/M5IOE1_Class.cpp b/src/utility/M5IOE1_Class.cpp new file mode 100644 index 0000000..4473ba5 --- /dev/null +++ b/src/utility/M5IOE1_Class.cpp @@ -0,0 +1,139 @@ +// Copyright (c) M5Stack. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#include "M5IOE1_Class.hpp" + +namespace m5 +{ + namespace + { + static constexpr std::uint8_t M5IOE1_REG_UID_L = 0x00; + static constexpr std::uint8_t M5IOE1_REG_GPIO_MODE_L = 0x03; + static constexpr std::uint8_t M5IOE1_REG_GPIO_OUT_L = 0x05; + static constexpr std::uint8_t M5IOE1_REG_GPIO_IN_L = 0x07; + static constexpr std::uint8_t M5IOE1_REG_GPIO_PU_L = 0x09; + static constexpr std::uint8_t M5IOE1_REG_GPIO_PD_L = 0x0B; + static constexpr std::uint8_t M5IOE1_REG_GPIO_IE_L = 0x0D; + static constexpr std::uint8_t M5IOE1_REG_GPIO_IS_L = 0x11; + static constexpr std::uint8_t M5IOE1_REG_GPIO_DRV_L = 0x13; + static constexpr std::uint8_t M5IOE1_REG_PWM1_DUTY_L = 0x1B; + static constexpr std::uint8_t M5IOE1_REG_I2C_CFG = 0x23; + static constexpr std::uint8_t M5IOE1_REG_PWM_FREQ_L = 0x25; + static constexpr std::uint8_t M5IOE1_PWM_POLARITY = 1 << 6; + static constexpr std::uint8_t M5IOE1_PWM_ENABLE = 1 << 7; + static constexpr std::uint32_t M5IOE1_I2C_FREQ_100K = 100000; + } + + bool M5IOE1_Class::begin() + { + if (!_init) { + _freq = M5IOE1_I2C_FREQ_100K; + std::uint8_t uid[2] = {}; + auto res = readRegister(M5IOE1_REG_UID_L, uid, sizeof(uid)); + if (res) { + // Disable I2C idle sleep and keep the IOE1 bus at its default 100 kHz speed. + writeRegister8(M5IOE1_REG_I2C_CFG, 0x00); + } + _init = res; + } + return _init; + } + + void M5IOE1_Class::setDirection(uint8_t pin, bool direction) + { + if (!_isValidPin(pin)) { return; } + // false=input, true=output. MODE bit set means output. + const auto reg = _regForPin(M5IOE1_REG_GPIO_MODE_L, pin); + const auto bit = _bitForPin(pin); + direction ? bitOn(reg, bit) : bitOff(reg, bit); + } + + void M5IOE1_Class::enablePull(uint8_t pin, bool enablePull) + { + if (!_isValidPin(pin)) { return; } + const auto pu_reg = _regForPin(M5IOE1_REG_GPIO_PU_L, pin); + const auto pd_reg = _regForPin(M5IOE1_REG_GPIO_PD_L, pin); + const auto bit = _bitForPin(pin); + if (enablePull) { + bitOn(pu_reg, bit); + } else { + bitOff(pu_reg, bit); + bitOff(pd_reg, bit); + } + } + + void M5IOE1_Class::setPullMode(uint8_t pin, bool mode) + { + if (!_isValidPin(pin)) { return; } + const auto pu_reg = _regForPin(M5IOE1_REG_GPIO_PU_L, pin); + const auto pd_reg = _regForPin(M5IOE1_REG_GPIO_PD_L, pin); + const auto bit = _bitForPin(pin); + // false=pull-down, true=pull-up. + mode ? bitOn(pu_reg, bit) : bitOff(pu_reg, bit); + mode ? bitOff(pd_reg, bit) : bitOn(pd_reg, bit); + } + + void M5IOE1_Class::setHighImpedance(uint8_t pin, bool enable) + { + if (!_isValidPin(pin)) { return; } + // M5IOE1 exposes drive mode here: 0=push-pull, 1=open-drain. + const auto reg = _regForPin(M5IOE1_REG_GPIO_DRV_L, pin); + const auto bit = _bitForPin(pin); + enable ? bitOn(reg, bit) : bitOff(reg, bit); + } + + bool M5IOE1_Class::getWriteValue(uint8_t pin) + { + if (!_isValidPin(pin)) { return false; } + return (readRegister8(_regForPin(M5IOE1_REG_GPIO_OUT_L, pin)) & _bitForPin(pin)) != 0; + } + + void M5IOE1_Class::digitalWrite(uint8_t pin, bool level) + { + if (!_isValidPin(pin)) { return; } + const auto reg = _regForPin(M5IOE1_REG_GPIO_OUT_L, pin); + const auto bit = _bitForPin(pin); + level ? bitOn(reg, bit) : bitOff(reg, bit); + } + + bool M5IOE1_Class::digitalRead(uint8_t pin) + { + if (!_isValidPin(pin)) { return false; } + return (readRegister8(_regForPin(M5IOE1_REG_GPIO_IN_L, pin)) & _bitForPin(pin)) != 0; + } + + void M5IOE1_Class::setPwmFrequency(std::uint16_t frequency) + { + std::uint8_t data[2] = { static_cast(frequency & 0xFF), static_cast(frequency >> 8) }; + writeRegister(M5IOE1_REG_PWM_FREQ_L, data, sizeof(data)); + } + + void M5IOE1_Class::setPwmDuty(std::uint8_t channel, std::uint16_t duty12, bool enable, bool polarity) + { + if (channel > pwm_ch4) { return; } + duty12 &= 0x0FFF; + std::uint8_t high = static_cast(duty12 >> 8); + if (enable) { high |= M5IOE1_PWM_ENABLE; } + if (polarity) { high |= M5IOE1_PWM_POLARITY; } + std::uint8_t data[2] = { static_cast(duty12 & 0xFF), high }; + writeRegister(static_cast(M5IOE1_REG_PWM1_DUTY_L + channel * 2), data, sizeof(data)); + } + + void M5IOE1_Class::resetIrq() + { + std::uint8_t data[2] = { 0x00, 0x00 }; + writeRegister(M5IOE1_REG_GPIO_IS_L, data, sizeof(data)); + } + + void M5IOE1_Class::disableIrq() + { + std::uint8_t data[2] = { 0x00, 0x00 }; + writeRegister(M5IOE1_REG_GPIO_IE_L, data, sizeof(data)); + } + + void M5IOE1_Class::enableIrq() + { + std::uint8_t data[2] = { 0xFF, 0x3F }; + writeRegister(M5IOE1_REG_GPIO_IE_L, data, sizeof(data)); + } +} diff --git a/src/utility/M5IOE1_Class.hpp b/src/utility/M5IOE1_Class.hpp new file mode 100644 index 0000000..3154fde --- /dev/null +++ b/src/utility/M5IOE1_Class.hpp @@ -0,0 +1,78 @@ +// Copyright (c) M5Stack. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#ifndef __M5_M5IOE1_CLASS_H__ +#define __M5_M5IOE1_CLASS_H__ + +#include "IOExpander_Base.hpp" + +namespace m5 +{ + class M5IOE1_Class : public IOExpander_Base + { + public: + static constexpr std::uint8_t DEFAULT_ADDRESS = 0x4F; + static constexpr std::uint32_t DEFAULT_FREQ = 100000; + + enum gpio_t : std::uint8_t + { gpio1 = 0 + , gpio2 = 1 + , gpio3 = 2 + , gpio4 = 3 + , gpio5 = 4 + , gpio6 = 5 + , gpio7 = 6 + , gpio8 = 7 + , gpio9 = 8 + , gpio10 = 9 + , gpio11 = 10 + , gpio12 = 11 + , gpio13 = 12 + , gpio14 = 13 + }; + + enum pwm_channel_t : std::uint8_t + { pwm_ch1 = 0 // IO_9 + , pwm_ch2 = 1 // IO_8 + , pwm_ch3 = 2 // IO_11 + , pwm_ch4 = 3 // IO_10 + }; + + M5IOE1_Class(std::uint8_t i2c_addr = DEFAULT_ADDRESS, std::uint32_t freq = DEFAULT_FREQ, m5::I2C_Class* i2c = &m5::In_I2C) + : IOExpander_Base(i2c_addr, freq, i2c) + {} + + bool begin(); + + void setDirection(uint8_t pin, bool direction) override; + + void enablePull(uint8_t pin, bool enablePull) override; + + void setPullMode(uint8_t pin, bool mode) override; + + void setHighImpedance(uint8_t pin, bool enable) override; + + bool getWriteValue(uint8_t pin) override; + + void digitalWrite(uint8_t pin, bool level) override; + + bool digitalRead(uint8_t pin) override; + + void setPwmFrequency(std::uint16_t frequency); + + void setPwmDuty(std::uint8_t channel, std::uint16_t duty12, bool enable = true, bool polarity = false); + + void resetIrq() override; + + void disableIrq() override; + + void enableIrq() override; + + private: + static bool _isValidPin(uint8_t pin) { return pin < 14; } + static std::uint8_t _regForPin(std::uint8_t reg_low, std::uint8_t pin) { return reg_low + (pin >> 3); } + static std::uint8_t _bitForPin(std::uint8_t pin) { return 1 << (pin & 7); } + }; +} + +#endif diff --git a/src/utility/Power_Class.cpp b/src/utility/Power_Class.cpp index efa4364..4be4353 100644 --- a/src/utility/Power_Class.cpp +++ b/src/utility/Power_Class.cpp @@ -3,6 +3,7 @@ #include "../M5Unified.hpp" #include "Power_Class.hpp" +#include "M5IOE1_Class.hpp" #if !defined (M5UNIFIED_PC_BUILD) @@ -35,7 +36,6 @@ namespace m5 #if defined (CONFIG_IDF_TARGET_ESP32S3) static constexpr uint8_t aw9523_i2c_addr = 0x58; static constexpr uint8_t powerhub_i2c_addr = 0x50; - static constexpr uint8_t m5ioe1_i2c_addr = 0x4F; static constexpr uint8_t ip2315_i2c_addr = 0x75; // M5PaperMono USB fast-charger static constexpr int M5PaperS3_CHG_STAT_PIN = GPIO_NUM_4; @@ -215,17 +215,12 @@ namespace m5 // M5IOE1: PWM1 drives IO9 (G9 motor). REG_PWM_FREQ 0x25/0x26 Hz LE; REG_PWM1_DUTY 0x1B/0x1C (bit7 EN). constexpr uint16_t motor_pwm_hz = 2000; - M5.In_I2C.writeRegister8(m5ioe1_i2c_addr, 0x23, 0x00, i2c_freq); // REG_I2C_CFG: disable I2C sleep - uint8_t pwm_freq_le[2] = { - static_cast(motor_pwm_hz & 0xFF), - static_cast((motor_pwm_hz >> 8) & 0xFF), - }; - M5.In_I2C.writeRegister(m5ioe1_i2c_addr, 0x25, pwm_freq_le, sizeof(pwm_freq_le), i2c_freq); + auto& ioe1 = static_cast(M5.getIOExpander(0)); + ioe1.setPwmFrequency(motor_pwm_hz); // IO9 (G9 motor / PWM1): push-pull output, duty off until setVibration - M5.In_I2C.bitOff(m5ioe1_i2c_addr, 0x14, 0b00000001, i2c_freq); - M5.In_I2C.bitOn(m5ioe1_i2c_addr, 0x04, 0b00000001, i2c_freq); - M5.In_I2C.writeRegister8(m5ioe1_i2c_addr, 0x1B, 0x00, i2c_freq); - M5.In_I2C.writeRegister8(m5ioe1_i2c_addr, 0x1C, 0x00, i2c_freq); // PWM off at boot + ioe1.setHighImpedance(M5IOE1_Class::gpio9, false); + ioe1.setDirection(M5IOE1_Class::gpio9, true); + ioe1.setPwmDuty(M5IOE1_Class::pwm_ch1, 0, false); // PWM off at boot } break; @@ -268,10 +263,12 @@ namespace m5 // 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). - M5.In_I2C.writeRegister8(m5ioe1_i2c_addr, 0x23, 0x00, i2c_freq); // I2C_CFG: disable IOE1 idle-sleep - M5.In_I2C.bitOff(m5ioe1_i2c_addr, 0x14, 1 << 2, i2c_freq); // GPIO_DRV_H: IO11 push-pull - M5.In_I2C.bitOn (m5ioe1_i2c_addr, 0x04, 1 << 2, i2c_freq); // GPIO_MODE_H: IO11 output - M5.In_I2C.bitOn (m5ioe1_i2c_addr, 0x06, 1 << 2, i2c_freq); // GPIO_OUT_H: IO11 high + { + 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); @@ -1094,7 +1091,7 @@ namespace m5 case board_t::board_M5Tab5: for (int i = 0; i < 10; ++i) { - M5.getIOExpander(1).digitalWrite(4, i & 1); // io1.pin4 == PWROFF_PLUSE + M5.getIOExpander(1).digitalWrite(4, i & 1); // io1.gpio4 == PWROFF_PLUSE m5gfx::delay(50); } break; @@ -1894,7 +1891,7 @@ namespace m5 #endif #if defined (CONFIG_IDF_TARGET_ESP32P4) case board_t::board_M5Tab5: - return M5.getIOExpander(1).digitalRead(6) // io1.pin6 == CHG_STAT + return M5.getIOExpander(1).digitalRead(6) // io1.gpio6 == CHG_STAT ? is_charging_t::is_charging : is_charging_t::is_discharging; #endif default: @@ -2022,19 +2019,15 @@ namespace m5 if (M5.getBoard() == board_t::board_M5StopWatch) { // M5IOE1 PWM1 (0x1B/0x1C) -> pin IO9 / G9 motor; duty 12-bit in [11:0], EN=bit7 of high byte. + auto& ioe1 = static_cast(M5.getIOExpander(0)); if (level == 0) { - uint8_t pwm_off[2] = { 0x00, 0x00 }; - M5.In_I2C.writeRegister(m5ioe1_i2c_addr, 0x1B, pwm_off, sizeof(pwm_off), i2c_freq); + ioe1.setPwmDuty(M5IOE1_Class::pwm_ch1, 0, false); } else { // PWM needs IO9 in output mode (M5IOE1 pin index 8 -> GPIO_MODE_H bit0). - M5.In_I2C.bitOff(m5ioe1_i2c_addr, 0x14, 0b00000001, i2c_freq); - M5.In_I2C.bitOn(m5ioe1_i2c_addr, 0x04, 0b00000001, i2c_freq); + ioe1.setHighImpedance(M5IOE1_Class::gpio9, false); + ioe1.setDirection(M5IOE1_Class::gpio9, true); uint16_t duty12 = static_cast((static_cast(level) * 0x0FFFu) / 255u); - uint8_t pwm_on[2] = { - static_cast(duty12 & 0xFF), - static_cast(((duty12 >> 8) & 0x0Fu) | 0x80u), - }; - M5.In_I2C.writeRegister(m5ioe1_i2c_addr, 0x1B, pwm_on, sizeof(pwm_on), i2c_freq); + ioe1.setPwmDuty(M5IOE1_Class::pwm_ch1, duty12); } return; } From 1157ae240d77b2b61c8f922a4e88b435b39f02a5 Mon Sep 17 00:00:00 2001 From: hlym123 Date: Fri, 17 Jul 2026 00:10:14 +0800 Subject: [PATCH 08/16] Add M5ChainCaptain board support --- src/M5Unified.cpp | 121 +++++++++++++++++++++++++++++++++++- src/M5Unified.hpp | 2 + src/utility/IMU_Class.cpp | 6 ++ src/utility/Power_Class.cpp | 66 ++++++++++++++++++++ src/utility/RTC_Class.cpp | 1 + 5 files changed, 195 insertions(+), 1 deletion(-) diff --git a/src/M5Unified.cpp b/src/M5Unified.cpp index dd8f1ff..2e1d675 100644 --- a/src/M5Unified.cpp +++ b/src/M5Unified.cpp @@ -98,6 +98,7 @@ static constexpr const uint8_t _pin_table_i2c_ex_in[][5] = { { board_t::board_M5PowerHub , GPIO_NUM_48,GPIO_NUM_45 , GPIO_NUM_16,GPIO_NUM_15 }, { board_t::board_M5StampS3Bat , GPIO_NUM_47,GPIO_NUM_48 , 255 ,255 }, { board_t::board_M5PaperColor , GPIO_NUM_2 ,GPIO_NUM_3 , GPIO_NUM_5 ,GPIO_NUM_4 }, +{ board_t::board_M5ChainCaptain,GPIO_NUM_2 ,GPIO_NUM_3 , GPIO_NUM_6 ,GPIO_NUM_7 }, { board_t::board_M5PaperMono , GPIO_NUM_48,GPIO_NUM_47 , 255 ,255 }, { board_t::board_M5StopWatch , GPIO_NUM_48,GPIO_NUM_47 , GPIO_NUM_11,GPIO_NUM_10 }, { board_t::board_unknown , GPIO_NUM_39,GPIO_NUM_38 , GPIO_NUM_1 ,GPIO_NUM_2 }, // AtomS3,AtomS3Lite,AtomS3U @@ -141,6 +142,7 @@ static constexpr const uint8_t _pin_table_port_bc[][5] = { { board_t::board_M5Dial , GPIO_NUM_1 ,GPIO_NUM_2 , 255 ,255 }, { board_t::board_M5DinMeter , GPIO_NUM_1 ,GPIO_NUM_2 , 255 ,255 }, { board_t::board_M5PowerHub , 255 , 255 , GPIO_NUM_1 ,GPIO_NUM_2 }, +{ board_t::board_M5ChainCaptain,GPIO_NUM_17,GPIO_NUM_18, 255 ,255 }, #elif defined (CONFIG_IDF_TARGET_ESP32C3) #elif defined (CONFIG_IDF_TARGET_ESP32C6) { board_t::board_M5UnitC6L ,GPIO_NUM_4 ,GPIO_NUM_5 , GPIO_NUM_4 ,GPIO_NUM_5 }, @@ -615,6 +617,37 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { return true; } + bool M5Unified::_speaker_enabled_cb_chain_captain(void* args, bool enabled) + { +#if defined (CONFIG_IDF_TARGET_ESP32S3) + auto self = (M5Unified*)args; + static constexpr const uint8_t enabled_bulk_data[] = { + 2, 0x00, 0x80, // 0x00 RESET/ CSM POWER ON + 2, 0x01, 0xB5, // 0x01 CLOCK_MANAGER/ MCLK=BCLK + 2, 0x02, 0x18, // 0x02 CLOCK_MANAGER/ MULT_PRE=3 + 2, 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry + 2, 0x12, 0x00, // 0x12 SYSTEM/ power-up DAC - NOT default + 2, 0x13, 0x10, // 0x13 SYSTEM/ Enable output to HP drive - NOT default + 2, 0x32, 0xEF, // 0x32 DAC/ DAC volume (0xBF == ±0 dB ) + 2, 0x37, 0x08, // 0x37 DAC/ Bypass DAC equalizer - NOT default + 0 + }; + if (enabled) + { + self->getIOExpander(0).digitalWrite(M5IOE1_Class::gpio5, true); // M5IOE1_G5 audio rail + self->delay(10); + in_i2c_bulk_write(es8311_i2c_addr0, enabled_bulk_data, 100000, 3); + m5gfx::gpio_hi(GPIO_NUM_21); // AW8737A one-wire enable, default mode + } + else + { + m5gfx::gpio_lo(GPIO_NUM_21); + self->getIOExpander(0).digitalWrite(M5IOE1_Class::gpio5, false); + } +#endif + return true; + } + bool M5Unified::_speaker_enabled_cb_tab5(void* args, bool enabled) { (void)args; @@ -1053,6 +1086,37 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { return true; } + bool M5Unified::_microphone_enabled_cb_chain_captain(void* args, bool enabled) + { +#if defined (CONFIG_IDF_TARGET_ESP32S3) + auto self = (M5Unified*)args; + static constexpr const uint8_t enabled_bulk_data[] = { + 2, 0x00, 0x80, // RESET / CSM power on + 2, 0x01, 0xBA, // MCLK from BCLK + 2, 0x02, 0x18, // clock multiplier + 2, 0x0D, 0x01, // power up analog circuitry + 2, 0x0E, 0x02, // enable analog PGA and ADC modulator + 2, 0x14, 0x10, // differential microphone input, minimum PGA gain + 2, 0x17, 0xFF, // ADC volume + 2, 0x1C, 0x6A, // bypass ADC equalizer and cancel DC offset + 0 + }; + static constexpr const uint8_t disabled_bulk_data[] = { + 2, 0x0D, 0xFC, + 2, 0x0E, 0x6A, + 2, 0x00, 0x00, + 0 + }; + if (enabled) + { + self->getIOExpander(0).digitalWrite(M5IOE1_Class::gpio5, true); // M5IOE1_G5 audio rail + self->delay(5); + } + in_i2c_bulk_write(es8311_i2c_addr0, enabled ? enabled_bulk_data : disabled_bulk_data, 100000, 3); +#endif + return true; + } + #if defined (CONFIG_IDF_TARGET_ESP32) && SOC_TOUCH_SENSOR_SUPPORTED static void _read_touch_pad(uint32_t* results, const touch_pad_t* channel, const size_t channel_count) { @@ -1732,6 +1796,7 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { _io_expander[0].reset(ioexp); } break; + case board_t::board_M5ChainCaptain: case board_t::board_M5PaperMono: case board_t::board_M5StopWatch: { @@ -2001,6 +2066,22 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { m5gfx::pinMode(GPIO_NUM_10, m5gfx::pin_mode_t::input); break; + case board_t::board_M5ChainCaptain: + m5gfx::pinMode(GPIO_NUM_1, m5gfx::pin_mode_t::input); + m5gfx::pinMode(GPIO_NUM_4, m5gfx::pin_mode_t::input); + m5gfx::pinMode(GPIO_NUM_5, m5gfx::pin_mode_t::input); + // Audio PA -- G21 + m5gfx::pinMode(GPIO_NUM_21, m5gfx::pin_mode_t::output); + m5gfx::gpio_lo(GPIO_NUM_21); + // Audio Power -- M5IO1_G5 + { + auto& ioe1 = getIOExpander(0); + ioe1.setHighImpedance(M5IOE1_Class::gpio5, false); + ioe1.setDirection(M5IOE1_Class::gpio5, true); + ioe1.digitalWrite(M5IOE1_Class::gpio5, false); + } + break; + case board_t::board_M5PaperMono: m5gfx::pinMode(GPIO_NUM_2, m5gfx::pin_mode_t::input); m5gfx::pinMode(GPIO_NUM_3, m5gfx::pin_mode_t::input); @@ -2147,6 +2228,19 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { } break; + case board_t::board_M5ChainCaptain: + if (cfg.internal_mic) + { + mic_cfg.pin_mck = GPIO_NUM_40; + mic_cfg.pin_bck = GPIO_NUM_38; + mic_cfg.pin_ws = GPIO_NUM_41; + mic_cfg.pin_data_in = GPIO_NUM_39; + mic_cfg.i2s_port = I2S_NUM_1; + mic_cfg.sample_rate = 16000; + mic_enable_cb = _microphone_enabled_cb_chain_captain; + } + break; + case board_t::board_M5AtomS3U: if (cfg.internal_mic) { @@ -2298,7 +2392,7 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { spk_cfg.pin_data_out = GPIO_NUM_14; spk_cfg.i2s_port = I2S_NUM_0; spk_cfg.magnification = 1; - spk_cfg.sample_rate = 44100; + spk_cfg.sample_rate = 22050; spk_cfg.stereo = true; spk_cfg.buzzer = false; spk_cfg.use_dac = false; @@ -2476,6 +2570,24 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { } break; + case board_t::board_M5ChainCaptain: + if (cfg.internal_spk) + { + spk_cfg.pin_mck = GPIO_NUM_40; + spk_cfg.pin_bck = GPIO_NUM_38; + spk_cfg.pin_ws = GPIO_NUM_41; + spk_cfg.pin_data_out = GPIO_NUM_42; + spk_cfg.i2s_port = I2S_NUM_0; + spk_cfg.magnification = 1; + spk_cfg.sample_rate = 44100; + spk_cfg.stereo = true; + spk_cfg.buzzer = false; + spk_cfg.use_dac = false; + spk_cfg.dac_zero_level = 0; + spk_enable_cb = _speaker_enabled_cb_chain_captain; + } + break; + case board_t::board_M5Cardputer: case board_t::board_M5CardputerADV: if (cfg.internal_spk) @@ -2937,6 +3049,13 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { | ((!m5gfx::gpio_in(GPIO_NUM_1)) & 1) << 2; break; + case board_t::board_M5ChainCaptain: + use_rawstate_bits = 0b00111; + btn_rawstate_bits = ((!m5gfx::gpio_in(GPIO_NUM_1)) & 1) + | ((!m5gfx::gpio_in(GPIO_NUM_4)) & 1) << 1 + | ((!m5gfx::gpio_in(GPIO_NUM_5)) & 1) << 2; + break; + case board_t::board_M5PaperMono: use_rawstate_bits = 0b00011; btn_rawstate_bits = ((!m5gfx::gpio_in(GPIO_NUM_2)) & 1) diff --git a/src/M5Unified.hpp b/src/M5Unified.hpp index dbacac7..03fa01d 100644 --- a/src/M5Unified.hpp +++ b/src/M5Unified.hpp @@ -660,6 +660,7 @@ namespace m5 static bool _speaker_enabled_cb_sticks3(void* args, bool enabled); static bool _speaker_enabled_cb_papercolor(void* args, bool enabled); static bool _speaker_enabled_cb_stopwatch(void* args, bool enabled); + static bool _speaker_enabled_cb_chain_captain(void* args, bool enabled); static bool _speaker_enabled_cb_tab5(void* args, bool enabled); static bool _speaker_enabled_cb_cardputer_adv(void* args, bool enabled); static bool _speaker_enabled_cb_atom_echos3r(void* args, bool enabled); @@ -670,6 +671,7 @@ namespace m5 static bool _microphone_enabled_cb_sticks3(void* args, bool enabled); static bool _microphone_enabled_cb_papercolor(void* args, bool enabled); static bool _microphone_enabled_cb_stopwatch(void* args, bool enabled); + static bool _microphone_enabled_cb_chain_captain(void* args, bool enabled); static bool _microphone_enabled_cb_tab5(void* args, bool enabled); static bool _microphone_enabled_cb_cardputer_adv(void* args, bool enabled); static bool _microphone_enabled_cb_atomic_echo(void* args, bool enabled); diff --git a/src/utility/IMU_Class.cpp b/src/utility/IMU_Class.cpp index c57d8e5..bb66584 100644 --- a/src/utility/IMU_Class.cpp +++ b/src/utility/IMU_Class.cpp @@ -102,6 +102,12 @@ namespace m5 _internal_axisorder_fixed[sensor_index_accel] = (internal_axisorder_t)(axis_order_yxz | axis_invert_y); _internal_axisorder_fixed[sensor_index_gyro ] = (internal_axisorder_t)(axis_order_yxz | axis_invert_y); _internal_axisorder_fixed[sensor_index_mag ] = (internal_axisorder_t)(axis_invert_x | axis_invert_z); // X軸,Z軸反転 + } else + if (board == m5::board_t::board_M5ChainCaptain) + { // ChainCaptain BMI270 : X=-Y, Y=-X, Z=-Z + _internal_axisorder_fixed[sensor_index_accel] = (internal_axisorder_t)(axis_order_yxz | axis_invert_x | axis_invert_y | axis_invert_z); + _internal_axisorder_fixed[sensor_index_gyro ] = (internal_axisorder_t)(axis_order_yxz | axis_invert_x | axis_invert_y | axis_invert_z); + _internal_axisorder_fixed[sensor_index_mag ] = (internal_axisorder_t)(axis_order_yxz | axis_invert_x | axis_invert_y | axis_invert_z); } #endif diff --git a/src/utility/Power_Class.cpp b/src/utility/Power_Class.cpp index 4be4353..45f971b 100644 --- a/src/utility/Power_Class.cpp +++ b/src/utility/Power_Class.cpp @@ -254,6 +254,30 @@ namespace m5 M5pm1.setGPIODrive(M5PM1_Class::gpio3, M5PM1_Class::push_pull); M5pm1.setGPIOOutput(M5PM1_Class::gpio3, true); // TF card power break; + + case board_t::board_M5ChainCaptain: + _pmic = pmic_t::pmic_m5pm1; + // M5PM1_G0 -- Grove Power + M5pm1.setGPIOFunction(M5PM1_Class::gpio0, M5PM1_Class::gpio); + M5pm1.setGPIOMode(M5PM1_Class::gpio0, M5PM1_Class::output); + M5pm1.setGPIODrive(M5PM1_Class::gpio0, M5PM1_Class::push_pull); + M5pm1.setGPIOOutput(M5PM1_Class::gpio0, false); + // M5PM1_G3 -- Chain Power + M5pm1.setGPIOFunction(M5PM1_Class::gpio3, M5PM1_Class::gpio); + M5pm1.setGPIOMode(M5PM1_Class::gpio3, M5PM1_Class::output); + M5pm1.setGPIODrive(M5PM1_Class::gpio3, M5PM1_Class::push_pull); + M5pm1.setGPIOOutput(M5PM1_Class::gpio3, false); + { + auto& ioe1 = M5.getIOExpander(0); + // M5IOE1_G3 -- Charge Status + ioe1.setDirection(M5IOE1_Class::gpio3, false); + ioe1.enablePull(M5IOE1_Class::gpio3, false); + // M5IOE1_G4 -- Boost Control + ioe1.setHighImpedance(M5IOE1_Class::gpio4, false); + ioe1.setDirection(M5IOE1_Class::gpio4, true); + ioe1.digitalWrite(M5IOE1_Class::gpio4, false); + } + break; case board_t::board_M5PaperMono: _rtcIntPin = GPIO_NUM_1; @@ -682,6 +706,21 @@ namespace m5 } break; + case board_t::board_M5ChainCaptain: + { + if (port_mask & ext_port_mask_t::ext_PA) + { + M5pm1.setGPIOOutput(M5PM1_Class::gpio0, enable); + } + if (port_mask & (ext_port_mask_t::ext_PB1 | ext_port_mask_t::ext_PB2)) + { + M5pm1.setGPIOOutput(M5PM1_Class::gpio3, enable); + } + const bool boost_enabled = M5pm1.getGPIOOutputLatch(M5PM1_Class::gpio0) + || M5pm1.getGPIOOutputLatch(M5PM1_Class::gpio3); + M5.getIOExpander(0).digitalWrite(M5IOE1_Class::gpio4, boost_enabled); + break; + } case board_t::board_M5StampS3Bat: // Use G1 Control 5V output M5pm1.setGPIOOutput(M5PM1_Class::gpio1, enable); @@ -805,6 +844,12 @@ namespace m5 return M5pm1.getExtOutput(); break; + case board_t::board_M5ChainCaptain: + return M5.getIOExpander(0).getWriteValue(M5IOE1_Class::gpio4) + && (M5pm1.getGPIOOutputLatch(M5PM1_Class::gpio0) + || M5pm1.getGPIOOutputLatch(M5PM1_Class::gpio3)); + break; + case board_t::board_M5StampS3Bat: return M5pm1.getGPIOOutputLatch(M5PM1_Class::gpio1); break; @@ -1883,6 +1928,12 @@ namespace m5 } break; + case board_t::board_M5ChainCaptain: + return M5.getIOExpander(0).digitalRead(M5IOE1_Class::gpio3) + ? is_charging_t::is_discharging + : is_charging_t::is_charging; + break; + case board_t::board_M5PaperS3: return (m5gfx::gpio_in(M5PaperS3_CHG_STAT_PIN) == false) ? is_charging_t::is_charging : is_charging_t::is_discharging; @@ -1939,6 +1990,21 @@ namespace m5 return M5pm1.get5VoutVoltage(); } break; + case board_t::board_M5ChainCaptain: { + if (!is_voltage) { return 0; } + static constexpr float diode_offset_mv = 530.0f; + static constexpr float valid_voltage_threshold_mv = 2000.0f; + if (port_mask & ext_port_mask_t::ext_PA) { + float mv = M5pm1.getVBUSVoltage(); + return mv >= valid_voltage_threshold_mv ? mv + diode_offset_mv : mv; + } + if (port_mask & (ext_port_mask_t::ext_PB1 | ext_port_mask_t::ext_PB2)) { + float mv = M5pm1.get5VoutVoltage(); + return mv >= valid_voltage_threshold_mv ? mv + diode_offset_mv : mv; + } + return 0; + } + case board_t::board_M5StampPLC: if (port_mask & (ext_port_mask_t::ext_PWR485 | ext_port_mask_t::ext_PWRCAN)) { if (is_voltage) diff --git a/src/utility/RTC_Class.cpp b/src/utility/RTC_Class.cpp index 11472a9..fbd24f7 100644 --- a/src/utility/RTC_Class.cpp +++ b/src/utility/RTC_Class.cpp @@ -44,6 +44,7 @@ namespace m5 case board_t::board_M5StampPLC: case board_t::board_M5PaperColor: case board_t::board_M5PaperMono: + case board_t::board_M5ChainCaptain: instance.reset(new RX8130_Class(RX8130_Class::DEFAULT_ADDRESS, 400000, i2c)); break; #endif From 9ce35affe57513340af95d12e5aab6bf37c1af91 Mon Sep 17 00:00:00 2001 From: Tinyu Date: Fri, 17 Jul 2026 14:36:18 +0800 Subject: [PATCH 09/16] 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 10/16] 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 11/16] 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 12/16] 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); } } From 5928e7cb07a84d3382aad810adae4449caf25229 Mon Sep 17 00:00:00 2001 From: Tinyu Date: Fri, 17 Jul 2026 18:31:54 +0800 Subject: [PATCH 13/16] Add Paper Mono microphone power support --- src/M5Unified.cpp | 24 ++++++++++++++++++++++++ src/M5Unified.hpp | 1 + 2 files changed, 25 insertions(+) diff --git a/src/M5Unified.cpp b/src/M5Unified.cpp index 2e1d675..7458a43 100644 --- a/src/M5Unified.cpp +++ b/src/M5Unified.cpp @@ -1051,6 +1051,29 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { return true; } + bool M5Unified::_microphone_enabled_cb_papermono(void* args, bool enabled) + { +#if defined (CONFIG_IDF_TARGET_ESP32S3) + auto self = (M5Unified*)args; + auto& ioe1 = self->getIOExpander(0); + bool result = true; + if (enabled) + { + // The factory firmware enables PM1 BOOST before powering the PDM mic. + result = self->Power.M5pm1.setExtOutput(true); + } + // M5IOE1 GPIO12 is the Paper Mono PDM microphone power enable. + ioe1.setHighImpedance(M5IOE1_Class::gpio12, false); + ioe1.setDirection(M5IOE1_Class::gpio12, true); + ioe1.digitalWrite(M5IOE1_Class::gpio12, enabled); + return result; +#else + (void)args; + (void)enabled; +#endif + return true; + } + bool M5Unified::_microphone_enabled_cb_stopwatch(void* args, bool enabled) { @@ -2213,6 +2236,7 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { { /// builtin PDM mic mic_cfg.pin_ws = GPIO_NUM_45; mic_cfg.pin_data_in = GPIO_NUM_46; + mic_enable_cb = _microphone_enabled_cb_papermono; } break; diff --git a/src/M5Unified.hpp b/src/M5Unified.hpp index 03fa01d..093f364 100644 --- a/src/M5Unified.hpp +++ b/src/M5Unified.hpp @@ -670,6 +670,7 @@ namespace m5 static bool _microphone_enabled_cb_stickc(void* args, bool enabled); static bool _microphone_enabled_cb_sticks3(void* args, bool enabled); static bool _microphone_enabled_cb_papercolor(void* args, bool enabled); + static bool _microphone_enabled_cb_papermono(void* args, bool enabled); static bool _microphone_enabled_cb_stopwatch(void* args, bool enabled); static bool _microphone_enabled_cb_chain_captain(void* args, bool enabled); static bool _microphone_enabled_cb_tab5(void* args, bool enabled); From ee72d00108421e3a11d0e19bd7edce9e4139c87f Mon Sep 17 00:00:00 2001 From: luoweiyuan Date: Tue, 21 Jul 2026 11:29:52 +0800 Subject: [PATCH 14/16] Add M5PaperDIY board support --- src/M5Unified.cpp | 13 +++++++++++++ src/utility/Power_Class.cpp | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/M5Unified.cpp b/src/M5Unified.cpp index 2e1d675..51a08c4 100644 --- a/src/M5Unified.cpp +++ b/src/M5Unified.cpp @@ -94,6 +94,7 @@ static constexpr const uint8_t _pin_table_i2c_ex_in[][5] = { { board_t::board_M5AtomVoiceS3R,GPIO_NUM_0 ,GPIO_NUM_45 , GPIO_NUM_1 ,GPIO_NUM_2 }, { board_t::board_M5AtomS3RCam , GPIO_NUM_0 ,GPIO_NUM_45 , GPIO_NUM_1 ,GPIO_NUM_2 }, { board_t::board_M5PaperS3 , GPIO_NUM_42,GPIO_NUM_41 , GPIO_NUM_1 ,GPIO_NUM_2 }, +{ board_t::board_M5PaperDIY , GPIO_NUM_42,GPIO_NUM_41 , GPIO_NUM_42,GPIO_NUM_41 }, { board_t::board_M5StampPLC , GPIO_NUM_15,GPIO_NUM_13 , GPIO_NUM_1 ,GPIO_NUM_2 }, { board_t::board_M5PowerHub , GPIO_NUM_48,GPIO_NUM_45 , GPIO_NUM_16,GPIO_NUM_15 }, { board_t::board_M5StampS3Bat , GPIO_NUM_47,GPIO_NUM_48 , 255 ,255 }, @@ -190,6 +191,7 @@ static constexpr const uint8_t _pin_table_sd[][7] = { { board_t::board_M5Cardputer , GPIO_NUM_40, GPIO_NUM_14, GPIO_NUM_39, 255 , 255 , GPIO_NUM_12 }, { board_t::board_M5CardputerADV,GPIO_NUM_40, GPIO_NUM_14, GPIO_NUM_39, 255 , 255 , GPIO_NUM_12 }, { board_t::board_M5PaperS3 , GPIO_NUM_39, GPIO_NUM_38, GPIO_NUM_40, 255 , 255 , GPIO_NUM_47 }, +{ board_t::board_M5PaperDIY , GPIO_NUM_39, GPIO_NUM_38, GPIO_NUM_40, 255 , 255 , GPIO_NUM_47 }, { board_t::board_M5StampPLC , GPIO_NUM_7, GPIO_NUM_8, GPIO_NUM_9, 255 , 255 , GPIO_NUM_10 }, { board_t::board_M5PaperColor , GPIO_NUM_15, GPIO_NUM_13, GPIO_NUM_14, 255 , 255 , GPIO_NUM_47 }, { board_t::board_M5PaperMono , GPIO_NUM_13, GPIO_NUM_12, GPIO_NUM_11, GPIO_NUM_10, GPIO_NUM_9, GPIO_NUM_8 }, @@ -2060,6 +2062,11 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { this->In_I2C.bitOff(m5pm1_i2c_addr, 0x11, 1 << 3, 100000); // Set gpio3 output low break; + case board_t::board_M5PaperDIY: + m5gfx::pinMode(GPIO_NUM_4, m5gfx::pin_mode_t::input); + m5gfx::pinMode(GPIO_NUM_3, m5gfx::pin_mode_t::input); + break; + case board_t::board_M5PaperColor: m5gfx::pinMode(GPIO_NUM_1, m5gfx::pin_mode_t::input); m5gfx::pinMode(GPIO_NUM_9, m5gfx::pin_mode_t::input); @@ -3042,6 +3049,12 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { | ((!m5gfx::gpio_in(GPIO_NUM_12)) & 1) << 1; break; + case board_t::board_M5PaperDIY: + use_rawstate_bits = 0b00011; + btn_rawstate_bits = ((!m5gfx::gpio_in(GPIO_NUM_4)) & 1) + | ((!m5gfx::gpio_in(GPIO_NUM_3)) & 1) << 1; + break; + case board_t::board_M5PaperColor: use_rawstate_bits = 0b00111; btn_rawstate_bits = ((!m5gfx::gpio_in(GPIO_NUM_10)) & 1) diff --git a/src/utility/Power_Class.cpp b/src/utility/Power_Class.cpp index d5c69ad..5aed55b 100644 --- a/src/utility/Power_Class.cpp +++ b/src/utility/Power_Class.cpp @@ -267,6 +267,17 @@ namespace m5 _adc_ratio = 2.0f; _wakeupPin = GPIO_NUM_48; // touch panel INT break; + + case board_t::board_M5PaperDIY: + _pmic = pmic_t::pmic_m5pm1; + M5pm1.setGPIOFunction(M5PM1_Class::gpio2, M5PM1_Class::gpio); + M5pm1.setGPIOMode(M5PM1_Class::gpio2, M5PM1_Class::output); + M5pm1.setGPIODrive(M5PM1_Class::gpio2, M5PM1_Class::push_pull); + M5pm1.setGPIOOutput(M5PM1_Class::gpio2, true); // EPD_PWR + M5pm1.setGPIOFunction(M5PM1_Class::gpio3, M5PM1_Class::gpio); + M5pm1.setGPIOMode(M5PM1_Class::gpio3, M5PM1_Class::input); + M5pm1.setGPIOPull(M5PM1_Class::gpio3, M5PM1_Class::pull_up); // CHG_STAT, active low + break; case board_t::board_M5PaperColor: _rtcIntPin = GPIO_NUM_7; @@ -1951,6 +1962,13 @@ namespace m5 return M5pm1.getGPIOInput(M5PM1_Class::gpio0) ? is_charging_t::is_discharging : is_charging_t::is_charging; } break; + + case board_t::board_M5PaperDIY: + { + // PM1_G3 is CHG_STAT, low=charging / high=not charging + return M5pm1.getGPIOInput(M5PM1_Class::gpio3) ? is_charging_t::is_discharging : is_charging_t::is_charging; + } + break; case board_t::board_M5StopWatch: // M5PM1_G2 case board_t::board_M5StampS3Bat: // M5PM1_G2 From 79c90bcc3fbe17457c1c4e4d0439a88fdc76de71 Mon Sep 17 00:00:00 2001 From: ainyan03 Date: Wed, 22 Jul 2026 18:54:40 +0900 Subject: [PATCH 15/16] Require M5GFX >=0.2.26 for M5PaperDIY board support --- idf_component.yml | 2 +- library.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/idf_component.yml b/idf_component.yml index 8e0f388..f1011ad 100644 --- a/idf_component.yml +++ b/idf_component.yml @@ -5,4 +5,4 @@ url: https://github.com/m5stack/M5Unified.git version: 0.2.19 dependencies: m5stack/m5gfx: - version: ">=0.2.25" + version: ">=0.2.26" diff --git a/library.json b/library.json index af213b7..805983c 100644 --- a/library.json +++ b/library.json @@ -13,7 +13,7 @@ "dependencies": [ { "name": "M5GFX", - "version": ">=0.2.25" + "version": ">=0.2.26" } ], "version": "0.2.19", From f806c81a787a38371d1f1c15bcdae2f5754b8f36 Mon Sep 17 00:00:00 2001 From: ainyan03 Date: Wed, 22 Jul 2026 18:54:47 +0900 Subject: [PATCH 16/16] Add recently added boards to HowToUse example and README - HowToUse: PaperDIY, StampS3Bat / StampS3Mini / StampPLC / AirQ / StackChan / ChainCaptain / DualKey (S3), new C5 section (StampC5 / ToughC5), StampC6 (C6), StampP4 (P4) - Fix nonexistent enum name board_NanoH2 -> board_M5NanoH2 in ESP32H2 section - README: sync supported device lists, add ESP32-C5 section --- README.md | 15 ++++++++++++++- examples/Basic/HowToUse/HowToUse.ino | 17 +++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f4c3992..6dabc70 100644 --- a/README.md +++ b/README.md @@ -65,24 +65,35 @@ or have explicit support in the M5Unified library. To use these functions, simp - M5StickS3 - M5ATOMS3 / S3Lite / S3U - M5ATOMS3R / S3RExt / S3RCam / ECHO S3R - - M5STAMPS3 (S3 / S3A) + - M5STAMPS3 (S3 / S3A) / S3Bat / S3Mini + - M5STAMPPLC - M5Dial - M5DinMeter - M5Capsule - M5Cardputer / M5CardputerADV - M5VAMeter + - M5AirQ - M5PaperS3 - M5PaperColor - M5PaperMono + - M5PaperDIY - M5PowerHub - M5StopWatch + - M5StackChan + - M5ChainCaptain + - M5DualKey ## Supported devices (ESP32-C3) - M5STAMPC3 / C3U +## Supported devices (ESP32-C5) + - M5STAMPC5 + - M5ToughC5 + ## Supported devices (ESP32-C6) - M5NanoC6 - M5UnitC6L + - M5STAMPC6 - ArduinoNessoN1 ## Supported devices (ESP32-H2) @@ -90,6 +101,8 @@ or have explicit support in the M5Unified library. To use these functions, simp ## Supported devices (ESP32-P4) - M5Tab5 + - M5STAMPP4 + - M5UnitPoEP4 ## Supported external displays and video adapters - Unit LCD diff --git a/examples/Basic/HowToUse/HowToUse.ino b/examples/Basic/HowToUse/HowToUse.ino index aff6870..40fa1f7 100644 --- a/examples/Basic/HowToUse/HowToUse.ino +++ b/examples/Basic/HowToUse/HowToUse.ino @@ -285,6 +285,9 @@ void setup(void) case m5::board_t::board_M5StackCoreS3SE: name = "StackCoreS3SE"; break; case m5::board_t::board_M5StickS3: name = "StickS3"; break; case m5::board_t::board_M5StampS3: name = "StampS3"; break; + case m5::board_t::board_M5StampS3Bat: name = "StampS3Bat"; break; + case m5::board_t::board_M5StampS3Mini: name = "StampS3Mini"; break; + case m5::board_t::board_M5StampPLC: name = "StampPLC"; break; case m5::board_t::board_M5AtomS3U: name = "ATOMS3U"; break; case m5::board_t::board_M5AtomS3Lite: name = "ATOMS3Lite"; break; case m5::board_t::board_M5AtomS3: name = "ATOMS3"; break; @@ -299,22 +302,32 @@ void setup(void) case m5::board_t::board_M5CardputerADV: name = "CardputerADV"; break; case m5::board_t::board_M5VAMeter: name = "VAMeter"; break; case m5::board_t::board_M5PaperS3: name = "PaperS3"; break; + case m5::board_t::board_M5PaperDIY: name = "PaperDIY"; break; case m5::board_t::board_M5PowerHub: name = "PowerHub"; break; case m5::board_t::board_M5PaperColor: name = "PaperColor"; break; case m5::board_t::board_M5PaperMono: name = "PaperMono"; break; case m5::board_t::board_M5StopWatch: name = "StopWatch"; break; + case m5::board_t::board_M5AirQ: name = "AirQ"; break; + case m5::board_t::board_M5StackChan: name = "StackChan"; break; + case m5::board_t::board_M5ChainCaptain: name = "ChainCaptain"; break; + case m5::board_t::board_M5DualKey: name = "DualKey"; break; #elif defined (CONFIG_IDF_TARGET_ESP32C3) case m5::board_t::board_M5StampC3: name = "StampC3"; break; case m5::board_t::board_M5StampC3U: name = "StampC3U"; break; +#elif defined (CONFIG_IDF_TARGET_ESP32C5) + case m5::board_t::board_M5StampC5: name = "StampC5"; break; + case m5::board_t::board_M5ToughC5: name = "ToughC5"; break; #elif defined (CONFIG_IDF_TARGET_ESP32C6) case m5::board_t::board_M5NanoC6: name = "NanoC6"; break; case m5::board_t::board_M5UnitC6L: name = "UnitC6L"; break; + case m5::board_t::board_M5StampC6: name = "StampC6"; break; case m5::board_t::board_ArduinoNessoN1: name = "NessoN1"; break; #elif defined (CONFIG_IDF_TARGET_ESP32H2) - case m5::board_t::board_NanoH2: name = "NanoH2"; break; + case m5::board_t::board_M5NanoH2: name = "NanoH2"; break; #elif defined (CONFIG_IDF_TARGET_ESP32P4) case m5::board_t::board_M5Tab5: name = "Tab5"; break; - case m5::board_t::board_M5UnitPoEP4: name = "UnitPoEP4"; break; + case m5::board_t::board_M5UnitPoEP4: name = "UnitPoEP4"; break; + case m5::board_t::board_M5StampP4: name = "StampP4"; break; #else case m5::board_t::board_M5Stack: name = "Stack"; break; case m5::board_t::board_M5StackCore2: name = "StackCore2"; break;