diff --git a/idf_component.yml b/idf_component.yml index a05d1261..b40dc9bf 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.25 +version: 0.2.26 dependencies: m5stack/m5gfx: version: ">=0.2.31" diff --git a/library.json b/library.json index 1d5fba4a..2a3199a8 100644 --- a/library.json +++ b/library.json @@ -16,7 +16,7 @@ "version": ">=0.2.31" } ], - "version": "0.2.25", + "version": "0.2.26", "frameworks": ["arduino", "espidf", "*"], "platforms": ["espressif32", "native"], "headers": "M5Unified.h" diff --git a/library.properties b/library.properties index 59a8a635..d19bb00e 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=M5Unified -version=0.2.25 +version=0.2.26 author=M5Stack maintainer=M5Stack sentence=Unified library for M5Stack series diff --git a/src/M5Unified.inl b/src/M5Unified.inl index 491f0d9f..81e86730 100644 --- a/src/M5Unified.inl +++ b/src/M5Unified.inl @@ -1902,6 +1902,9 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { board = board_t::board_M5Capsule; // 自動検出の際。PortAに余分な波形が出ているので、一度 I2C STOPコンディションを出しておく。 // ※ これをしないと正しく動作しないデバイスが存在した。UnitHEART MAX30100 + // Release the Grove pins afterwards: left as push-pull outputs they keep driving High, + // and drivers that only enable the input (e.g. RMT RX on ESP-IDF 5.4+) cannot see the line. + m5gfx::gpio::pin_backup_t grove_backup[] = { GPIO_NUM_15, GPIO_NUM_13 }; m5gfx::gpio::command( (const uint8_t[]) { m5gfx::gpio::command_mode_output, GPIO_NUM_15, @@ -1913,6 +1916,9 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { m5gfx::gpio::command_end } ); + for (auto &backup : grove_backup) { + backup.restore(); + } } } for (auto &backup : pin_backup) { @@ -2250,6 +2256,7 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { #if !defined (M5UNIFIED_PC_BUILD) int led_count = 1; int byte_per_led = 3; + int pin_power = -1; switch (board) { #if defined (CONFIG_IDF_TARGET_ESP32S3) @@ -2273,6 +2280,14 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { case board_t::board_M5PaperColor: led_count = 2; break; +#elif defined (CONFIG_IDF_TARGET_ESP32C6) + case board_t::board_M5NanoC6: + pin_power = GPIO_NUM_19; // RGB LED power switch + break; +#elif defined (CONFIG_IDF_TARGET_ESP32H2) + case board_t::board_M5NanoH2: + pin_power = GPIO_NUM_10; // RGB LED power switch + break; #else case board_t::board_M5AtomMatrix: led_count = 25; @@ -2288,6 +2303,7 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { auto busled = std::make_shared(); auto buscfg = busled->getConfig(); buscfg.pin_data = pin_rgb_led; + buscfg.pin_power = pin_power; busled->setConfig(buscfg); auto led_strip = std::make_shared(); auto ledcfg = led_strip->getConfig(); diff --git a/src/gitTagVersion.h b/src/gitTagVersion.h index 4a82349b..043aae5a 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 25 +#define M5UNIFIED_VERSION_PATCH 26 #define M5UNIFIED_VERSION F( M5UNIFIED_VERSION_MAJOR "." M5UNIFIED_VERSION_MINOR "." M5UNIFIED_VERSION_PATCH ) diff --git a/src/utility/Power_Class.hpp b/src/utility/Power_Class.hpp index eb66396e..68af41bc 100644 --- a/src/utility/Power_Class.hpp +++ b/src/utility/Power_Class.hpp @@ -94,7 +94,9 @@ namespace m5 /// @note On the CoreS3 family (CoreS3 / CoreS3 SE / StackChan), disabling an enabled output blocks for /// about 200 ms (the boost converter is stopped first and the bus is left to discharge before the /// switch-over), and enabling without a battery may block for up to 1 s while the protection check - /// waits for the TS reading to settle. The switch-over is serialized with setUsbOutput and the + /// waits for the TS reading to settle. Enabling also precharges the bus with short pulses first + /// (about 10 ms), so that the empty bus does not pull the PMIC under its DCDC under-voltage + /// threshold on a weak USB supply. The switch-over is serialized with setUsbOutput and the /// internal speaker enable, so those may wait for it as well. /// @note Core2 v1.1 (AXP2101; a Tough with the AXP2101 takes the same path): disabling /// the output while powered from USB with no battery makes the ESP32 brownout and @@ -180,7 +182,7 @@ namespace m5 /// set battery charge current /// @param max_mA milli ampere. /// @note CoreMatrix selects 180 mA below 650 mA, otherwise 650 mA. - /// @note ToughC5 selects 180 mA below 830 mA, otherwise 830 mA. + /// @note ToughC5 selects 180 mA below 650 mA, otherwise 650 mA. /// @attention Non-functioning models : CoreInk , M5Paper , M5Stack(with non I2C IP5306) void setChargeCurrent(std::uint16_t max_mA); diff --git a/src/utility/Power_Class.inl b/src/utility/Power_Class.inl index f201de0f..cdec4ac3 100644 --- a/src/utility/Power_Class.inl +++ b/src/utility/Power_Class.inl @@ -425,6 +425,14 @@ namespace m5 , 0x30, 0x0F // ADC enabled (for voltage measurement) }; Axp2101.writeRegister8Array(reg_data_array, sizeof(reg_data_array)); + // TS senses the 5V bus (no battery thermistor). Its power-on default comes from the + // EFUSE and differs between chips; when it is left as the temperature input, the + // charger treats the bus voltage as a cold battery and never charges. + // The charge voltage (0x64) is not always at the datasheet default either: a unit read 0x00, + // which is reserved (4.6V in an early datasheet revision). The cell is a 4.2V type. + // Set it before the TS change below lets the charger start. + Axp2101.setChargeVoltage(4200); + Axp2101.bitOn(0x50, 0x10); // TS pin: external input, does not affect the charger // The touch INT is routed to the ESP32 as TOUCH_INT -> AW9523 P1_2 -> AW9523 INTN // -> I2C_INT -> GPIO21. The power key is wired to the AXP2101 PWRON only, and the // AXP2101 IRQ pin is shared with the RTC INT, so neither reaches the ESP32. @@ -863,6 +871,10 @@ namespace m5 // setExtOutput) makes the AXP2101 power itself off, and it stays off until the power // key; let the ESP32 brown out and restart instead. Over-voltage (bit 5) stays enabled. Axp2101.bitOff(0x23, 0x1F); // DCDC1-5 UVP power-off disable + // Charge voltage: the power-on value may be reserved; see the CoreS3 setup. + Axp2101.setChargeVoltage(4200); + // No battery thermistor on TS (reads about 2.9V); see the CoreS3 setup. + Axp2101.bitOn(0x50, 0x10); // TS pin: external input, does not affect the charger // for Core2 v1.1 (AXP2101+INA3221) if (Ina3221[0].begin()) @@ -908,6 +920,50 @@ namespace m5 ~_core_s3_lock_t(void) { if (locked) { xSemaphoreGive(mutex); } } }; + // Before BUS_OUT_EN goes 0 -> 1, precharge BUS_OUT with short pulses of growing on-time. + // Connecting the empty BUS_OUT at once on a weak USB supply pulls the AXP2101 DCDC under + // its threshold through the inrush, and the PMIC powers the board off. + // BUS_OUT has a constant discharge path, so short fixed pulses do not accumulate. Once it + // passes about 1.7 V it is pulled up from VBUS even with BUS_OUT_EN = 0, and the final + // enable is a small step. Each on-time is one register write (about 0.3 ms at 100 kHz) + // plus i * 16 us; M5GFX's board detection runs the same sequence at 100 kHz. + // After a failed on- or off-pulse write, OFF is rewritten and read back up to three times: + // confirmed off aborts a failed on-pulse or continues the ramp after a failed off-pulse, + // still on goes to the final enable, and an unknown state returns false. + static bool _core_s3_bus_precharge(uint8_t port0_off) + { + static constexpr const uint8_t port0_reg = 0x02; + enum class off_state_t { unknown, off, on }; + auto confirm_off = [&]() { + uint8_t cur = 0; + bool read_ok = false; + for (int retry = 0; retry < 3; ++retry) + { + M5.In_I2C.writeRegister8(aw9523_i2c_addr, port0_reg, port0_off, i2c_freq); + read_ok = M5.In_I2C.readRegister(aw9523_i2c_addr, port0_reg, &cur, 1, i2c_freq); + if (read_ok && !(cur & _core_s3_bus_en)) { return off_state_t::off; } + } + return read_ok ? off_state_t::on : off_state_t::unknown; + }; + for (int i = 0; i < 8; ++i) + { + if (!M5.In_I2C.writeRegister8(aw9523_i2c_addr, port0_reg, port0_off | _core_s3_bus_en, i2c_freq)) + { + if (confirm_off() == off_state_t::on) { break; } + return false; + } + m5gfx::delayMicroseconds(i * 16); + if (!M5.In_I2C.writeRegister8(aw9523_i2c_addr, port0_reg, port0_off, i2c_freq)) + { + const auto state = confirm_off(); + if (state == off_state_t::unknown) { return false; } + if (state == off_state_t::on) { break; } + } + m5gfx::delayMicroseconds(1000); + } + return true; + } + static void _core_s3_output_locked(uint8_t mask, bool enable) { static constexpr const uint8_t port0_reg = 0x02; @@ -974,6 +1030,20 @@ namespace m5 if (enable) { if (!M5.In_I2C.writeRegister8(aw9523_i2c_addr, port1_reg, buf[1], i2c_freq)) { return; } + if ((mask & _core_s3_bus_en) && !(orig[0] & _core_s3_bus_en)) + { + if (!_core_s3_bus_precharge(orig[0])) { return; } + // A precharged BUS_OUT left with BUS_OUT_EN = 0 is pulled up from VBUS, and every later + // enable is then cancelled by the TS check; read the final write back, bounded retries. + for (int retry = 0; retry < 3; ++retry) + { + uint8_t cur; + if (M5.In_I2C.writeRegister8(aw9523_i2c_addr, port0_reg, buf[0], i2c_freq) + && M5.In_I2C.readRegister(aw9523_i2c_addr, port0_reg, &cur, 1, i2c_freq) + && cur == buf[0]) { return; } + } + return; + } M5.In_I2C.writeRegister8(aw9523_i2c_addr, port0_reg, buf[0], i2c_freq); } else @@ -2646,11 +2716,11 @@ namespace m5 case pmic_t::pmic_m5pm1: if (M5.getBoard() == board_t::board_M5ToughC5) { - // ToughC5 CHG_PROG is IOE1 G1: low selects 830 mA, high selects 180 mA. + // ToughC5 CHG_PROG is IOE1 G1: low selects 650 mA, high selects 180 mA. // Set the latch before enabling push-pull output to avoid a transient // selection of the opposite current during the mode transition. auto& ioe1 = M5.getIOExpander(0); - const bool select_180mA = max_mA < 830; + const bool select_180mA = max_mA < 650; ioe1.setPullMode(M5IOE1_Class::gpio1, IOExpander_Base::pull_none); ioe1.digitalWrite(M5IOE1_Class::gpio1, select_180mA); ioe1.setHighImpedance(M5IOE1_Class::gpio1, false); diff --git a/src/utility/Speaker_Class.inl b/src/utility/Speaker_Class.inl index cc8e0ef8..05982e4c 100644 --- a/src/utility/Speaker_Class.inl +++ b/src/utility/Speaker_Class.inl @@ -574,8 +574,22 @@ namespace m5 if (buf_cnt) { // (no data... wait for new data) --buf_cnt; - uint32_t wait_msec = 1 + (self->_cfg.dma_buf_len / (spk_sample_rate_x256 >> 17)); - flg_nodata = (0 == ulTaskNotifyTake( pdFALSE, wait_msec )); + if (self->_cfg.buzzer) + { + uint32_t wait_msec = 1 + (self->_cfg.dma_buf_len / (spk_sample_rate_x256 >> 17)); + flg_nodata = (0 == ulTaskNotifyTake( pdFALSE, wait_msec )); + } + else + { // Keep the DMA fed at the hold level instead of sleeping. A timed wait can let the + // DMA reach the auto-cleared buffers, which output 0 (on the DAC, a drop to the zero + // level and a jump back to the bias). The blocking write paces this loop. + uint32_t hold = self->_cfg.use_dac ? (uint32_t)(dac_offset | dac_offset << 16) : 0u; + size_t words = dma_buf_len >> 1; + for (size_t i = 0; i < words; ++i) { sound_buf32[i] = hold; } + size_t write_bytes; + _spk_i2s_write(i2s_port, sound_buf32, words * sizeof(int32_t), &write_bytes, portMAX_DELAY); + flg_nodata = (0 == ulTaskNotifyTake( pdFALSE, 0 )); + } } if (flg_nodata && 0 == buf_cnt) diff --git a/src/utility/led/LED_Strip_Class.hpp b/src/utility/led/LED_Strip_Class.hpp index c17161d3..98a164bb 100644 --- a/src/utility/led/LED_Strip_Class.hpp +++ b/src/utility/led/LED_Strip_Class.hpp @@ -102,6 +102,9 @@ namespace m5 uint16_t t1l_ns = 300; uint16_t reset_us = 280; int8_t pin_data = -1; + /// GPIO that switches the LED power supply on (active high); -1 if the LEDs are always powered. + /// It is driven at init(), i.e. when the LEDs are first used, so an unused LED stays unpowered. + int8_t pin_power = -1; }; const config_t& config(void) const { return _config; } const config_t& getConfig(void) const { return _config; } diff --git a/src/utility/led/LED_Strip_Class.inl b/src/utility/led/LED_Strip_Class.inl index 871ddea8..aadb10d4 100644 --- a/src/utility/led/LED_Strip_Class.inl +++ b/src/utility/led/LED_Strip_Class.inl @@ -141,6 +141,14 @@ namespace m5 // The RMT driver does not clear a stale open-drain pad flag. (ESP-IDF v6 removed io_od_mode) m5gfx::gpio_lo(_config.pin_data); m5gfx::pinMode(_config.pin_data, m5gfx::pin_mode_t::output); + if (_config.pin_power >= 0) + { + m5gfx::gpio_hi(_config.pin_power); + m5gfx::pinMode(_config.pin_power, m5gfx::pin_mode_t::output); + // A frame sent right after switching the supply on is lost (measured on NanoC6 / NanoH2: + // 0 ms fails, 1 ms works); give the LEDs a few ms to come up. + m5gfx::delay(5); + } rmt_tx_channel_config_t rmt_tx; memset(&rmt_tx, 0, sizeof(rmt_tx)); @@ -174,6 +182,10 @@ namespace m5 } void LedBus_RMT::release(void) { + if (_config.pin_power >= 0) + { + m5gfx::gpio_lo(_config.pin_power); + } #if M5UNIFIED_RMT_VERSION == 2 auto rmt_ch_handle = _rmt_ch_handle; auto led_encoder = _led_encoder;