tdpro & t5s3 pro - lock screen power saving improvements; fix stupid stupid merged firmware - bug

This commit is contained in:
pelgraine
2026-03-20 20:22:07 +11:00
parent f81de07830
commit f78824cdc4
6 changed files with 126 additions and 9 deletions

View File

@@ -1662,6 +1662,27 @@ void loop() {
// CPU frequency auto-timeout back to idle
cpuPower.loop();
// Low-power mode — drop CPU to 40 MHz and throttle loop when lock screen
// is active. The mesh radio has its own FIFO so packets are buffered;
// 50 ms yield means the loop still runs 20×/sec which is more than enough
// to drain the radio FIFO before overflow.
#if defined(LilyGo_T5S3_EPaper_Pro) || defined(LilyGo_TDeck_Pro)
{
static bool wasLocked = false;
bool nowLocked = ui_task.isLocked();
if (nowLocked && !wasLocked) {
cpuPower.setLowPower();
Serial.printf("[Power] Low-power mode: CPU %d MHz, loop throttled\n",
cpuPower.getFrequencyMHz());
} else if (!nowLocked && wasLocked) {
cpuPower.clearLowPower();
Serial.printf("[Power] Normal mode: CPU %d MHz\n",
cpuPower.getFrequencyMHz());
}
wasLocked = nowLocked;
}
#endif
// Audiobook: service audio decode regardless of which screen is active
#if defined(LilyGo_TDeck_Pro) && !defined(HAS_4G_MODEM)
{
@@ -2182,6 +2203,16 @@ void loop() {
}
}
#endif
// Low-power loop throttle — yield CPU when lock screen is active.
// The RTOS idle task executes WFI (wait-for-interrupt) during delay(),
// dramatically reducing CPU power draw. 50 ms gives 20 loop cycles/sec
// which is ample for LoRa packet reception (radio has hardware FIFO).
#if defined(LilyGo_T5S3_EPaper_Pro) || defined(LilyGo_TDeck_Pro)
if (ui_task.isLocked()) {
delay(50);
}
#endif
}
// ============================================================================

View File

@@ -1844,7 +1844,7 @@ void UITask::lockScreen() {
_next_refresh = 0; // Draw lock screen immediately
_auto_off = millis() + 60000; // 60s before display off while locked
_lastLockRefresh = millis(); // Start 15-min clock refresh cycle
Serial.println("[UI] Screen locked");
Serial.println("[UI] Screen locked — entering low-power mode");
}
void UITask::unlockScreen() {
@@ -1863,7 +1863,7 @@ void UITask::unlockScreen() {
_auto_off = millis() + AUTO_OFF_MILLIS;
_lastInputMillis = millis(); // Reset auto-lock idle timer
_next_refresh = 0;
Serial.println("[UI] Screen unlocked");
Serial.println("[UI] Screen unlocked — exiting low-power mode");
}
#endif // LilyGo_T5S3_EPaper_Pro || LilyGo_TDeck_Pro

View File

@@ -21,7 +21,7 @@ def merge_bin(source, target, env):
bootloader = os.path.join(build_dir, "bootloader.bin")
partitions = os.path.join(build_dir, "partitions.bin")
firmware = os.path.join(build_dir, "firmware.bin")
output = os.path.join(build_dir, "firmware_merged.bin")
output = os.path.join(build_dir, "firmware-merged.bin")
# Verify all inputs exist
for f in [bootloader, partitions, firmware]:

BIN
readback.bin Normal file

Binary file not shown.

View File

@@ -8,9 +8,10 @@
// 240 MHz ~70-80 mA
// 160 MHz ~50-60 mA
// 80 MHz ~30-40 mA
// 40 MHz ~15-20 mA (low-power / lock screen mode)
//
// SPI peripherals and UART use their own clock dividers from the APB clock,
// so LoRa, e-ink, and GPS serial all work fine at 80MHz.
// so LoRa, e-ink, and GPS serial all work fine at 80MHz and 40MHz.
#ifdef ESP32
@@ -22,23 +23,36 @@
#define CPU_FREQ_BOOST 240 // MHz — heavy processing
#endif
#ifndef CPU_FREQ_LOW_POWER
#define CPU_FREQ_LOW_POWER 40 // MHz — lock screen / idle standby
#endif
#ifndef CPU_BOOST_TIMEOUT_MS
#define CPU_BOOST_TIMEOUT_MS 10000 // 10 seconds
#endif
class CPUPowerManager {
public:
CPUPowerManager() : _boosted(false), _boost_started(0) {}
CPUPowerManager() : _boosted(false), _lowPower(false), _boost_started(0) {}
void begin() {
setCpuFrequencyMhz(CPU_FREQ_IDLE);
_boosted = false;
_lowPower = false;
MESH_DEBUG_PRINTLN("CPU power: idle at %d MHz", CPU_FREQ_IDLE);
}
void loop() {
if (_boosted && (millis() - _boost_started >= CPU_BOOST_TIMEOUT_MS)) {
setIdle();
// Return to low-power if locked, otherwise normal idle
if (_lowPower) {
setCpuFrequencyMhz(CPU_FREQ_LOW_POWER);
MESH_DEBUG_PRINTLN("CPU power: boost expired, returning to low-power %d MHz", CPU_FREQ_LOW_POWER);
} else {
setCpuFrequencyMhz(CPU_FREQ_IDLE);
MESH_DEBUG_PRINTLN("CPU power: idle at %d MHz", CPU_FREQ_IDLE);
}
_boosted = false;
}
}
@@ -57,13 +71,42 @@ public:
_boosted = false;
MESH_DEBUG_PRINTLN("CPU power: idle at %d MHz", CPU_FREQ_IDLE);
}
if (_lowPower) {
_lowPower = false;
}
}
// Low-power mode — drops CPU to 40 MHz for lock screen standby.
// If currently boosted, the boost timeout will return to 40 MHz
// instead of 80 MHz.
void setLowPower() {
_lowPower = true;
if (!_boosted) {
setCpuFrequencyMhz(CPU_FREQ_LOW_POWER);
MESH_DEBUG_PRINTLN("CPU power: low-power at %d MHz", CPU_FREQ_LOW_POWER);
}
// If boosted, the loop() timeout will drop to low-power instead of idle
}
// Exit low-power mode — returns to normal idle (80 MHz).
// If currently boosted, the boost timeout will return to idle
// instead of low-power.
void clearLowPower() {
_lowPower = false;
if (!_boosted) {
setCpuFrequencyMhz(CPU_FREQ_IDLE);
MESH_DEBUG_PRINTLN("CPU power: idle at %d MHz (low-power cleared)", CPU_FREQ_IDLE);
}
// If boosted, the loop() timeout will drop to idle as normal
}
bool isBoosted() const { return _boosted; }
bool isLowPower() const { return _lowPower; }
uint32_t getFrequencyMHz() const { return getCpuFrequencyMhz(); }
private:
bool _boosted;
bool _lowPower;
unsigned long _boost_started;
};

View File

@@ -8,9 +8,10 @@
// 240 MHz ~70-80 mA
// 160 MHz ~50-60 mA
// 80 MHz ~30-40 mA
// 40 MHz ~15-20 mA (low-power / lock screen mode)
//
// SPI peripherals and UART use their own clock dividers from the APB clock,
// so LoRa, e-ink, and GPS serial all work fine at 80MHz.
// so LoRa, e-ink, and GPS serial all work fine at 80MHz and 40MHz.
#ifdef ESP32
@@ -22,23 +23,36 @@
#define CPU_FREQ_BOOST 240 // MHz — heavy processing
#endif
#ifndef CPU_FREQ_LOW_POWER
#define CPU_FREQ_LOW_POWER 40 // MHz — lock screen / idle standby
#endif
#ifndef CPU_BOOST_TIMEOUT_MS
#define CPU_BOOST_TIMEOUT_MS 10000 // 10 seconds
#endif
class CPUPowerManager {
public:
CPUPowerManager() : _boosted(false), _boost_started(0) {}
CPUPowerManager() : _boosted(false), _lowPower(false), _boost_started(0) {}
void begin() {
setCpuFrequencyMhz(CPU_FREQ_IDLE);
_boosted = false;
_lowPower = false;
MESH_DEBUG_PRINTLN("CPU power: idle at %d MHz", CPU_FREQ_IDLE);
}
void loop() {
if (_boosted && (millis() - _boost_started >= CPU_BOOST_TIMEOUT_MS)) {
setIdle();
// Return to low-power if locked, otherwise normal idle
if (_lowPower) {
setCpuFrequencyMhz(CPU_FREQ_LOW_POWER);
MESH_DEBUG_PRINTLN("CPU power: boost expired, returning to low-power %d MHz", CPU_FREQ_LOW_POWER);
} else {
setCpuFrequencyMhz(CPU_FREQ_IDLE);
MESH_DEBUG_PRINTLN("CPU power: idle at %d MHz", CPU_FREQ_IDLE);
}
_boosted = false;
}
}
@@ -57,13 +71,42 @@ public:
_boosted = false;
MESH_DEBUG_PRINTLN("CPU power: idle at %d MHz", CPU_FREQ_IDLE);
}
if (_lowPower) {
_lowPower = false;
}
}
// Low-power mode — drops CPU to 40 MHz for lock screen standby.
// If currently boosted, the boost timeout will return to 40 MHz
// instead of 80 MHz.
void setLowPower() {
_lowPower = true;
if (!_boosted) {
setCpuFrequencyMhz(CPU_FREQ_LOW_POWER);
MESH_DEBUG_PRINTLN("CPU power: low-power at %d MHz", CPU_FREQ_LOW_POWER);
}
// If boosted, the loop() timeout will drop to low-power instead of idle
}
// Exit low-power mode — returns to normal idle (80 MHz).
// If currently boosted, the boost timeout will return to idle
// instead of low-power.
void clearLowPower() {
_lowPower = false;
if (!_boosted) {
setCpuFrequencyMhz(CPU_FREQ_IDLE);
MESH_DEBUG_PRINTLN("CPU power: idle at %d MHz (low-power cleared)", CPU_FREQ_IDLE);
}
// If boosted, the loop() timeout will drop to idle as normal
}
bool isBoosted() const { return _boosted; }
bool isLowPower() const { return _lowPower; }
uint32_t getFrequencyMHz() const { return getCpuFrequencyMhz(); }
private:
bool _boosted;
bool _lowPower;
unsigned long _boost_started;
};