diff --git a/src/M5Unified.cpp b/src/M5Unified.cpp index f3b1308..cbc3d01 100644 --- a/src/M5Unified.cpp +++ b/src/M5Unified.cpp @@ -115,6 +115,8 @@ static constexpr const uint8_t _pin_table_i2c_ex_in[][5] = { { 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_ESP32C61) +{ board_t::board_M5CoreMatrix , GPIO_NUM_1 ,GPIO_NUM_0 , GPIO_NUM_1 ,GPIO_NUM_0 }, // Grove shares the internal bus (level-shifted) +{ 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 }, @@ -204,6 +206,7 @@ static constexpr const uint8_t _pin_table_sd[][7] = { #elif defined (CONFIG_IDF_TARGET_ESP32C3) #elif defined (CONFIG_IDF_TARGET_ESP32C6) #elif defined (CONFIG_IDF_TARGET_ESP32C61) +{ board_t::board_M5CoreMatrix , GPIO_NUM_25, GPIO_NUM_27, GPIO_NUM_26, 255 , 255 , GPIO_NUM_28 }, #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_40, GPIO_NUM_41, GPIO_NUM_42 }, @@ -354,6 +357,23 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { #elif defined (CONFIG_IDF_TARGET_ESP32C3) #elif defined (CONFIG_IDF_TARGET_ESP32C6) #elif defined (CONFIG_IDF_TARGET_ESP32C61) +{ board_t::board_M5CoreMatrix, + 255 , GPIO_NUM_3 , + 255 , GPIO_NUM_4 , + 255 , 255 , + GPIO_NUM_27, GPIO_NUM_5 , + GPIO_NUM_26, GPIO_NUM_6 , + GPIO_NUM_25, 255 , + GPIO_NUM_10, GPIO_NUM_11, + GPIO_NUM_7 , GPIO_NUM_8 , + GPIO_NUM_0 , GPIO_NUM_1 , + GPIO_NUM_0 , GPIO_NUM_1 , + GPIO_NUM_23, GPIO_NUM_22, + GPIO_NUM_24, GPIO_NUM_9 , + 255 , GPIO_NUM_29, + 255 , 255 , + 255 , 255 , +}, #elif defined (CONFIG_IDF_TARGET_ESP32H2) #elif defined (CONFIG_IDF_TARGET_ESP32C5) #else @@ -1864,6 +1884,15 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { _io_expander[0].reset(ioexp); } break; +#elif defined (CONFIG_IDF_TARGET_ESP32C61) + case board_t::board_M5CoreMatrix: + { /// Controls the LED matrix / TF / Grove / buzzer power rails, + /// the charge current selector and the buzzer PWM. + auto ioexp = new M5IOE1_Class; + ioexp->begin(); + _io_expander[0].reset(ioexp); + } + break; #endif default: break; @@ -2914,6 +2943,23 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { } break; + default: + break; + } +#elif defined (CONFIG_IDF_TARGET_ESP32C61) + switch (getBoard()) + { + case board_t::board_M5CoreMatrix: + { // KEY and IMU wake events are funneled into the PM1, whose IRQ output + // (GPIO2) is the only wakeup pin. The IRQ output stays low until every + // IRQ status bit is cleared, so clear them here to release the pin. + // Clear WAKE_SRC first: while it is set, the WAKEUP bit of IRQ status 3 + // keeps getting re-asserted. + Power.M5pm1.clearWakeSource(); + Power.M5pm1.clearIRQStatus(); + } + break; + default: break; } @@ -3273,6 +3319,28 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { break; } +#elif defined (CONFIG_IDF_TARGET_ESP32C61) + + switch (_board) + { + case board_t::board_M5CoreMatrix: + /// KEY1/2/3 are wired to PM1 GPIO0/1/2 (pressed = LOW), not to the ESP, + /// so they are read by I2C polling. Skip the update on an I2C failure so + /// a bus error is not reported as a button press. + { + uint8_t in; + if (Power.M5pm1.getGPIOInputBits(&in)) + { + use_rawstate_bits = 0b00111; + btn_rawstate_bits = (~in) & 0b00111; + } + } + break; + + default: + break; + } + #endif if (use_rawstate_bits) { @@ -3283,7 +3351,7 @@ static constexpr const uint8_t _pin_table_mbus[][31] = { } } -#if defined (CONFIG_IDF_TARGET_ESP32) || defined (CONFIG_IDF_TARGET_ESP32S3) || defined (CONFIG_IDF_TARGET_ESP32C5) +#if defined (CONFIG_IDF_TARGET_ESP32) || defined (CONFIG_IDF_TARGET_ESP32S3) || defined (CONFIG_IDF_TARGET_ESP32C5) || defined (CONFIG_IDF_TARGET_ESP32C61) if (_use_pmic_button) { Button_Class::button_state_t state = Button_Class::button_state_t::state_nochange; diff --git a/src/utility/IMU_Class.cpp b/src/utility/IMU_Class.cpp index 8ea9b75..4e402a4 100644 --- a/src/utility/IMU_Class.cpp +++ b/src/utility/IMU_Class.cpp @@ -108,6 +108,12 @@ namespace m5 _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); } +#elif defined(CONFIG_IDF_TARGET_ESP32C61) + if (board == m5::board_t::board_M5CoreMatrix) + { // CoreMatrix BMI270 : X=+Y, Y=-X, Z=+Z (90-degree rotation about Z) + _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); + } #endif } diff --git a/src/utility/IOExpander_Base.hpp b/src/utility/IOExpander_Base.hpp index 93bade7..2580e4e 100644 --- a/src/utility/IOExpander_Base.hpp +++ b/src/utility/IOExpander_Base.hpp @@ -33,6 +33,15 @@ namespace m5 virtual bool digitalRead(uint8_t pin) = 0; + /// digitalRead with I2C error reporting. returns false on I2C failure + /// or when the expander driver does not implement error detection. + virtual bool getInputLevel(uint8_t pin, bool* level) + { + (void)pin; + (void)level; + return false; + } + virtual void resetIrq() = 0; virtual void disableIrq() = 0; diff --git a/src/utility/M5IOE1_Class.cpp b/src/utility/M5IOE1_Class.cpp index 4473ba5..7b0f08c 100644 --- a/src/utility/M5IOE1_Class.cpp +++ b/src/utility/M5IOE1_Class.cpp @@ -102,6 +102,15 @@ namespace m5 return (readRegister8(_regForPin(M5IOE1_REG_GPIO_IN_L, pin)) & _bitForPin(pin)) != 0; } + bool M5IOE1_Class::getInputLevel(uint8_t pin, bool* level) + { + if (!_isValidPin(pin) || level == nullptr) { return false; } + std::uint8_t v; + if (!readRegister(_regForPin(M5IOE1_REG_GPIO_IN_L, pin), &v, 1)) { return false; } + *level = (v & _bitForPin(pin)) != 0; + return true; + } + void M5IOE1_Class::setPwmFrequency(std::uint16_t frequency) { std::uint8_t data[2] = { static_cast(frequency & 0xFF), static_cast(frequency >> 8) }; diff --git a/src/utility/M5IOE1_Class.hpp b/src/utility/M5IOE1_Class.hpp index 3154fde..c6c7fc0 100644 --- a/src/utility/M5IOE1_Class.hpp +++ b/src/utility/M5IOE1_Class.hpp @@ -57,6 +57,7 @@ namespace m5 void digitalWrite(uint8_t pin, bool level) override; bool digitalRead(uint8_t pin) override; + bool getInputLevel(uint8_t pin, bool* level) override; void setPwmFrequency(std::uint16_t frequency); diff --git a/src/utility/Power_Class.cpp b/src/utility/Power_Class.cpp index 98795a9..39d4be6 100644 --- a/src/utility/Power_Class.cpp +++ b/src/utility/Power_Class.cpp @@ -206,14 +206,43 @@ namespace m5 case board_t::board_M5CoreMatrix: _pmic = pmic_t::pmic_m5pm1; + _wakeupPin = GPIO_NUM_2; + /// KEY1/2/3 are wired to PM1 GPIO0/1/2 (pressed = LOW) + M5pm1.setGPIOFunction(M5PM1_Class::gpio0, M5PM1_Class::gpio); M5pm1.setGPIOFunction(M5PM1_Class::gpio1, M5PM1_Class::gpio); M5pm1.setGPIOFunction(M5PM1_Class::gpio2, M5PM1_Class::gpio); - M5pm1.setGPIOFunction(M5PM1_Class::gpio3, M5PM1_Class::gpio); - M5pm1.setGPIOMode(M5PM1_Class::gpio1, M5PM1_Class::output); + M5pm1.setGPIOMode(M5PM1_Class::gpio0, M5PM1_Class::input); + M5pm1.setGPIOMode(M5PM1_Class::gpio1, M5PM1_Class::input); M5pm1.setGPIOMode(M5PM1_Class::gpio2, M5PM1_Class::input); + /// PM1 GPIO4 is the BMI270 INT1 input (motion wakeup) + M5pm1.setGPIOFunction(M5PM1_Class::gpio4, M5PM1_Class::gpio); + M5pm1.setGPIOMode(M5PM1_Class::gpio4, M5PM1_Class::input); + /// PM1 GPIO3 is the IRQ output wired to ESP32 G2. Without an IRQ pin + /// configured the PM1 auto-clears its IRQ status (0x40-0x42) and the + /// power button / wake events cannot be detected. + /// Configure it as a push-pull high output before switching to the IRQ + /// function, so the released line is actively driven high. M5pm1.setGPIOMode(M5PM1_Class::gpio3, M5PM1_Class::output); - M5pm1.setGPIODrive(M5PM1_Class::gpio1, M5PM1_Class::push_pull); M5pm1.setGPIODrive(M5PM1_Class::gpio3, M5PM1_Class::push_pull); + M5pm1.setGPIOPull(M5PM1_Class::gpio3, M5PM1_Class::pull_up); + M5pm1.setGPIOOutput(M5PM1_Class::gpio3, true); + M5pm1.setGPIOFunction(M5PM1_Class::gpio3, M5PM1_Class::irq); +#if SOC_RTCIO_INPUT_OUTPUT_SUPPORTED + /// After an EXT1 wakeup the pin is still owned by the RTC IO mux and the + /// digital GPIO input reads low forever (the release wait on the next + /// sleep entry would never finish). Return it to the digital function. + rtc_gpio_deinit((gpio_num_t)_wakeupPin); +#endif + /// make the PM1 IRQ output readable as the wakeup pin + m5gfx::pinMode(_wakeupPin, m5gfx::pin_mode_t::input_pullup); + /// charge detect input (IOE1 G8 = AW32901 CHG_STAT, low = charging) + M5.getIOExpander(0).setDirection(M5IOE1_Class::gpio8, false); + { /// TF card power (IOE1 G1) is off at reset; enable it so the SD card is usable + auto& ioe1 = M5.getIOExpander(0); + ioe1.setHighImpedance(M5IOE1_Class::gpio1, false); + ioe1.setDirection(M5IOE1_Class::gpio1, true); + ioe1.digitalWrite(M5IOE1_Class::gpio1, true); + } break; } @@ -247,7 +276,7 @@ namespace m5 /// RTC アラームが IRQ 出力 (= ESP32 G4 の Low) として伝わる。 M5pm1.setGPIOFunction(M5PM1_Class::gpio3, M5PM1_Class::gpio); M5pm1.setGPIOMode(M5PM1_Class::gpio3, M5PM1_Class::input); - /// PM1 の IRQ 出力を wakeup ピンとして読めるよう入力にしておく。 + /// make the PM1 IRQ output readable as the wakeup pin。 /// この線には外部プルアップが無く、IRQ 解放時に High へ戻す駆動も /// 期待できないため、内部プルアップを有効にする。 m5gfx::pinMode(_wakeupPin, m5gfx::pin_mode_t::input_pullup); @@ -794,6 +823,16 @@ namespace m5 } break; +#elif defined (CONFIG_IDF_TARGET_ESP32C61) + case board_t::board_M5CoreMatrix: + { /// IOE1 G5 gates the Grove port power (both the 3.3V rail and the 5V boost) + auto& ioe1 = M5.getIOExpander(0); + ioe1.setHighImpedance(M5IOE1_Class::gpio5, false); + ioe1.setDirection(M5IOE1_Class::gpio5, true); + ioe1.digitalWrite(M5IOE1_Class::gpio5, enable); + } + break; + #elif defined (CONFIG_IDF_TARGET_ESP32H2) #elif defined (CONFIG_IDF_TARGET_ESP32S3) @@ -972,6 +1011,11 @@ namespace m5 return M5pm1.getExtOutput(); break; +#elif defined (CONFIG_IDF_TARGET_ESP32C61) + case board_t::board_M5CoreMatrix: + return M5.getIOExpander(0).getWriteValue(M5IOE1_Class::gpio5); + break; + #elif !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) case board_t::board_M5Paper: return m5gfx::gpio_in(M5Paper_EXT5V_ENABLE_PIN); @@ -1422,8 +1466,9 @@ namespace m5 #if SOC_RTCIO_INPUT_OUTPUT_SUPPORTED if (pin_wakeup_enabled) { -#if defined (CONFIG_IDF_TARGET_ESP32C5) - if (M5.getBoard() == board_t::board_M5ToughC5) +#if defined (CONFIG_IDF_TARGET_ESP32C5) || defined (CONFIG_IDF_TARGET_ESP32C61) + if (M5.getBoard() == board_t::board_M5ToughC5 + || M5.getBoard() == board_t::board_M5CoreMatrix) { // PM1 の IRQ 出力線には外部プルアップが無く、プルダウンすると // Low に固定されて wakeup ピンが解放されなくなる。内部プルアップで // High を維持し、IRQ アサート (Low) だけを wakeup 条件にする。 @@ -1732,6 +1777,40 @@ namespace m5 return -1; } +#if defined (CONFIG_IDF_TARGET_ESP32C61) + /// CoreMatrix: with no battery attached, the PM1 VBAT ADC reads the + /// AW32901 charger float voltage (~4.2V), which is indistinguishable from + /// a fully charged battery. Distinguish them by briefly pausing the + /// charger: without a battery VBAT collapses well below 2V, while a real + /// battery holds its voltage. The PM1 refreshes the VBAT register on an + /// internal ~1 second ADC cycle, so the pause must cover one full cycle. + /// Only the first call blocks (~1.2s); the result is cached. + /// An I2C failure during the probe is not cached, so a later call retries + /// and the battery APIs can report the bus error instead of a wrong state. + std::int8_t Power_Class::_batteryPresent(void) + { + if (_batt_present < 0) + { + bool chg_enabled = true; + std::uint16_t pre_mv = 0, post_mv = 0; + bool ok = M5pm1.getBatteryCharge(&chg_enabled) + && M5pm1.getBatteryVoltage(&pre_mv) + && M5pm1.setBatteryCharge(false); + if (ok) + { /// wait only when the charger pause actually took effect + m5gfx::delay(1200); + ok = M5pm1.getBatteryVoltage(&post_mv); + } + M5pm1.setBatteryCharge(chg_enabled); + if (ok) + { + _batt_present = (post_mv > 2000) && ((std::int32_t)pre_mv - (std::int32_t)post_mv < 500); + } + } + return _batt_present; + } +#endif + int16_t Power_Class::getBatteryVoltage(void) { #if !defined (M5UNIFIED_PC_BUILD) @@ -1743,6 +1822,10 @@ namespace m5 case pmic_t::pmic_aw32001: return Bq27220.getVoltage_mV(); #elif defined (CONFIG_IDF_TARGET_ESP32C61) + case pmic_t::pmic_m5pm1: + /// 0 = no battery attached or read failure (see _batteryPresent) + if (_batteryPresent() != 1) { return 0; } + return M5pm1.getBatteryVoltage(); #elif defined (CONFIG_IDF_TARGET_ESP32P4) #else #if !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) @@ -1806,6 +1889,16 @@ namespace m5 } break; #elif defined (CONFIG_IDF_TARGET_ESP32C61) + case pmic_t::pmic_m5pm1: + { + // Get battery voltage in mV + int16_t bat_mv = getBatteryVoltage(); + if (bat_mv <= 0) { + return -1; // Error reading voltage + } + mv = bat_mv; + } + break; #elif defined (CONFIG_IDF_TARGET_ESP32P4) #else #if !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) @@ -2126,6 +2219,25 @@ namespace m5 return Aw32001.isCharging() ? is_charging_t::is_charging : is_charging_t::is_discharging; #elif defined (CONFIG_IDF_TARGET_ESP32C61) + case pmic_t::pmic_m5pm1: + /// CoreMatrix: the AW32901 CHG_STAT is wired to IOE1 G8 (low = charging) + if (M5.getBoard() == board_t::board_M5CoreMatrix) + { + /// With no battery the charger retries periodically and CHG_STAT + /// blips low for a moment; report "not charging" instead. + { + std::int8_t present = _batteryPresent(); + if (present < 0) { return is_charging_t::charge_unknown; } + if (present == 0) { return is_charging_t::is_discharging; } + } + bool level; + if (!M5.getIOExpander(0).getInputLevel(M5IOE1_Class::gpio8, &level)) + { /// do not report an I2C failure as "charging" + return is_charging_t::charge_unknown; + } + return level ? is_charging_t::is_discharging : is_charging_t::is_charging; + } + return is_charging_t::charge_unknown; #elif defined (CONFIG_IDF_TARGET_ESP32P4) #else #if !defined (CONFIG_IDF_TARGET) || defined (CONFIG_IDF_TARGET_ESP32) diff --git a/src/utility/Power_Class.hpp b/src/utility/Power_Class.hpp index 8377e69..cff4fe1 100644 --- a/src/utility/Power_Class.hpp +++ b/src/utility/Power_Class.hpp @@ -251,6 +251,14 @@ namespace m5 void _powerOff(bool withTimer); void _timerSleep(void); +#if defined (CONFIG_IDF_TARGET_ESP32C61) + /// Check whether a battery is actually attached. + /// @return 1=present / 0=absent / -1=unknown (I2C failure, not cached) + std::int8_t _batteryPresent(void); + /// Cached result of the battery presence probe. -1 = not yet probed. + std::int8_t _batt_present = -1; +#endif + /// Release the wakeup pin so that it can be asserted again while sleeping. /// @return true if the pin is released ( high ). bool _releaseWakeupPin(std::uint_fast8_t wakeup_pin); diff --git a/src/utility/power/M5PM1_Class.cpp b/src/utility/power/M5PM1_Class.cpp index a6f643d..c0dd328 100644 --- a/src/utility/power/M5PM1_Class.cpp +++ b/src/utility/power/M5PM1_Class.cpp @@ -157,6 +157,12 @@ namespace m5 return readRegister8(M5PM1_REG_GPIO_IN) & (1 << gpio_num(pin)); } + bool M5PM1_Class::getGPIOInputBits(std::uint8_t* bits) + { + if (!_init || bits == nullptr) { return false; } + return readRegister(M5PM1_REG_GPIO_IN, bits, 1); + } + bool M5PM1_Class::getGPIOOutputLatch(gpio_t pin) { if (!_init || !is_valid_gpio(pin)) { return false; } @@ -212,6 +218,15 @@ namespace m5 : bitOff(M5PM1_REG_PWR_CFG, M5PM1_PWR_CFG_CHG_EN); } + bool M5PM1_Class::getBatteryCharge(bool* enabled) + { + if (!_init) { return false; } + std::uint8_t cfg = 0; + if (!readRegister(M5PM1_REG_PWR_CFG, &cfg, 1)) { return false; } + *enabled = cfg & M5PM1_PWR_CFG_CHG_EN; + return true; + } + bool M5PM1_Class::setChargeCurrent(std::uint16_t max_mA) { return false; @@ -274,9 +289,17 @@ namespace m5 std::uint16_t M5PM1_Class::getBatteryVoltage(void) { - if (!_init) { return 0; } + std::uint16_t mv = 0; + return getBatteryVoltage(&mv) ? mv : 0; + } + + bool M5PM1_Class::getBatteryVoltage(std::uint16_t* millivolt) + { + if (!_init) { return false; } std::uint8_t buf[2] = {}; - return readRegister(M5PM1_REG_VBAT_L, buf, sizeof(buf)) ? (buf[1] << 8) | buf[0] : 0; + if (!readRegister(M5PM1_REG_VBAT_L, buf, sizeof(buf))) { return false; } + *millivolt = (buf[1] << 8) | buf[0]; + return true; } std::uint16_t M5PM1_Class::get5VoutVoltage(void) diff --git a/src/utility/power/M5PM1_Class.hpp b/src/utility/power/M5PM1_Class.hpp index 64c1558..977e5ba 100644 --- a/src/utility/power/M5PM1_Class.hpp +++ b/src/utility/power/M5PM1_Class.hpp @@ -103,6 +103,9 @@ namespace m5 /// get PM1 GPIO input level. bool getGPIOInput(gpio_t pin); + /// read all GPIO input levels at once. returns false on I2C failure. + bool getGPIOInputBits(std::uint8_t* bits); + /// get PM1 GPIO output latch level, not the physical input level. bool getGPIOOutputLatch(gpio_t pin); @@ -134,6 +137,11 @@ namespace m5 /// @param enable true=enable / false=disable bool setBatteryCharge(bool enable); + /// get battery charge enable state with I2C error reporting. + /// @param enabled output parameter, receives the charge enable state. + /// @return false on I2C failure. + bool getBatteryCharge(bool* enabled); + /// set battery charge current /// @param max_mA milli ampere. (8 - 512). bool setChargeCurrent(std::uint16_t max_mA); @@ -165,6 +173,11 @@ namespace m5 /// @return milli volt. 0=read failed std::uint16_t getBatteryVoltage(void); + /// get battery voltage with I2C error reporting. + /// @param millivolt output parameter, receives the battery voltage [mV]. + /// @return false on I2C failure. + bool getBatteryVoltage(std::uint16_t* millivolt); + /// get 5V output voltage. /// @return milli volt. 0=read failed std::uint16_t get5VoutVoltage(void);