Skip to content
Open
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
21 changes: 20 additions & 1 deletion include/graphics/driver/DisplayDriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "graphics/DeviceGUI.h"
#include "graphics/LVGL/LVGLGraphics.h"
#include "graphics/driver/ScreenSleepController.h"
#include <cstdint>

#define H_NORM_PX(h_scr_percent) ((int16_t)((screenWidth / 100.0) * (h_scr_percent)))
Expand All @@ -25,7 +26,17 @@ class DisplayDriver
virtual ~DisplayDriver() {}

virtual uint8_t getBrightness() { return 255; }
virtual void setBrightness(uint8_t timeout) {}
// setBrightness is the user-facing path: updates the wakeBrightness mirror in ScreenSleepController.
// setHardwareBrightness is the animation-internal path: drives hardware only, leaves the mirror untouched.
virtual void setBrightness(uint8_t brightness) {}
virtual void setHardwareBrightness(uint8_t brightness) {}

virtual void panelSleep(void) {}
virtual void panelWake(void) {}
virtual void powerSaveOn(void) {}
virtual void powerSaveOff(void) {}
virtual bool hasBacklight(void) { return false; }
virtual int getTouchIntPin(void) { return -1; }

virtual uint16_t getScreenTimeout() { return 0; }
virtual void setScreenTimeout(uint16_t timeout) {}
Expand All @@ -35,11 +46,19 @@ class DisplayDriver

lv_display_t *getDisplay(void) { return display; }

ScreenSleepController *sleepController(void) { return _sleepController; }

// signal wake/sleep from any context (e.g. I2C keyboard driver without GPIO interrupt)
static void requestWake(void) { if (_sleepController) _sleepController->wake(); }
static void requestSleep(void) { if (_sleepController) _sleepController->sleep(); }
static bool isScreenSleeping(void) { return _sleepController && _sleepController->isSleeping(); }

protected:
LVGLGraphics lvgl;
LVGLDisplay *display;
LVGLTouch *touch;
DeviceGUI *view;
uint16_t screenWidth;
uint16_t screenHeight;
static ScreenSleepController *_sleepController;
};
125 changes: 33 additions & 92 deletions include/graphics/driver/LGFXDriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

#include "LovyanGFX.h"
#include "graphics/driver/DisplayDriverConfig.h"
#include "graphics/driver/ScreenSleepController.h"
#include "graphics/driver/TFTDriver.h"
#include "input/InputDriver.h"
#include "input/ScreenSleepObserver.h"
#include "lvgl_private.h"
#include "util/ILog.h"
#include <functional>
Expand All @@ -12,7 +14,7 @@ constexpr uint32_t defaultLongPressTime = 600; // ms until long press is detecte
constexpr uint32_t defaultGestureLimit = 10; // x/y diff pixel until a swipe gesture is detected (lvgl default is 50)

constexpr uint32_t defaultScreenTimeout = 30 * 1000;
constexpr uint32_t defaultBrightness = 153;
constexpr uint8_t defaultBrightness = 153;

template <class LGFX> class LGFXDriver : public TFTDriver<LGFX>
{
Expand All @@ -24,15 +26,23 @@ template <class LGFX> class LGFXDriver : public TFTDriver<LGFX>
bool hasTouch(void) override;
bool hasButton(void) override { return lgfx->hasButton(); }
bool hasLight(void) override { return lgfx->light(); }
bool isPowersaving(void) override { return powerSaving; }
bool isPowersaving(void) override { return _sleepController.isSleeping(); }
void printConfig(void) override;
void task_handler(void) override;

uint8_t getBrightness(void) override { return lgfx->getBrightness(); }
void setBrightness(uint8_t brightness) override;
uint8_t getBrightness(void) override { return lgfx->getBrightness(); }
void setHardwareBrightness(uint8_t brightness) override { lgfx->setBrightness(brightness); }
void setBrightness(uint8_t brightness) override;

uint16_t getScreenTimeout() override { return screenTimeout / 1000; }
void setScreenTimeout(uint16_t timeout) override { screenTimeout = timeout * 1000; };
void setScreenTimeout(uint16_t timeout) override { screenTimeout = (uint32_t)timeout * 1000; };

void panelSleep(void) override { lgfx->sleep(); }
void panelWake(void) override { lgfx->wakeup(); }
void powerSaveOn(void) override { lgfx->powerSaveOn(); }
void powerSaveOff(void) override { lgfx->powerSaveOff(); }
bool hasBacklight(void) override { return lgfx->light() != nullptr; }
int getTouchIntPin(void) override;

protected:
// lvgl callbacks have to be static cause it's a C library, not C++
Expand All @@ -41,13 +51,12 @@ template <class LGFX> class LGFXDriver : public TFTDriver<LGFX>
static void touchpad_read(lv_indev_t *indev_driver, lv_indev_data_t *data);

uint32_t screenTimeout;
uint32_t lastBrightness;
bool powerSaving;

private:
void init_lgfx(void);

static LGFX *lgfx;
ScreenSleepController _sleepController;
size_t bufsize;
lv_color_t *buf1;
lv_color_t *buf2;
Expand All @@ -59,15 +68,17 @@ template <class LGFX> LGFX *LGFXDriver<LGFX>::lgfx = nullptr;
template <class LGFX>
LGFXDriver<LGFX>::LGFXDriver(uint16_t width, uint16_t height)
: TFTDriver<LGFX>(lgfx ? lgfx : new LGFX, width, height), screenTimeout(defaultScreenTimeout),
lastBrightness(defaultBrightness), powerSaving(false), bufsize(0), buf1(nullptr), buf2(nullptr), calibrating(false)
_sleepController(this, defaultBrightness),
bufsize(0), buf1(nullptr), buf2(nullptr), calibrating(false)
{
lgfx = this->tft;
}

template <class LGFX>
LGFXDriver<LGFX>::LGFXDriver(const DisplayDriverConfig &cfg)
: TFTDriver<LGFX>(lgfx ? lgfx : new LGFX(cfg), cfg.width(), cfg.height()), screenTimeout(defaultScreenTimeout),
lastBrightness(defaultBrightness), powerSaving(false), bufsize(0), buf1(nullptr), buf2(nullptr), calibrating(false)
_sleepController(this, defaultBrightness),
bufsize(0), buf1(nullptr), buf2(nullptr), calibrating(false)
{
lgfx = this->tft;
}
Expand All @@ -81,91 +92,18 @@ template <class LGFX> bool LGFXDriver<LGFX>::hasTouch(void)
#endif
}

template <class LGFX> void LGFXDriver<LGFX>::task_handler(void)
template <class LGFX> int LGFXDriver<LGFX>::getTouchIntPin(void)
{
// handle display timeout
if ((screenTimeout > 0 && lv_display_get_inactive_time(NULL) > screenTimeout) || powerSaving ||
(DisplayDriver::view->isScreenLocked())) {
// sleep screen only if there are means for wakeup
if (DisplayDriver::view->getInputDriver()->hasPointerDevice() || hasTouch() ||
DisplayDriver::view->getInputDriver()->hasKeyboardDevice() || hasButton()) {
if (hasLight()) {
if (!powerSaving) {
// dim display brightness slowly down
uint32_t brightness = lgfx->getBrightness();
if (brightness > 0) {
lgfx->setBrightness(brightness - 1);
} else {
ILOG_INFO("enter powersave");
DisplayDriver::view->screenSaving(true);
if (hasTouch() && hasButton()) {
ILOG_DEBUG("disable touch, enable button input");
lv_indev_enable(DisplayDriver::touch, false);
lv_indev_enable(InputDriver::instance()->getButton(), true);
}
lgfx->sleep();
lgfx->powerSaveOn();
powerSaving = true;
}
}
if (powerSaving) {
int pin_int = -1;
if (hasTouch()) {
#ifndef CUSTOM_TOUCH_DRIVER
pin_int = lgfx->touch()->config().pin_int;
return lgfx->touch() ? lgfx->touch()->config().pin_int : -1;
#else
pin_int = lgfx->getTouchInt();
#endif
}
if (hasButton()) {
#ifdef BUTTON_PIN // only relevant for CYD scenario
pin_int = BUTTON_PIN;
return lgfx->getTouchInt();
#endif
}
if ((pin_int >= 0 && DisplayDriver::view->sleep(pin_int)) ||
(screenTimeout + 50 > lv_display_get_inactive_time(NULL) && !DisplayDriver::view->isScreenLocked())) {
delay(2); // let the CPU finish to restore all register in case of light sleep
// woke up by touch or button
ILOG_INFO("leaving powersave");
powerSaving = false;
DisplayDriver::view->triggerHeartbeat();
lgfx->powerSaveOff();
lgfx->wakeup();
lgfx->setBrightness(lastBrightness);
DisplayDriver::view->screenSaving(false);
if (hasTouch() && hasButton()) {
ILOG_DEBUG("enable touch, disable button input");
lv_indev_enable(DisplayDriver::touch, true);
lv_indev_enable(InputDriver::instance()->getButton(), false);
}
lv_display_trigger_activity(NULL);
} else {
// we woke up due to e.g. serial traffic (or sleep() simply not implemented)
// continue with processing loop and enter sleep() again next round
}
}
}
// no BL pin defined to control brightness, so show blank screen instead
else {
if (!powerSaving) {
DisplayDriver::view->blankScreen(true);
lgfx->sleep();
lgfx->powerSaveOn();
powerSaving = true;
}
if (screenTimeout > lv_display_get_inactive_time(NULL)) {
DisplayDriver::view->blankScreen(false);
lgfx->powerSaveOff();
lgfx->wakeup();
powerSaving = false;
lv_disp_trig_activity(NULL);
}
}
}
} else if (lgfx->getBrightness() < lastBrightness) {
lgfx->setBrightness(lastBrightness);
lastBrightness = lgfx->getBrightness();
}
}

template <class LGFX> void LGFXDriver<LGFX>::task_handler(void)
{
_sleepController.tick(screenTimeout, hasTouch(), hasButton());

if (!calibrating) {
DisplayDriver::task_handler();
Expand Down Expand Up @@ -337,6 +275,9 @@ template <class LGFX> void LGFXDriver<LGFX>::init(DeviceGUI *gui)
lv_timer_set_period(timer, 10); // 100Hz as I2C touch controllers support
#endif
}

_sleepController.setContext(DisplayDriver::view, DisplayDriver::touch);
DisplayDriver::_sleepController = &_sleepController;
}

template <class LGFX> void LGFXDriver<LGFX>::init_lgfx(void)
Expand Down Expand Up @@ -413,8 +354,8 @@ template <class LGFX> bool LGFXDriver<LGFX>::calibrate(uint16_t parameters[8])

template <class LGFX> void LGFXDriver<LGFX>::setBrightness(uint8_t brightness)
{
lgfx->setBrightness(brightness);
lastBrightness = brightness;
setHardwareBrightness(brightness);
_sleepController.setWakeBrightness(brightness);
}

template <class LGFX> void LGFXDriver<LGFX>::printConfig(void)
Expand Down
62 changes: 62 additions & 0 deletions include/graphics/driver/ScreenSleepController.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#pragma once

#include "lvgl.h"
#include <cstdint>

class DisplayDriver;
class DeviceGUI;

/**
* @brief Controls the display sleep/wake state machine and sinusoidal backlight
* fade animations. Owned by LGFXDriver; accessible externally via
* DisplayDriver::sleepController().
*
* Unlike a traditional screensaver (which keeps the screen on to prevent
* burn-in), this class powers the display off entirely and coordinates
* peripheral backlight synchronisation via ScreenSleepObserver.
*/
class ScreenSleepController
{
public:
explicit ScreenSleepController(DisplayDriver *panel, uint8_t wakeBrightness);
void setContext(DeviceGUI *view, lv_indev_t *touch);

void wake(void); // request wake — safe to call from any context
void sleep(void); // request sleep — safe to call from any context
bool isSleeping(void) const;

void tick(uint32_t screenTimeout, bool hasTouch, bool hasButton);

void setWakeBrightness(uint8_t brightness);
uint8_t getWakeBrightness(void) const;

private:
void dimStep(bool hasTouch, bool hasButton);
void wakeStep(void);
void enterPowerSave(bool hasTouch, bool hasButton);
void exitPowerSave(bool hasTouch, bool hasButton);
void checkWakeConditions(uint32_t screenTimeout, bool hasTouch, bool hasButton);
void startWakeFromCurrent(uint8_t targetBrightness);

// ms for the brightness fade triggered by a manual sleep request or screen-lock
static constexpr uint16_t defaultDimDuration = 1000;
// ms for the brightness fade triggered by the inactivity timeout
static constexpr uint16_t timeoutDimDuration = 10000;

DisplayDriver *panel;
DeviceGUI *view = nullptr;
lv_indev_t *touch = nullptr;

volatile bool wakeRequested = false;
volatile bool sleepRequested = false;
bool powerSaving = false;

// Target brightness to restore after waking. Mirrored here on every user-facing
// setBrightness() call; animation frames use setHardwareBrightness() so they never
// corrupt this value.
uint8_t wakeBrightness = 0;
uint32_t dimStartTime = 0;
uint8_t dimStartBrightness = 0;
uint16_t dimDuration = 0;
uint32_t wakeStartTime = 0;
};
23 changes: 23 additions & 0 deletions include/input/ScreenSleepObserver.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#pragma once

/**
* @brief Optional observer that synchronizes peripheral backlights (e.g. a keyboard
* LED) with the display fade animation. Register via setInstance(); all
* call-sites null-guard, so it is safe to leave unset on devices that don't need it.
*/
class ScreenSleepObserver
{
public:
static ScreenSleepObserver *instance(void) { return observer; }
static void setInstance(ScreenSleepObserver *obs) { observer = obs; }

virtual void onScreenSleep(void) {}
// progress: animation progress 0.0–1.0 (0 = animation start, 1 = animation complete);
// fadingIn: true = waking (0→full brightness), false = dimming (full brightness→0)
virtual void applyBrightnessProgress(float progress, bool fadingIn) {}

virtual ~ScreenSleepObserver() = default;

protected:
static ScreenSleepObserver *observer;
};
2 changes: 2 additions & 0 deletions source/graphics/driver/DisplayDriver.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include "graphics/driver/DisplayDriver.h"
#include "util/ILog.h"

ScreenSleepController *DisplayDriver::_sleepController = nullptr;

#if LV_USE_PROFILER
#if defined(ARCH_PORTDUINO)
#include <sys/syscall.h>
Expand Down
Loading
Loading