Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 69 additions & 1 deletion src/M5Unified.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down Expand Up @@ -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 },
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions src/utility/IMU_Class.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

}
Expand Down
9 changes: 9 additions & 0 deletions src/utility/IOExpander_Base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
9 changes: 9 additions & 0 deletions src/utility/M5IOE1_Class.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::uint8_t>(frequency & 0xFF), static_cast<std::uint8_t>(frequency >> 8) };
Expand Down
1 change: 1 addition & 0 deletions src/utility/M5IOE1_Class.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
124 changes: 118 additions & 6 deletions src/utility/Power_Class.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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 条件にする。
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
8 changes: 8 additions & 0 deletions src/utility/Power_Class.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading
Loading