From 115e9394c999bd618f4382aabfe7140c5cbe9485 Mon Sep 17 00:00:00 2001 From: ainyan03 Date: Fri, 15 May 2026 17:17:42 +0900 Subject: [PATCH 01/13] ci: add workflow to publish to ESP Component Registry on tag push --- .github/workflows/PushESPComponent.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/PushESPComponent.yml diff --git a/.github/workflows/PushESPComponent.yml b/.github/workflows/PushESPComponent.yml new file mode 100644 index 0000000..db5cf49 --- /dev/null +++ b/.github/workflows/PushESPComponent.yml @@ -0,0 +1,22 @@ +name: Push component to ESP Component Registry + +on: + push: + tags: + - '*.*.*' + +jobs: + upload_components: + if: github.repository == 'm5stack/M5Unified' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: 'recursive' + - name: Upload component to the component registry + uses: espressif/upload-components-ci-action@v1 + with: + name: 'm5unified' + namespace: 'm5stack' + version: ${{ github.ref_name }} + api_token: ${{ secrets.IDF_COMPONENT_API_TOKEN }} From 87f5a4ad58543c6320aadf909c85893862c43930 Mon Sep 17 00:00:00 2001 From: ainyan03 Date: Fri, 15 May 2026 17:28:45 +0900 Subject: [PATCH 02/13] fix: detect M5GFX/m5gfx flavor to support local checkout and managed_components --- CMakeLists.txt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bef75f0..a57a9fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,10 +11,20 @@ file(GLOB SRCS ) set(COMPONENT_SRCS ${SRCS}) +# ESP-IDF uses the directory name as the component name (case sensitive). +# Use "M5GFX" when a sibling components/M5GFX checkout exists, otherwise "m5gfx" +# (managed_components/m5stack__m5gfx is registered as the lower-case "m5gfx"). +get_filename_component(_m5u_parent "${CMAKE_CURRENT_LIST_DIR}" DIRECTORY) +if(EXISTS "${_m5u_parent}/M5GFX/CMakeLists.txt") + set(_m5gfx_name M5GFX) +else() + set(_m5gfx_name m5gfx) +endif() + if (IDF_VERSION_MAJOR GREATER_EQUAL 5) - set(COMPONENT_REQUIRES M5GFX esp_adc driver) + set(COMPONENT_REQUIRES ${_m5gfx_name} esp_adc driver) else() - set(COMPONENT_REQUIRES M5GFX esp_adc_cal) + set(COMPONENT_REQUIRES ${_m5gfx_name} esp_adc_cal) endif() ### If you use arduino-esp32 components, please activate next comment line. From 0514670d82af71dbdc58f0d1a238ce2fb34ea334 Mon Sep 17 00:00:00 2001 From: ainyan03 Date: Fri, 15 May 2026 18:08:04 +0900 Subject: [PATCH 03/13] ci: clone M5GFX into _deps/m5gfx so the component name resolves as 'm5gfx' --- .github/workflows/IDFBuild.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/IDFBuild.yml b/.github/workflows/IDFBuild.yml index c7fe737..9caa94e 100644 --- a/.github/workflows/IDFBuild.yml +++ b/.github/workflows/IDFBuild.yml @@ -75,13 +75,13 @@ jobs: run: | git clone --depth 1 --branch "${{ steps.m5gfx.outputs.branch }}" \ "https://github.com/${{ steps.m5gfx.outputs.repo }}.git" \ - "$GITHUB_WORKSPACE/_deps/M5GFX" + "$GITHUB_WORKSPACE/_deps/m5gfx" - name: Build working-directory: examples/Test/build_test shell: bash env: - M5GFX_PATH: ${{ github.workspace }}/_deps/M5GFX + M5GFX_PATH: ${{ github.workspace }}/_deps/m5gfx run: | . $IDF_PATH/export.sh idf.py set-target ${{ matrix.target }} From fbc841ff55e41020a6188fce5e8a20dcf41f21f1 Mon Sep 17 00:00:00 2001 From: luoweiyuan Date: Mon, 18 May 2026 17:08:44 +0800 Subject: [PATCH 04/13] board: Add StopWatch support. --- src/M5Unified.cpp | 114 +++++++++++++++++++++++++++++++++++- src/M5Unified.hpp | 2 + src/utility/Power_Class.cpp | 57 +++++++++++++++++- 3 files changed, 170 insertions(+), 3 deletions(-) diff --git a/src/M5Unified.cpp b/src/M5Unified.cpp index e27203d..856e18e 100644 --- a/src/M5Unified.cpp +++ b/src/M5Unified.cpp @@ -406,6 +406,7 @@ 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; @@ -558,6 +559,38 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { return true; } + bool M5Unified::_speaker_enabled_cb_stopwatch(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, 0xBF, // 0x32 DAC/ DAC volume (0xBF == ±0 dB ) + 2, 0x37, 0x08, // 0x37 DAC/ Bypass DAC equalizer - NOT default + 0 + }; + if (enabled) + { + self->In_I2C.bitOn(m5ioe1_i2c_addr, 0x05, 0b00000100, 100000); // 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) + } + 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) + } +#endif + return true; + } + bool M5Unified::_speaker_enabled_cb_tab5(void* args, bool enabled) { (void)args; @@ -961,6 +994,41 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { return true; } + + bool M5Unified::_microphone_enabled_cb_stopwatch(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, 0xBA, // 0x01 CLOCK_MANAGER/ MCLK=BCLK + 2, 0x02, 0x18, // 0x02 CLOCK_MANAGER/ MULT_PRE=3 + 2, 0x0D, 0x01, // 0x0D SYSTEM/ Power up analog circuitry + 2, 0x0E, 0x02, // 0x0E SYSTEM/ : Enable analog PGA, enable ADC modulator + 2, 0x14, 0x10, // ES8311_ADC_REG14 : select Mic1p-Mic1n / PGA GAIN (minimum) + 2, 0x17, 0xFF, // ES8311_ADC_REG17 : ADC_VOLUME (MAXGAIN) // (0xBF == ± 0 dB ) + 2, 0x1C, 0x6A, // ES8311_ADC_REG1C : ADC Equalizer bypass, cancel DC offset in digital domain + 0 + }; + static constexpr const uint8_t disabled_bulk_data[] = { + 2, 0x0D, 0xFC, // 0x0D SYSTEM/ Power down analog circuitry + 2, 0x0E, 0x6A, // 0x0E SYSTEM + 2, 0x00, 0x00, // 0x00 RESET/ CSM POWER DOWN + 0 + }; + if (enabled) + { + self->In_I2C.bitOn(m5ioe1_i2c_addr, 0x05, 0b00000100, 100000); // Enable Audio Power (M5IOE1_G3) + self->delay(5); + } + m5gfx::i2c::i2c_temporary_switcher_t backup_i2c_setting(1, GPIO_NUM_47, GPIO_NUM_48); + in_i2c_bulk_write(es8311_i2c_addr0, enabled ? enabled_bulk_data : disabled_bulk_data, 100000, 3); + backup_i2c_setting.restore(); +#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) { @@ -1856,6 +1924,20 @@ 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 + { + 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); + } + // 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); break; #elif defined (CONFIG_IDF_TARGET_ESP32P4) @@ -1955,7 +2037,19 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { mic_cfg.input_channel = input_channel_t::input_only_left; mic_enable_cb = _microphone_enabled_cb_papercolor; } - break; + break; + + case board_t::board_M5StopWatch: + if (cfg.internal_mic) + { + mic_cfg.pin_mck = GPIO_NUM_18; + mic_cfg.pin_bck = GPIO_NUM_17; + mic_cfg.pin_ws = GPIO_NUM_15; + mic_cfg.pin_data_in = GPIO_NUM_16; + mic_cfg.i2s_port = I2S_NUM_1; + mic_enable_cb = _microphone_enabled_cb_stopwatch; + } + break; case board_t::board_M5AtomS3U: if (cfg.internal_mic) @@ -2259,6 +2353,24 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { } break; + case board_t::board_M5StopWatch: + if (cfg.internal_spk) + { + spk_cfg.pin_mck = GPIO_NUM_18; + spk_cfg.pin_bck = GPIO_NUM_17; + 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.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_stopwatch; + } + break; + case board_t::board_M5Cardputer: case board_t::board_M5CardputerADV: if (cfg.internal_spk) diff --git a/src/M5Unified.hpp b/src/M5Unified.hpp index 3bb85f0..6981ded 100644 --- a/src/M5Unified.hpp +++ b/src/M5Unified.hpp @@ -639,6 +639,7 @@ namespace m5 static bool _speaker_enabled_cb_cores3(void* args, bool enabled); 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_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); @@ -648,6 +649,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_stopwatch(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/Power_Class.cpp b/src/utility/Power_Class.cpp index 8db5256..a4f7f5f 100644 --- a/src/utility/Power_Class.cpp +++ b/src/utility/Power_Class.cpp @@ -37,6 +37,7 @@ 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 int M5PaperS3_CHG_STAT_PIN = GPIO_NUM_4; #elif defined (CONFIG_IDF_TARGET_ESP32C6) @@ -195,6 +196,33 @@ namespace m5 } break; + case board_t::board_M5StopWatch: + _pmic = pmic_t::pmic_m5pm1; + { + // M5PM1: GPIO2 as GPIO input (charge status on G2; low = charging). REG_GPIO_FUNC0 0x16 [5:4]=00. + uint8_t reg_val = M5.In_I2C.readRegister8(m5pm1_i2c_addr, 0x16, i2c_freq); + reg_val &= static_cast(~(0x03u << 4)); + M5.In_I2C.writeRegister8(m5pm1_i2c_addr, 0x16, reg_val, i2c_freq); + reg_val = M5.In_I2C.readRegister8(m5pm1_i2c_addr, 0x10, i2c_freq); + reg_val &= static_cast(~(1u << 2)); // REG_GPIO_MODE: 0=input + M5.In_I2C.writeRegister8(m5pm1_i2c_addr, 0x10, reg_val, i2c_freq); + + // 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); + // 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 + } + break; + case board_t::board_M5StampS3Bat: _pmic = pmic_t::pmic_m5pm1; { @@ -633,6 +661,7 @@ namespace m5 break; case board_t::board_M5StickS3: + case board_t::board_M5StopWatch: case board_t::board_M5PaperColor: if (_pmic == pmic_t::pmic_m5pm1) { @@ -769,6 +798,7 @@ namespace m5 break; case board_t::board_M5StickS3: + case board_t::board_M5StopWatch: case board_t::board_M5PaperColor: { // Read 5V output status: register 0x06 bit 3 @@ -1841,7 +1871,7 @@ namespace m5 default: switch (M5.getBoard()) { #if defined (CONFIG_IDF_TARGET_ESP32S3) - case board_t::board_M5StickS3: + case board_t::board_M5StickS3: { // PM1_G0 is charging status input pin, low=charging / high=not charging uint8_t reg_val = M5.In_I2C.readRegister8(m5pm1_i2c_addr, 0x12, i2c_freq); @@ -1849,7 +1879,8 @@ namespace m5 } break; - case board_t::board_M5StampS3Bat: + case board_t::board_M5StopWatch: // M5PM1_G2 + case board_t::board_M5StampS3Bat: // M5PM1_G2 { // PM1_G2 is charging status input pin, low=charging / high=not charging uint8_t reg_val = M5.In_I2C.readRegister8(m5pm1_i2c_addr, 0x12, i2c_freq); @@ -1908,6 +1939,7 @@ namespace m5 } case board_t::board_M5StampS3Bat: + case board_t::board_M5StopWatch: case board_t::board_M5StickS3: { // Read output voltage from device PM1: register 0x26 (5VOUT_L) and 0x27 (5VOUT_H) // Unit: mV, format: (5VOUT_H << 8) | 5VOUT_L @@ -1997,6 +2029,27 @@ namespace m5 void Power_Class::setVibration(uint8_t level) { +#if !defined (M5UNIFIED_PC_BUILD) && defined (CONFIG_IDF_TARGET_ESP32S3) + 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. + 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); + } 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); + 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); + } + return; + } +#endif #if !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) if (M5.getBoard() == board_t::board_M5StackCore2) { From 945faf962881afa829f380bd786b133499d14f80 Mon Sep 17 00:00:00 2001 From: luoweiyuan Date: Tue, 19 May 2026 11:24:19 +0800 Subject: [PATCH 05/13] Add StopWatch RTC support. --- src/utility/RTC_Class.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utility/RTC_Class.cpp b/src/utility/RTC_Class.cpp index a04014f..ae2f677 100644 --- a/src/utility/RTC_Class.cpp +++ b/src/utility/RTC_Class.cpp @@ -40,6 +40,7 @@ namespace m5 instance.reset(new RTC_PowerHub_Class(RTC_PowerHub_Class::DEFAULT_ADDRESS, 400000)); break; + case board_t::board_M5StopWatch: case board_t::board_M5StampPLC: case board_t::board_M5PaperColor: instance.reset(new RX8130_Class(RX8130_Class::DEFAULT_ADDRESS, 400000, i2c)); From 5bfd67c3df0563690b364d6aabd8ddff8c382f4d Mon Sep 17 00:00:00 2001 From: lovyan03 <42724151+lovyan03@users.noreply.github.com> Date: Tue, 19 May 2026 19:26:37 +0900 Subject: [PATCH 06/13] =?UTF-8?q?Fix=20PaperColor's=20BtnA=E2=87=94BtnB=20?= =?UTF-8?q?swap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/M5Unified.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/M5Unified.cpp b/src/M5Unified.cpp index 856e18e..2a6e0f3 100644 --- a/src/M5Unified.cpp +++ b/src/M5Unified.cpp @@ -2825,8 +2825,8 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { case board_t::board_M5PaperColor: use_rawstate_bits = 0b00111; - btn_rawstate_bits = ((!m5gfx::gpio_in(GPIO_NUM_9)) & 1) - | ((!m5gfx::gpio_in(GPIO_NUM_10)) & 1) << 1 + btn_rawstate_bits = ((!m5gfx::gpio_in(GPIO_NUM_10)) & 1) + | ((!m5gfx::gpio_in(GPIO_NUM_9)) & 1) << 1 | ((!m5gfx::gpio_in(GPIO_NUM_1)) & 1) << 2; break; From e4d3370cd4c1ae1a5fd1cfc6906d6f017524463f Mon Sep 17 00:00:00 2001 From: lovyan03 <42724151+lovyan03@users.noreply.github.com> Date: Wed, 20 May 2026 15:31:20 +0900 Subject: [PATCH 07/13] board: Add M5NanoH2 support --- src/M5Unified.cpp | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/M5Unified.cpp b/src/M5Unified.cpp index 2a6e0f3..8691ceb 100644 --- a/src/M5Unified.cpp +++ b/src/M5Unified.cpp @@ -103,8 +103,10 @@ static constexpr const uint8_t _pin_table_i2c_ex_in[][5] = { #elif defined (CONFIG_IDF_TARGET_ESP32C6) { board_t::board_M5UnitC6L ,GPIO_NUM_8 ,GPIO_NUM_10 , 255 ,255 }, { board_t::board_ArduinoNessoN1,GPIO_NUM_8 ,GPIO_NUM_10 , GPIO_NUM_8 ,GPIO_NUM_10 }, -{ board_t::board_unknown , 255 ,255 , GPIO_NUM_1 ,GPIO_NUM_2 }, // NanoC6 +{ board_t::board_M5NanoC6 , 255 ,255 , GPIO_NUM_1 ,GPIO_NUM_2 }, +{ board_t::board_unknown , 255 ,255 , 255 ,255 }, #elif defined (CONFIG_IDF_TARGET_ESP32H2) +{ board_t::board_M5NanoH2 , 255 ,255 , GPIO_NUM_1 ,GPIO_NUM_2 }, { board_t::board_unknown , 255 ,255 , 255 ,255 }, #elif defined (CONFIG_IDF_TARGET_ESP32P4) { board_t::board_M5Tab5 , GPIO_NUM_32,GPIO_NUM_31 , GPIO_NUM_54,GPIO_NUM_53 }, // Tab5 @@ -211,6 +213,7 @@ static constexpr const uint8_t _pin_table_other0[][2] = { { board_t::board_M5NanoC6 , GPIO_NUM_20 }, { board_t::board_M5UnitC6L , GPIO_NUM_2 }, #elif defined (CONFIG_IDF_TARGET_ESP32H2) +{ board_t::board_M5NanoH2 , GPIO_NUM_11 }, #else { board_t::board_M5Stack , GPIO_NUM_15 }, { board_t::board_M5StackCore2 , GPIO_NUM_25 }, @@ -1577,6 +1580,12 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { board = board_t::board_M5NanoC6; } +#elif defined (CONFIG_IDF_TARGET_ESP32H2) + if (board == board_t::board_unknown) + { // NanoH2 + board = board_t::board_M5NanoH2; + } + #elif defined (CONFIG_IDF_TARGET_ESP32P4) if (board == board_t::board_unknown) { @@ -1842,6 +1851,12 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { m5gfx::pinMode(GPIO_NUM_9, m5gfx::pin_mode_t::input_pullup); break; +#elif defined (CONFIG_IDF_TARGET_ESP32H2) + + case board_t::board_M5NanoH2: + m5gfx::pinMode(GPIO_NUM_9, m5gfx::pin_mode_t::input_pullup); + break; + #elif defined (CONFIG_IDF_TARGET_ESP32S3) case board_t::board_M5AtomS3: case board_t::board_M5AtomS3Lite: @@ -2895,6 +2910,20 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { default: break; } + +#elif defined (CONFIG_IDF_TARGET_ESP32H2) + + switch (_board) + { + case board_t::board_M5NanoH2: + use_rawstate_bits = 0b00001; + btn_rawstate_bits = (!m5gfx::gpio_in(GPIO_NUM_9) ? 0b00001 : 0); + break; + + default: + break; + } + #elif defined (CONFIG_IDF_TARGET_ESP32P4) switch (_board) From b56086e5de8d97f986d1144c145663f4c3f0c9dd Mon Sep 17 00:00:00 2001 From: lovyan03 <42724151+lovyan03@users.noreply.github.com> Date: Wed, 20 May 2026 15:31:20 +0900 Subject: [PATCH 08/13] board: Add M5PaperMono power management (RTC int pin / M5PM1 PMIC) --- src/utility/Power_Class.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/utility/Power_Class.cpp b/src/utility/Power_Class.cpp index a4f7f5f..3bfe575 100644 --- a/src/utility/Power_Class.cpp +++ b/src/utility/Power_Class.cpp @@ -267,6 +267,11 @@ namespace m5 M5.In_I2C.bitOn(m5pm1_i2c_addr, 0x11, 1 << 3, i2c_freq); // Set gpio3 output high } break; + + case board_t::board_M5PaperMono: + _rtcIntPin = GPIO_NUM_1; + _pmic = pmic_t::pmic_m5pm1; + break; case board_t::board_M5Capsule: _batAdcCh = ADC1_GPIO6_CHANNEL; From f106e56d17ef9648008c8c49cc0976505debe43d Mon Sep 17 00:00:00 2001 From: lovyan03 <42724151+lovyan03@users.noreply.github.com> Date: Thu, 21 May 2026 11:30:54 +0900 Subject: [PATCH 09/13] tweak for PaperMono and NanoH2 --- examples/Basic/HowToUse/HowToUse.ino | 3 + src/M5Unified.cpp | 14 ++++ src/utility/RTC_Class.cpp | 1 + src/utility/led/LED_PaperMono_Class.cpp | 96 +++++++++++++++++++++++++ src/utility/led/LED_PaperMono_Class.hpp | 31 ++++++++ 5 files changed, 145 insertions(+) create mode 100644 src/utility/led/LED_PaperMono_Class.cpp create mode 100644 src/utility/led/LED_PaperMono_Class.hpp diff --git a/examples/Basic/HowToUse/HowToUse.ino b/examples/Basic/HowToUse/HowToUse.ino index b5183ec..63c0f94 100644 --- a/examples/Basic/HowToUse/HowToUse.ino +++ b/examples/Basic/HowToUse/HowToUse.ino @@ -285,6 +285,7 @@ void setup(void) case m5::board_t::board_M5PaperS3: name = "PaperS3"; 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; #elif defined (CONFIG_IDF_TARGET_ESP32C3) case m5::board_t::board_M5StampC3: name = "StampC3"; break; @@ -293,6 +294,8 @@ void setup(void) case m5::board_t::board_M5NanoC6: name = "NanoC6"; break; case m5::board_t::board_M5UnitC6L: name = "UnitC6L"; 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; #elif defined (CONFIG_IDF_TARGET_ESP32P4) case m5::board_t::board_M5Tab5: name = "Tab5"; break; #else diff --git a/src/M5Unified.cpp b/src/M5Unified.cpp index 856e18e..76e1d96 100644 --- a/src/M5Unified.cpp +++ b/src/M5Unified.cpp @@ -34,6 +34,7 @@ #include "utility/led/LED_Strip_Class.hpp" #include "utility/led/LED_PMIC_Class.hpp" #include "utility/led/LED_PowerHub_Class.hpp" +#include "utility/led/LED_PaperMono_Class.hpp" #endif @@ -105,6 +106,7 @@ static constexpr const uint8_t _pin_table_i2c_ex_in[][5] = { { board_t::board_ArduinoNessoN1,GPIO_NUM_8 ,GPIO_NUM_10 , GPIO_NUM_8 ,GPIO_NUM_10 }, { board_t::board_unknown , 255 ,255 , GPIO_NUM_1 ,GPIO_NUM_2 }, // NanoC6 #elif defined (CONFIG_IDF_TARGET_ESP32H2) +{ board_t::board_NanoH2 , 255 ,255 , GPIO_NUM_1 ,GPIO_NUM_2 }, { board_t::board_unknown , 255 ,255 , 255 ,255 }, #elif defined (CONFIG_IDF_TARGET_ESP32P4) { board_t::board_M5Tab5 , GPIO_NUM_32,GPIO_NUM_31 , GPIO_NUM_54,GPIO_NUM_53 }, // Tab5 @@ -1692,6 +1694,10 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { Led.setLedInstance(busled); return; } + case board_t::board_M5PaperMono: + Led.setLedInstance(std::make_shared()); + return; + case board_t::board_M5PaperColor: led_count = 2; break; @@ -2039,6 +2045,14 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { } break; + case board_t::board_M5PaperMono: + if (cfg.internal_mic) + { /// builtin PDM mic + mic_cfg.pin_ws = GPIO_NUM_45; + mic_cfg.pin_data_in = GPIO_NUM_46; + } + break; + case board_t::board_M5StopWatch: if (cfg.internal_mic) { diff --git a/src/utility/RTC_Class.cpp b/src/utility/RTC_Class.cpp index ae2f677..11472a9 100644 --- a/src/utility/RTC_Class.cpp +++ b/src/utility/RTC_Class.cpp @@ -43,6 +43,7 @@ namespace m5 case board_t::board_M5StopWatch: case board_t::board_M5StampPLC: case board_t::board_M5PaperColor: + case board_t::board_M5PaperMono: instance.reset(new RX8130_Class(RX8130_Class::DEFAULT_ADDRESS, 400000, i2c)); break; #endif diff --git a/src/utility/led/LED_PaperMono_Class.cpp b/src/utility/led/LED_PaperMono_Class.cpp new file mode 100644 index 0000000..444d390 --- /dev/null +++ b/src/utility/led/LED_PaperMono_Class.cpp @@ -0,0 +1,96 @@ +// Copyright (c) M5Stack. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#include "LED_PaperMono_Class.hpp" +#include "../../M5Unified.hpp" + +#if defined (CONFIG_IDF_TARGET_ESP32S3) + +namespace m5 +{ +// RGBLED_R = PMIC -> LED_EN_PP +// RGBLED_G = PYB -> G8 +// RGBLED_B = PYB -> G2 + + 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; + + 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); + + // IOE1 IO2 (blue), IO8 (green) push-pull + M5.In_I2C.bitOff(m5ioe1_i2c_addr, 0x13, 0x82, i2c_freq); + + + { + setBrightness(_brightness); + return true; + } + return false; + } + + void LED_PaperMono_Class::setColors(const RGBColor* values, size_t index, size_t length) + { + if (index + length > led_count) { + length = led_count - index; + } + std::copy(values, values + length, &_rgb_buffer + index); + } + + 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 + uint32_t br = _brightness + 1; + br = br * br; + + uint16_t r = _rgb_buffer.R8(); + uint16_t g = _rgb_buffer.G8(); + uint16_t b = _rgb_buffer.B8(); + r = (r * br) >> 8; + g = (g * br) >> 8; + b = (b * br) >> 8; + + if (r < 2048) { + M5.In_I2C.bitOff(m5pm1_i2c_addr, 0x06, 0x10, i2c_freq); + } else { + 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); + } + + { // 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); + } + } +} + +#endif diff --git a/src/utility/led/LED_PaperMono_Class.hpp b/src/utility/led/LED_PaperMono_Class.hpp new file mode 100644 index 0000000..6f5db1e --- /dev/null +++ b/src/utility/led/LED_PaperMono_Class.hpp @@ -0,0 +1,31 @@ +// Copyright (c) M5Stack. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +#ifndef M5_PAPERMONO_CLASS_H__ +#define M5_PAPERMONO_CLASS_H__ + +#include "LED_Base.hpp" +#include "../I2C_Class.hpp" + +namespace m5 +{ + class LED_PaperMono_Class : public LED_Base + { + public: + LED_PaperMono_Class() {} + + bool begin(void) override; + led_type_t getLedType(size_t index) const override { return led_type_t::led_type_fullcolor; } + size_t getCount(void) const override { return 1; } + void setColors(const RGBColor* values, size_t index, size_t length) override; + void setBrightness(const uint8_t brightness) override; + void display(void) override; + RGBColor* getBuffer(void) override { return &_rgb_buffer; } + + private: + RGBColor _rgb_buffer; + uint8_t _brightness = 63; + }; +} + +#endif From 58f084ddf532c9864e07f72e8581db10e986846e Mon Sep 17 00:00:00 2001 From: lovyan03 <42724151+lovyan03@users.noreply.github.com> Date: Thu, 21 May 2026 17:55:43 +0900 Subject: [PATCH 10/13] board: Add M5PaperMono SDMMC pins / unify SD pin table Expose M5PaperMono's TF card SDMMC pins (4bit: CLK=13/CMD=12/D0-D3=11/10/9/8) via M5.getPin(). Unify the SD-SPI and SDMMC pin handling into a single _pin_table_sd[][7] (clk/cmd/d0/d1/d2/d3); sd_spi_* are now aliases of the SDMMC slots (sclk=clk, mosi=cmd, miso=d0, cs=d3), matching the SD card SPI-mode pinout. SPI-only boards leave d1/d2 unused (255). TF power enable / card detect via the PY32 IO expander are not handled yet (separate task). --- src/M5Unified.cpp | 35 ++++++++++++++++++----------------- src/M5Unified.hpp | 10 ++++++---- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/M5Unified.cpp b/src/M5Unified.cpp index d0034d9..ddecd2d 100644 --- a/src/M5Unified.cpp +++ b/src/M5Unified.cpp @@ -168,29 +168,30 @@ static constexpr const uint8_t _pin_table_port_de[][5] = { { board_t::board_unknown , 255 ,255 , 255 ,255 }, }; -static constexpr const uint8_t _pin_table_spi_sd[][5] = { - // clk,mosi,miso,cs +static constexpr const uint8_t _pin_table_sd[][7] = { + // clk,cmd(MOSI),D0(MISO),D1,D2,D3(CS) #if defined (CONFIG_IDF_TARGET_ESP32S3) -{ board_t::board_M5StackCoreS3, GPIO_NUM_36, GPIO_NUM_37, GPIO_NUM_35, GPIO_NUM_4 }, -{ board_t::board_M5StackCoreS3SE,GPIO_NUM_36,GPIO_NUM_37, GPIO_NUM_35, GPIO_NUM_4 }, -{ board_t::board_M5StackChan , GPIO_NUM_36, GPIO_NUM_37, GPIO_NUM_35, GPIO_NUM_4 }, -{ board_t::board_M5Capsule , GPIO_NUM_14, GPIO_NUM_12, GPIO_NUM_39, GPIO_NUM_11 }, -{ board_t::board_M5Cardputer , GPIO_NUM_40, GPIO_NUM_14, GPIO_NUM_39, GPIO_NUM_12 }, -{ board_t::board_M5CardputerADV,GPIO_NUM_40, GPIO_NUM_14, GPIO_NUM_39, GPIO_NUM_12 }, -{ board_t::board_M5PaperS3 , GPIO_NUM_39, GPIO_NUM_38, GPIO_NUM_40, GPIO_NUM_47 }, -{ board_t::board_M5StampPLC , GPIO_NUM_7, GPIO_NUM_8, GPIO_NUM_9, GPIO_NUM_10 }, -{ board_t::board_M5PaperColor , GPIO_NUM_15, GPIO_NUM_13, GPIO_NUM_14, GPIO_NUM_47 }, +{ board_t::board_M5StackCoreS3, GPIO_NUM_36, GPIO_NUM_37, GPIO_NUM_35, 255 , 255 , GPIO_NUM_4 }, +{ board_t::board_M5StackCoreS3SE,GPIO_NUM_36,GPIO_NUM_37, GPIO_NUM_35, 255 , 255 , GPIO_NUM_4 }, +{ board_t::board_M5StackChan , GPIO_NUM_36, GPIO_NUM_37, GPIO_NUM_35, 255 , 255 , GPIO_NUM_4 }, +{ board_t::board_M5Capsule , GPIO_NUM_14, GPIO_NUM_12, GPIO_NUM_39, 255 , 255 , GPIO_NUM_11 }, +{ 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_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 }, #elif defined (CONFIG_IDF_TARGET_ESP32C3) #elif defined (CONFIG_IDF_TARGET_ESP32C6) #elif defined (CONFIG_IDF_TARGET_ESP32H2) #elif defined (CONFIG_IDF_TARGET_ESP32P4) -{ board_t::board_M5Tab5 , GPIO_NUM_43,GPIO_NUM_44, GPIO_NUM_39, GPIO_NUM_42 }, +{ board_t::board_M5Tab5 , GPIO_NUM_43, GPIO_NUM_44, GPIO_NUM_39, GPIO_NUM_40, GPIO_NUM_41, GPIO_NUM_42 }, #else -{ board_t::board_M5Stack , GPIO_NUM_18, GPIO_NUM_23, GPIO_NUM_19, GPIO_NUM_4 }, -{ board_t::board_M5StackCore2 , GPIO_NUM_18, GPIO_NUM_23, GPIO_NUM_38, GPIO_NUM_4 }, -{ board_t::board_M5Paper , GPIO_NUM_14, GPIO_NUM_12, GPIO_NUM_13, GPIO_NUM_4 }, +{ board_t::board_M5Stack , GPIO_NUM_18, GPIO_NUM_23, GPIO_NUM_19, 255 , 255 , GPIO_NUM_4 }, +{ board_t::board_M5StackCore2 , GPIO_NUM_18, GPIO_NUM_23, GPIO_NUM_38, 255 , 255 , GPIO_NUM_4 }, +{ board_t::board_M5Paper , GPIO_NUM_14, GPIO_NUM_12, GPIO_NUM_13, 255 , 255 , GPIO_NUM_4 }, #endif -{ board_t::board_unknown , 255 , 255 , 255 , 255 }, +{ board_t::board_unknown , 255 , 255 , 255 , 255 , 255 , 255 }, }; static constexpr const uint8_t _pin_table_other0[][2] = { @@ -371,7 +372,7 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { { _pin_table_i2c_ex_in, sizeof(_pin_table_i2c_ex_in[0]) }, { _pin_table_port_bc, sizeof(_pin_table_port_bc[0]) }, { _pin_table_port_de, sizeof(_pin_table_port_de[0]) }, - { _pin_table_spi_sd, sizeof(_pin_table_spi_sd[0]) }, + { _pin_table_sd, sizeof(_pin_table_sd[0]) }, { _pin_table_other0, sizeof(_pin_table_other0[0]) }, { _pin_table_other1, sizeof(_pin_table_other1[0]) }, { _pin_table_mbus, sizeof(_pin_table_mbus[0]) }, diff --git a/src/M5Unified.hpp b/src/M5Unified.hpp index 6981ded..3ac7b82 100644 --- a/src/M5Unified.hpp +++ b/src/M5Unified.hpp @@ -37,10 +37,12 @@ namespace m5 port_d_pin2, port_d_txd = port_d_pin2, port_b2_pin2 = port_d_pin2, port_e_pin1, port_e_rxd = port_e_pin1, port_c2_pin1 = port_e_pin1, port_e_pin2, port_e_txd = port_e_pin2, port_c2_pin2 = port_e_pin2, - sd_spi_sclk, - sd_spi_copi, sd_spi_mosi = sd_spi_copi, - sd_spi_cipo, sd_spi_miso = sd_spi_cipo, - sd_spi_cs, sd_spi_ss = sd_spi_cs, + sd_mmc_clk, sd_spi_sclk = sd_mmc_clk, + sd_mmc_cmd, sd_spi_copi = sd_mmc_cmd, sd_spi_mosi = sd_mmc_cmd, + sd_mmc_d0, sd_spi_cipo = sd_mmc_d0, sd_spi_miso = sd_mmc_d0, + sd_mmc_d1, + sd_mmc_d2, + sd_mmc_d3, sd_spi_cs = sd_mmc_d3, sd_spi_ss = sd_mmc_d3, rgb_led, power_hold, mbus_pin1, mbus_pin2, mbus_pin3, mbus_pin4, mbus_pin5, From d2c3b60c887ac8a1f3a0c2f220bbf6e880150e25 Mon Sep 17 00:00:00 2001 From: lovyan03 <42724151+lovyan03@users.noreply.github.com> Date: Tue, 26 May 2026 14:04:30 +0900 Subject: [PATCH 11/13] board: M5PaperMono power management and M5PM1 init hardening - begin(): for all m5pm1 boards, disable I2C idle-sleep (I2C_CFG 0x09, SLP_TO=0) and watchdog (WDT_CNT 0x0A). M5Unified's generic I2C access has no PM1 wake handshake, and the always-on PMIC keeps these settings across MCU reset, so re-assert them at init to avoid intermittent comm failures and periodic resets. - M5PaperMono: re-enable battery charging (PWR_CFG 0x06 bit0, cleared on reset/download), add isCharging() via PWR_SRC (0x04) and getKeyState() via IRQ_STATUS3 (0x42). --- src/utility/Power_Class.cpp | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/utility/Power_Class.cpp b/src/utility/Power_Class.cpp index 3bfe575..9cfaaa4 100644 --- a/src/utility/Power_Class.cpp +++ b/src/utility/Power_Class.cpp @@ -271,6 +271,8 @@ namespace m5 case board_t::board_M5PaperMono: _rtcIntPin = GPIO_NUM_1; _pmic = pmic_t::pmic_m5pm1; + // PWR_CFG(0x06) is cleared to 0 on reset/download; re-enable battery charging (CHG_EN, bit0). + M5.In_I2C.bitOn(m5pm1_i2c_addr, 0x06, 1 << 0, i2c_freq); break; case board_t::board_M5Capsule: @@ -578,6 +580,18 @@ namespace m5 #endif + if (_pmic == pmic_t::pmic_m5pm1) + { + // reg: 0x09(I2C_CFG) - Set to 0x00 to disable I2C idle sleep mode. + // PMIC is always-on powered, and with battery power, shutdown doesn't reset the chip. + // This register may have been modified elsewhere, causing PMIC communication issues. + // Explicitly set it here during initialization to ensure proper operation. + M5.In_I2C.writeRegister8(m5pm1_i2c_addr, 0x09, 0x00, i2c_freq); + + // PM1 watchdog is enabled by default; disable it to avoid periodic reset. + M5.In_I2C.writeRegister8(m5pm1_i2c_addr, 0x0A, 0x00, i2c_freq); // WDT_CNT = 0 (disable) + } + #endif return (_pmic != pmic_t::pmic_unknown); } @@ -1876,6 +1890,16 @@ namespace m5 default: switch (M5.getBoard()) { #if defined (CONFIG_IDF_TARGET_ESP32S3) + case board_t::board_M5PaperMono: + { + // PM1 PWR_SRC (0x04) [2:0]: 0=5VIN / 1=5VINOUT / 2=BAT + // On external power (not running from battery) we treat it as charging. + // (The precise "charge complete" state requires the IP2315 charger IC.) + uint8_t pwr_src = M5.In_I2C.readRegister8(m5pm1_i2c_addr, 0x04, i2c_freq) & 0x07; + return (pwr_src == 0x02) ? is_charging_t::is_discharging : is_charging_t::is_charging; + } + break; + case board_t::board_M5StickS3: { // PM1_G0 is charging status input pin, low=charging / high=not charging @@ -2003,6 +2027,19 @@ namespace m5 case pmic_t::pmic_py32pmic: return PY32pmic.getPekPress(); + + case pmic_t::pmic_m5pm1: + { + // PM1 IRQ_STATUS3 (0x42): bit0=Click / bit1=Wakeup / bit2=DoubleClick + // (Long press is handled as power-off/reset by the PMIC hardware.) + uint8_t irq3 = M5.In_I2C.readRegister8(m5pm1_i2c_addr, 0x42, i2c_freq); + if (irq3 & ((1 << 0) | (1 << 2))) + { // a (double) click was detected; clear all button IRQ flags (write 0 to clear). + M5.In_I2C.writeRegister8(m5pm1_i2c_addr, 0x42, 0x00, i2c_freq); + return 2; // short clicked + } + } + return 0; #endif #endif From 122022505b623b5b2773438954d9b49a978a6a40 Mon Sep 17 00:00:00 2001 From: lovyan03 <42724151+lovyan03@users.noreply.github.com> Date: Tue, 26 May 2026 18:11:31 +0900 Subject: [PATCH 12/13] board: M5PaperMono charge control & status via IP2316 charger PaperMono's battery charging is controlled by the IP2316 charger (reached over the IOE1 IO11 "CHARGE READ" enable line), not by PM1. Drive the charger and read its status accordingly. - begin(): drive IOE1 IO11 high, poll the IP2316 until it comes up (it answers ~1.3ms after the high level, measured), then enable charging (SYS_CTL1 0x01 bit0 = EN_CHG). - setBatteryCharge(): toggle IP2316 EN_CHG (0x01 bit0). - isCharging(): on battery power -> discharging; on external power read REG_CHG_STAT(0xC7) bit7 (charging in progress): set -> charging, else discharging (covers charge-complete and charge-disabled). Verified on hardware: setBatteryCharge(false) -> discharging (0xC7=0x00, Vbat drops), setBatteryCharge(true) -> charging (0xC7 bit7, Vbat recovers). The unit label reads IP2316; its 0x75/0x01/0xC7 layout matches IP2315. --- src/utility/Power_Class.cpp | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/src/utility/Power_Class.cpp b/src/utility/Power_Class.cpp index 9cfaaa4..c2d1983 100644 --- a/src/utility/Power_Class.cpp +++ b/src/utility/Power_Class.cpp @@ -38,6 +38,7 @@ namespace m5 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; #elif defined (CONFIG_IDF_TARGET_ESP32C6) @@ -271,8 +272,18 @@ namespace m5 case board_t::board_M5PaperMono: _rtcIntPin = GPIO_NUM_1; _pmic = pmic_t::pmic_m5pm1; - // PWR_CFG(0x06) is cleared to 0 on reset/download; re-enable battery charging (CHG_EN, bit0). - M5.In_I2C.bitOn(m5pm1_i2c_addr, 0x06, 1 << 0, i2c_freq); + // 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). + 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 + // 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); break; case board_t::board_M5Capsule: @@ -1654,6 +1665,13 @@ namespace m5 if (M5.getBoard() == board_t::board_M5PaperColor) { 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); } + return; + } // Control charge enable: register 0x06 bit 0 (1=enable, 0=disable) uint8_t reg_val = M5.In_I2C.readRegister8(m5pm1_i2c_addr, 0x06, i2c_freq); if (enable) { @@ -1893,10 +1911,18 @@ namespace m5 case board_t::board_M5PaperMono: { // PM1 PWR_SRC (0x04) [2:0]: 0=5VIN / 1=5VINOUT / 2=BAT - // On external power (not running from battery) we treat it as charging. - // (The precise "charge complete" state requires the IP2315 charger IC.) + // Running from battery (no external power) -> not charging. uint8_t pwr_src = M5.In_I2C.readRegister8(m5pm1_i2c_addr, 0x04, i2c_freq) & 0x07; - return (pwr_src == 0x02) ? is_charging_t::is_discharging : is_charging_t::is_charging; + if (pwr_src == 0x02) { return is_charging_t::is_discharging; } + // External power present. The IP2316 charger (IO11 enabled in begin()) 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)) + { + 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; + } + return is_charging_t::is_discharging; // fallback: charger not responding -> not charging } break; From f4bf3de33b32204a380485ed4e4efacaf40011aa Mon Sep 17 00:00:00 2001 From: lovyan03 <42724151+lovyan03@users.noreply.github.com> Date: Wed, 27 May 2026 09:00:55 +0900 Subject: [PATCH 13/13] rising version 0.2.16 --- idf_component.yml | 2 +- library.json | 4 ++-- library.properties | 2 +- src/gitTagVersion.h | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/idf_component.yml b/idf_component.yml index 5dc7069..11defd2 100644 --- a/idf_component.yml +++ b/idf_component.yml @@ -2,4 +2,4 @@ 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.15 +version: 0.2.16 diff --git a/library.json b/library.json index 8a53037..0b3eece 100644 --- a/library.json +++ b/library.json @@ -13,10 +13,10 @@ "dependencies": [ { "name": "M5GFX", - "version": ">=0.2.21" + "version": ">=0.2.22" } ], - "version": "0.2.15", + "version": "0.2.16", "frameworks": ["arduino", "espidf", "*"], "platforms": ["espressif32", "native"], "headers": "M5Unified.h" diff --git a/library.properties b/library.properties index 6daa233..6bac5cb 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=M5Unified -version=0.2.15 +version=0.2.16 author=M5Stack maintainer=M5Stack sentence=Unified library for M5Stack series diff --git a/src/gitTagVersion.h b/src/gitTagVersion.h index eea7039..9384882 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 15 +#define M5UNIFIED_VERSION_PATCH 16 #define M5UNIFIED_VERSION F( M5UNIFIED_VERSION_MAJOR "." M5UNIFIED_VERSION_MINOR "." M5UNIFIED_VERSION_PATCH )