mirror of
https://github.com/meshtastic/firmware.git
synced 2026-10-10 23:56:25 -04:00
* fix(heltec-t1): sleep the panel on screen-off to stop image retention The T1 is excluded from the LovyanGFX sleep()/wakeup() calls because it uses TFT_eSPI, so DISPLAYOFF only dropped the backlight and the ST7735 kept driving the last frame unlit for the whole screen-off timeout. That constant static image is what burns ghost pixels into the panel. Add opt-in TFT_SLEEP_WHEN_OFF for the TFT_eSPI path: DISPOFF + SLPIN on screen-off, SLPOUT (120 ms) + DISPON on wake, issued as raw MIPI DCS since TFT_eSPI exposes no sleep API. Frame memory survives sleep-in, so the previous frame reappears and the dirty-window diff continues unchanged. The sleep flag keeps the double displayOn() in Screen::handleSetOn() from paying the delay twice. * fix(heltec-t096): sleep the panel on screen-off to stop image retention The T096 sits behind the same TFT_eSPI exclusion as the T1, so DISPLAYOFF only dropped its backlight while the ST7735S kept driving the last frame unlit for the whole screen-off timeout. Same panel, same bus, same burn-in. Opt in to TFT_SLEEP_WHEN_OFF; the TFTDisplay side of the fix is already generic. * fix(tft): harden the TFT_SLEEP_WHEN_OFF wake/sleep sequence Drive VTFT_CTRL LOW before SLPOUT, so the rail is up before the panel is addressed. Wait out the remainder of the 120 ms the controller needs after SLPIN before sending SLPOUT, so a wake landing as the screen timeout fires is not dropped. Guard DISPLAYOFF on panelAsleep to match DISPLAYON. Correct the T1 VTFT_CTRL comment: LOW enables the rail, not HIGH. * fix(tft): wait out only what is left of the sleep-in window before SLPOUT Throttle::isWithinTimespanMs() is true for the whole 120 ms after SLPIN, so the wake path paid a fresh 120 ms on top of however much had already elapsed. A wake 119 ms after the SLPIN waited ~120 ms rather than ~1 ms, up to 119 ms of avoidable latency on every quick off/on. Add Throttle::remainingMs(), which returns what is left of the interval and saturates at 0 instead of underflowing to a ~49 day wait. It reads the clock once, so a caller that tests and then waits cannot be preempted between the two and land on that underflow - which a separate isWithinTimespanMs() plus subtraction at the call site could. heltec-mesh-node-t1 and -t096 both build; neither is board_level = pr, so CI does not compile this path. Docker native suite green, 1515/1515. * trunk --------- Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz>
56 lines
2.3 KiB
C++
56 lines
2.3 KiB
C++
#include "Throttle.h"
|
|
#include "UptimeClock.h"
|
|
#include <Arduino.h>
|
|
|
|
/// @brief Execute a function throttled to a minimum interval
|
|
/// @param lastExecutionMs Pointer to the last execution time in milliseconds
|
|
/// @param minumumIntervalMs Minimum execution interval in milliseconds
|
|
/// @param throttleFunc Function to execute if the execution is not deferred
|
|
/// @param onDefer Default to NULL, execute the function if the execution is deferred
|
|
/// @return true if the function was executed, false if it was deferred
|
|
bool Throttle::execute(uint32_t *lastExecutionMs, uint32_t minumumIntervalMs, void (*throttleFunc)(void), void (*onDefer)(void))
|
|
{
|
|
// TODO(elapsed-stamp): 0 doubles as "never run" here, so neither store is safe on the wrap tick:
|
|
// skipZero()'s 1 underflows a same-instant `now - *lastExecutionMs`, and 0 re-takes this branch.
|
|
if (*lastExecutionMs == 0) {
|
|
*lastExecutionMs = Time::getMillis();
|
|
throttleFunc();
|
|
return true;
|
|
}
|
|
uint32_t now = Time::getMillis();
|
|
|
|
if ((now - *lastExecutionMs) >= minumumIntervalMs) {
|
|
throttleFunc();
|
|
*lastExecutionMs = now;
|
|
return true;
|
|
} else if (onDefer != NULL) {
|
|
onDefer();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// @brief Check if the last execution time is within the interval
|
|
/// @param lastExecutionMs The last execution time in milliseconds
|
|
/// @param timeSpanMs The interval in milliseconds of the timespan
|
|
bool Throttle::isWithinTimespanMs(uint32_t lastExecutionMs, uint32_t timeSpanMs)
|
|
{
|
|
uint32_t now = Time::getMillis();
|
|
return (now - lastExecutionMs) < timeSpanMs;
|
|
}
|
|
|
|
/// @brief How much of an interval is left since a stored event, 0 once the interval has passed
|
|
/// @param lastExecutionMs The last execution time in milliseconds
|
|
/// @param intervalMs The interval in milliseconds
|
|
uint32_t Throttle::remainingMs(uint32_t lastExecutionMs, uint32_t intervalMs)
|
|
{
|
|
uint32_t elapsed = Time::getMillis() - lastExecutionMs;
|
|
return elapsed < intervalMs ? intervalMs - elapsed : 0;
|
|
}
|
|
|
|
/// @brief Check whether an absolute deadline has arrived, correctly across the millis() wrap
|
|
/// @param deadlineMs The deadline, as a millis() value
|
|
/// See the header for the range limit and the sentinel requirement.
|
|
bool Throttle::deadlinePassed(uint32_t deadlineMs)
|
|
{
|
|
return deadlinePassedAt(Time::getMillis(), deadlineMs);
|
|
} |