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 }} 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 }} 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. 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/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/M5Unified.cpp b/src/M5Unified.cpp index e27203d..ddecd2d 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 @@ -103,8 +104,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 @@ -165,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] = { @@ -211,6 +215,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 }, @@ -367,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]) }, @@ -406,6 +411,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 +564,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 +999,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) { @@ -1509,6 +1582,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) { @@ -1624,6 +1703,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; @@ -1774,6 +1857,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: @@ -1856,6 +1945,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 +2058,27 @@ 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_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) + { + 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 +2382,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) @@ -2713,8 +2854,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; @@ -2783,6 +2924,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) diff --git a/src/M5Unified.hpp b/src/M5Unified.hpp index 3bb85f0..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, @@ -639,6 +641,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 +651,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/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 ) diff --git a/src/utility/Power_Class.cpp b/src/utility/Power_Class.cpp index 8db5256..c2d1983 100644 --- a/src/utility/Power_Class.cpp +++ b/src/utility/Power_Class.cpp @@ -37,6 +37,8 @@ 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; #elif defined (CONFIG_IDF_TARGET_ESP32C6) @@ -195,6 +197,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; { @@ -239,6 +268,23 @@ 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; + // 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: _batAdcCh = ADC1_GPIO6_CHANNEL; @@ -545,6 +591,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); } @@ -633,6 +691,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 +828,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 @@ -1605,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) { @@ -1841,7 +1908,25 @@ namespace m5 default: switch (M5.getBoard()) { #if defined (CONFIG_IDF_TARGET_ESP32S3) - case board_t::board_M5StickS3: + case board_t::board_M5PaperMono: + { + // PM1 PWR_SRC (0x04) [2:0]: 0=5VIN / 1=5VINOUT / 2=BAT + // Running from battery (no external power) -> not charging. + uint8_t pwr_src = M5.In_I2C.readRegister8(m5pm1_i2c_addr, 0x04, i2c_freq) & 0x07; + 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; + + 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 +1934,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 +1994,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 @@ -1966,6 +2053,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 @@ -1997,6 +2097,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) { diff --git a/src/utility/RTC_Class.cpp b/src/utility/RTC_Class.cpp index a04014f..11472a9 100644 --- a/src/utility/RTC_Class.cpp +++ b/src/utility/RTC_Class.cpp @@ -40,8 +40,10 @@ 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: + 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