fix: stabilize deep sleep wake on USB power (#2060)

When the device went into deep sleep while plugged into USB, a
power-button press would occasionally not wake it. The display held its
last frame, the chip stayed in deep sleep, and recovery required an
unplug + reset + hold-power cycle. On battery the symptom never surfaced
because the power button physically re-energises the chip.

Two peripherals were holding power domains alive across the deep sleep
boundary and interfering with the configured GPIO wake on the power
button:

1. HWCDC. Once Serial is initialized, the USB Serial/JTAG peripheral
keeps its power domain configured even with TX timeout at zero and no
host draining. Tear it down with Serial.end() in
HalPowerManager::startDeepSleep, gated by ENABLE_SERIAL_LOG to match the
Serial.begin site. This hit me if I was charging off my computer.

2. WiFi. enterDeepSleep had no WiFi teardown, so sleeping from any
network-using activity left the modem domain alive. Call
WiFi.disconnect(true) + WiFi.mode(WIFI_OFF) when WiFi is active. Wake
from deep sleep is effectively a chip reset, so no WiFi state needs to
survive. While this doesn't cause higher power drain, it apparently was
causing issues where I'd occasionally have the chip hang on sleep
transition from a wifi activity.

Confirmed on device.


Did you use AI tools to help write this code? partial
This commit is contained in:
Jeremy Klein
2026-05-19 12:04:38 -04:00
committed by GitHub
parent acc1ed4358
commit 86a9510957
2 changed files with 17 additions and 0 deletions
+9
View File
@@ -66,6 +66,15 @@ void HalPowerManager::startDeepSleep(HalGPIO& gpio) const {
delay(50);
gpio.update();
}
#ifdef ENABLE_SERIAL_LOG
// Tear down HWCDC so the host sees a clean disconnect and the peripheral
// doesn't hold power domains that interfere with USB-powered GPIO wake.
// logSerial is the raw HWCDC reference; Serial is the MySerialImpl proxy
// (which doesn't expose end()).
logSerial.end();
#endif
// Pre-sleep routines from the original firmware
// GPIO13 is connected to battery latch MOSFET, we need to make sure it's low during sleep
// Note that this means the MCU will be completely powered off during sleep, including RTC
+8
View File
@@ -13,6 +13,7 @@
#include <I18n.h>
#include <Logging.h>
#include <SPI.h>
#include <WiFi.h>
#include <builtinFonts/all.h>
#include <cstring>
@@ -214,6 +215,13 @@ void enterDeepSleep() {
activityManager.goToSleep();
// Tear down WiFi so the modem power domain isn't held alive across deep sleep.
// Wake from deep sleep is effectively a chip reset, so no state needs to survive.
if (WiFi.getMode() != WIFI_MODE_NULL) {
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
}
halTiltSensor.deepSleep();
display.deepSleep();
LOG_DBG("MAIN", "Entering deep sleep");