diff --git a/src/utility/Power_Class.hpp b/src/utility/Power_Class.hpp index ffd5192a..1594bf44 100644 --- a/src/utility/Power_Class.hpp +++ b/src/utility/Power_Class.hpp @@ -242,6 +242,16 @@ namespace m5 /// 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 /// 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 + /// reset (the board's VBUS-to-bus switch closes before the boost stops and the + /// transient pulls VSYS down). The PMIC stays on and the board reboots with the + /// output enabled again, so a sketch that unconditionally disables it at startup + /// will reboot in a loop on such a unit. With a battery, or with 5 V supplied on + /// the bus, the transition itself does not disturb the board. Disabling an enabled + /// output blocks for about 20 ms while the PMIC's DCDC under-voltage power-off is + /// suspended; it is false, without touching the output, when that protection cannot + /// be suspended, and false after the output was disabled when it could not be re-armed. bool setExtOutput(bool enable, ext_port_mask_t port_mask = (ext_port_mask_t)0xFF); /// deprecated : Change to "setExtOutput" diff --git a/src/utility/Power_Class.inl b/src/utility/Power_Class.inl index 7949beb7..422eb28b 100644 --- a/src/utility/Power_Class.inl +++ b/src/utility/Power_Class.inl @@ -902,6 +902,10 @@ namespace m5 // , 0x18, 0x0E }; Axp2101.writeRegister8Array(reg_data_array, sizeof(reg_data_array)); + // Re-arm the DCDC1/DCDC3 under-voltage power-off that setExtOutput(false) + // suspends; an ESP32 brownout reset during that call skips the restore. + // Forced like the table above: these bits are the chip default. + Axp2101.bitOn(0x23, 0x05); // for Core2 v1.1 (AXP2101+INA3221) if (Ina3221[0].begin()) @@ -1299,7 +1303,42 @@ namespace m5 } } if (!cancel) { - result = Axp2101.setBLDO2(enable * 3300); + if (enable) { + result = Axp2101.setBLDO2(3300); + break; + } + // Core2 v1.1: BLDO2 drives both the boost enable and the /EN of the + // switch that ties USB VBUS to the 5V bus. While BLDO2 falls the switch + // closes before the boost stops, and the boost output feeds back into + // VBUS -> PMIC -> boost. With no battery this sags VSYS and the AXP2101 + // powers off on DCDC under-voltage (latched until the power key). + // Suspending that power-off for the transition turns it into an ESP32 + // brownout reset instead; begin() re-arms it after such a reset. + // (A Tough with the AXP2101 shares this path; the suspend is harmless there.) + uint8_t r90, r23 = 0; + if (!Axp2101.readRegister(0x90, &r90, 1)) { r90 = 0xFF; } // unreadable: assume a transition + bool transition = (r90 & (1 << 5)) != 0; + bool suspended = false; + if (transition) { + // Fail closed: when the power-off cannot be suspended the output is left as is. + if (!Axp2101.readRegister(0x23, &r23, 1)) { + ESP_LOGW("Power", "setExtOutput(false): protection register unreadable, refused."); + break; + } + suspended = (r23 & 0x05) != 0; + if (suspended && !Axp2101.writeRegister8(0x23, r23 & ~0x05)) { + ESP_LOGW("Power", "setExtOutput(false): could not suspend the DCDC UVP power-off, refused."); + break; + } + } + result = Axp2101.setBLDO2(0); + if (suspended) { + m5gfx::delay(20); // the transition completes within 10 ms (measured) + if (!Axp2101.writeRegister8(0x23, r23)) { + ESP_LOGW("Power", "setExtOutput(false): DCDC UVP power-off not re-armed."); + result = false; + } + } break; } } else {