Skip to content

Commit 7b00048

Browse files
authored
fix(graphics): drive GPIO backlights from the stored brightness level (#11588)
* fix(graphics): drive GPIO backlights from the stored brightness level Screen::handleSetOn restored PIN_EINK_EN only when screen_brightness was exactly 1. The field is 0..255 and defaults to 153, so the frontlight stayed off after a screen timeout until the next reboot. InputBroker read screen_brightness as "currently lit" for the touch backlight, so a stored level made touch-to-light a no-op. The HAPTIC_FEEDBACK_PIN block then reassigned touchConfig.onPress and onRelease, dropping those handlers on any variant defining both. MINI_EPAPER_S3 names its panel power rail PIN_EINK_EN. It was switched off with the screen and never restored. graphics::Backlight gains a GPIO backend covering PIN_EINK_EN and PCA_PIN_EINK_EN, so Screen, MenuHandler and InputBroker call backlightOn, backlightOff, backlightToggle and backlightIsLit instead of touching pins. backlightIsLit reports the driven state, separate from the configured level. Power-up state is declared per variant with GPIO_BACKLIGHT_DEFAULT_ON rather than hardcoded in the e-ink driver. The backend stores only 0 or 255, so any other stored level falls back to the variant default and no board changes its existing behaviour. MINI_EPAPER_S3 is excluded and keeps its rail powered. Touch handlers are merged so backlight and haptic feedback compose. Verified on ThinkNode M1: lit at boot, off on timeout, lit on wake, and an explicit off surviving both wake and reboot. * chore(thinknode_m1): correct the LED pin comments P0.13 drives the blue indicator, not a green one. P1.06 is a second drive for the same red LED as LED_POWER, which is why it stays disabled. * fix(graphics): clamp GPIO backlight levels at the setter backlightSet stored whatever level it was given, so a caller passing an intermediate value left backlightGet and the persisted config holding a level the rail cannot drive. Clamp to off or on in the setter, which keeps the invariant at the single write point instead of only at init.
1 parent c45b663 commit 7b00048

8 files changed

Lines changed: 145 additions & 80 deletions

File tree

src/graphics/Backlight.cpp

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,75 @@
11
#include "graphics/Backlight.h"
22

3-
#if HAS_PWM_BACKLIGHT
3+
#if HAS_BACKLIGHT
44

55
#include "mesh/NodeDB.h"
66

7+
#if HAS_GPIO_BACKLIGHT && defined(PCA_PIN_EINK_EN)
8+
#include "main.h" // the GPIO expander the rail hangs off
9+
#endif
10+
711
namespace graphics
812
{
913
namespace
1014
{
11-
bool pinConfigured = false;
15+
bool initialized = false;
16+
17+
// What the hardware is driven at right now, which is not the stored level while the screen is off.
18+
uint8_t litLevel = 0;
1219

1320
// Level restored when the backlight is switched back on after being toggled off.
21+
#if HAS_PWM_BACKLIGHT
1422
uint8_t lastOnLevel = PWM_BACKLIGHT_DEFAULT;
23+
#else
24+
uint8_t lastOnLevel = GPIO_BACKLIGHT_ON_LEVEL;
25+
#endif
1526

1627
void drive(uint8_t level)
1728
{
18-
if (!pinConfigured) {
19-
pinMode(PIN_PWM_BACKLIGHT, OUTPUT);
20-
pinConfigured = true;
21-
}
29+
litLevel = level;
30+
#if HAS_PWM_BACKLIGHT
2231
analogWrite(PIN_PWM_BACKLIGHT, level);
32+
#elif defined(PIN_EINK_EN)
33+
digitalWrite(PIN_EINK_EN, level > 0 ? HIGH : LOW);
34+
#elif defined(PCA_PIN_EINK_EN)
35+
io.digitalWrite(PCA_PIN_EINK_EN, level > 0 ? HIGH : LOW);
36+
#endif
2337
}
2438
} // namespace
2539

40+
void backlightInit()
41+
{
42+
if (initialized)
43+
return;
44+
initialized = true;
45+
46+
#if HAS_PWM_BACKLIGHT
47+
pinMode(PIN_PWM_BACKLIGHT, OUTPUT);
48+
#elif defined(PIN_EINK_EN)
49+
pinMode(PIN_EINK_EN, OUTPUT);
50+
#endif
51+
// PCA_PIN_EINK_EN is already configured by the variant's earlyInitVariant()
52+
53+
#if HAS_GPIO_BACKLIGHT
54+
// Any level that is not ours, such as the legacy 153 default, was not set here, so fall back to
55+
// the variant default rather than reading it as "lit".
56+
if (uiconfig.screen_brightness != 0 && uiconfig.screen_brightness != GPIO_BACKLIGHT_ON_LEVEL)
57+
uiconfig.screen_brightness = GPIO_BACKLIGHT_DEFAULT_LEVEL;
58+
#endif
59+
60+
// So a toggle or a momentary press restores the level the user last chose, not the compiled default
61+
if (uiconfig.screen_brightness > 0)
62+
lastOnLevel = uiconfig.screen_brightness;
63+
64+
drive(uiconfig.screen_brightness);
65+
}
66+
2667
void backlightSet(uint8_t level)
2768
{
69+
#if HAS_GPIO_BACKLIGHT
70+
// The rail has no intermediate states, so keep the stored level to the two this backend drives
71+
level = level > 0 ? GPIO_BACKLIGHT_ON_LEVEL : 0;
72+
#endif
2873
if (level > 0)
2974
lastOnLevel = level;
3075
uiconfig.screen_brightness = level;
@@ -36,11 +81,21 @@ uint8_t backlightGet()
3681
return uiconfig.screen_brightness;
3782
}
3883

84+
bool backlightIsLit()
85+
{
86+
return litLevel > 0;
87+
}
88+
3989
void backlightOn()
4090
{
4191
drive(uiconfig.screen_brightness);
4292
}
4393

94+
void backlightMomentaryOn()
95+
{
96+
drive(lastOnLevel);
97+
}
98+
4499
void backlightOff()
45100
{
46101
drive(0);
@@ -51,6 +106,7 @@ void backlightToggle()
51106
backlightSet(uiconfig.screen_brightness > 0 ? 0 : lastOnLevel);
52107
}
53108

109+
#if HAS_PWM_BACKLIGHT
54110
void backlightStepUp()
55111
{
56112
uint16_t raised = (uint16_t)uiconfig.screen_brightness + PWM_BACKLIGHT_STEP;
@@ -66,6 +122,7 @@ void backlightStepDown()
66122
? PWM_BACKLIGHT_MIN
67123
: uiconfig.screen_brightness - PWM_BACKLIGHT_STEP);
68124
}
125+
#endif
69126
} // namespace graphics
70127

71-
#endif // HAS_PWM_BACKLIGHT
128+
#endif // HAS_BACKLIGHT

src/graphics/Backlight.h

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,25 @@
22

33
#include "configuration.h"
44

5-
// PWM backlight control. A variant opts in by defining PIN_PWM_BACKLIGHT, optionally with
6-
// PWM_BACKLIGHT_DEFAULT, _MIN, _MAX and _STEP. Levels are 0..255 in uiconfig.screen_brightness.
5+
// Backlight control for a PWM rail (PIN_PWM_BACKLIGHT) or an on/off GPIO rail (PIN_EINK_EN,
6+
// PCA_PIN_EINK_EN). uiconfig.screen_brightness is the configured level, not the live pin state.
77

88
#if defined(PIN_PWM_BACKLIGHT)
99
#define HAS_PWM_BACKLIGHT 1
1010
#else
1111
#define HAS_PWM_BACKLIGHT 0
1212
#endif
1313

14+
// MINI_EPAPER_S3 names its panel power rail PIN_EINK_EN. That is not a backlight and has to stay
15+
// powered for the panel to work, so EInkDisplay::connect() drives it instead.
16+
#if !HAS_PWM_BACKLIGHT && (defined(PIN_EINK_EN) || defined(PCA_PIN_EINK_EN)) && !defined(MINI_EPAPER_S3)
17+
#define HAS_GPIO_BACKLIGHT 1
18+
#else
19+
#define HAS_GPIO_BACKLIGHT 0
20+
#endif
21+
22+
#define HAS_BACKLIGHT (HAS_PWM_BACKLIGHT || HAS_GPIO_BACKLIGHT)
23+
1424
#if HAS_PWM_BACKLIGHT
1525

1626
#ifndef PWM_BACKLIGHT_DEFAULT
@@ -26,21 +36,46 @@
2636
#define PWM_BACKLIGHT_STEP 20
2737
#endif
2838

39+
#endif // HAS_PWM_BACKLIGHT
40+
41+
#if HAS_GPIO_BACKLIGHT
42+
43+
// On or off only, so these are the sole levels this backend stores. A variant defines
44+
// GPIO_BACKLIGHT_DEFAULT_ON to power up lit.
45+
#define GPIO_BACKLIGHT_ON_LEVEL 255
46+
#if defined(GPIO_BACKLIGHT_DEFAULT_ON)
47+
#define GPIO_BACKLIGHT_DEFAULT_LEVEL GPIO_BACKLIGHT_ON_LEVEL
48+
#else
49+
#define GPIO_BACKLIGHT_DEFAULT_LEVEL 0
50+
#endif
51+
52+
#endif // HAS_GPIO_BACKLIGHT
53+
54+
#if HAS_BACKLIGHT
55+
2956
namespace graphics
3057
{
58+
void backlightInit(); // configure the pin, settle the stored level, then drive it. Idempotent
59+
3160
void backlightSet(uint8_t level);
3261

33-
uint8_t backlightGet();
62+
uint8_t backlightGet(); // configured level, unchanged by backlightOff()
63+
64+
bool backlightIsLit(); // what the hardware is being driven at right now
3465

3566
void backlightOn(); // drive the stored level
3667

68+
void backlightMomentaryOn(); // light it regardless of the stored level, for press-and-hold
69+
3770
void backlightOff(); // drive 0, leaving the stored level alone
3871

3972
void backlightToggle();
4073

74+
#if HAS_PWM_BACKLIGHT
4175
void backlightStepUp();
4276

4377
void backlightStepDown();
78+
#endif
4479
} // namespace graphics
4580

46-
#endif // HAS_PWM_BACKLIGHT
81+
#endif // HAS_BACKLIGHT

src/graphics/EInkDisplay2.cpp

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -143,19 +143,14 @@ bool EInkDisplay::connect()
143143
{
144144
LOG_INFO("Do EInk init");
145145

146-
#ifdef PIN_EINK_EN
147-
// backlight power, HIGH is backlight on, LOW is off
148-
pinMode(PIN_EINK_EN, OUTPUT);
149-
#ifdef ELECROW_ThinkNode_M1
150-
// ThinkNode M1 has a hardware dimmable backlight. Start enabled
151-
digitalWrite(PIN_EINK_EN, HIGH);
152-
#elif defined(MINI_EPAPER_S3)
146+
#if HAS_GPIO_BACKLIGHT
147+
// Frontlight rail, level comes from uiconfig and is defaulted per variant
148+
graphics::backlightInit();
149+
#elif defined(PIN_EINK_EN)
153150
// T-Mini Epaper S3 requires panel power rail enabled before SPI transfer.
151+
pinMode(PIN_EINK_EN, OUTPUT);
154152
digitalWrite(PIN_EINK_EN, HIGH);
155153
delay(10);
156-
#else
157-
digitalWrite(PIN_EINK_EN, LOW);
158-
#endif
159154
#endif
160155

161156
#if defined(TTGO_T_ECHO) || defined(ELECROW_ThinkNode_M1) || defined(T_ECHO_LITE) || defined(TTGO_T_ECHO_PLUS) || \

src/graphics/Screen.cpp

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -706,14 +706,8 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
706706
dispdev->displayOn();
707707
#endif
708708

709-
#if HAS_PWM_BACKLIGHT
709+
#if HAS_BACKLIGHT
710710
graphics::backlightOn();
711-
#elif defined(PIN_EINK_EN)
712-
if (uiconfig.screen_brightness == 1)
713-
digitalWrite(PIN_EINK_EN, HIGH);
714-
#elif defined(PCA_PIN_EINK_EN)
715-
if (uiconfig.screen_brightness > 0)
716-
io.digitalWrite(PCA_PIN_EINK_EN, HIGH);
717711
#endif
718712

719713
#if defined(ST7789_CS) && \
@@ -772,12 +766,8 @@ void Screen::handleSetOn(bool on, FrameCallback einkScreensaver)
772766
drawLockdownLockScreen(dispdev);
773767
#endif
774768

775-
#if HAS_PWM_BACKLIGHT
769+
#if HAS_BACKLIGHT
776770
graphics::backlightOff();
777-
#elif defined(PIN_EINK_EN)
778-
digitalWrite(PIN_EINK_EN, LOW);
779-
#elif defined(PCA_PIN_EINK_EN)
780-
io.digitalWrite(PCA_PIN_EINK_EN, LOW);
781771
#endif
782772

783773
dispdev->displayOff();
@@ -835,6 +825,11 @@ void Screen::setup()
835825
// Enable display rendering
836826
useDisplay = true;
837827

828+
#if HAS_BACKLIGHT
829+
// Settles uiconfig.screen_brightness for GPIO backlights, so read it only after this
830+
graphics::backlightInit();
831+
#endif
832+
838833
// Load saved brightness from UI config
839834
// For OLED displays (SSD1306), default brightness is 255 if not set
840835
if (uiconfig.screen_brightness == 0) {

src/graphics/draw/MenuHandler.cpp

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1177,7 +1177,7 @@ void menuHandler::homeBaseMenu()
11771177
}
11781178
optionsEnumArray[options++] = Mute;
11791179
}
1180-
#if HAS_PWM_BACKLIGHT || defined(PIN_EINK_EN) || defined(PCA_PIN_EINK_EN)
1180+
#if HAS_BACKLIGHT
11811181
optionsArray[options] = "Toggle Backlight";
11821182
optionsEnumArray[options++] = Backlight;
11831183
#else
@@ -1207,27 +1207,9 @@ void menuHandler::homeBaseMenu()
12071207
}
12081208
} else if (selected == Backlight) {
12091209
screen->setOn(false);
1210-
#if HAS_PWM_BACKLIGHT
1210+
#if HAS_BACKLIGHT
12111211
graphics::backlightToggle();
12121212
saveUIConfig();
1213-
#elif defined(PIN_EINK_EN)
1214-
if (uiconfig.screen_brightness == 1) {
1215-
uiconfig.screen_brightness = 0;
1216-
digitalWrite(PIN_EINK_EN, LOW);
1217-
} else {
1218-
uiconfig.screen_brightness = 1;
1219-
digitalWrite(PIN_EINK_EN, HIGH);
1220-
}
1221-
saveUIConfig();
1222-
#elif defined(PCA_PIN_EINK_EN)
1223-
if (uiconfig.screen_brightness > 0) {
1224-
uiconfig.screen_brightness = 0;
1225-
io.digitalWrite(PCA_PIN_EINK_EN, LOW);
1226-
} else {
1227-
uiconfig.screen_brightness = 1;
1228-
io.digitalWrite(PCA_PIN_EINK_EN, HIGH);
1229-
}
1230-
saveUIConfig();
12311213
#endif
12321214
} else if (selected == Sleep) {
12331215
screen->setOn(false);

src/input/InputBroker.cpp

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141

4242
#if defined(BUTTON_PIN_TOUCH)
4343
ButtonThread *TouchButtonThread = nullptr;
44-
#if HAS_PWM_BACKLIGHT || defined(PIN_EINK_EN)
44+
#if HAS_BACKLIGHT
4545
static bool touchBacklightWasOn = false;
4646
static bool touchBacklightActive = false;
4747
#endif
@@ -247,41 +247,39 @@ void InputBroker::Init()
247247
};
248248
touchConfig.singlePress = INPUT_BROKER_NONE;
249249
touchConfig.longPress = INPUT_BROKER_BACK;
250-
#if HAS_PWM_BACKLIGHT || defined(PIN_EINK_EN)
250+
#if HAS_BACKLIGHT
251251
// Touch pad drives the backlight on devices that have one
252252
touchConfig.longPress = INPUT_BROKER_NONE;
253+
#endif
254+
#if HAS_BACKLIGHT || defined(HAPTIC_FEEDBACK_PIN)
253255
touchConfig.suppressLeadUpSound = true;
254-
touchConfig.onPress = []() {
255-
touchBacklightWasOn = uiconfig.screen_brightness > 0;
256-
if (!touchBacklightWasOn) {
257-
#if HAS_PWM_BACKLIGHT
258-
graphics::backlightOn();
259-
#else
260-
digitalWrite(PIN_EINK_EN, HIGH);
256+
#if defined(HAPTIC_FEEDBACK_PIN)
257+
initHapticFeedback();
261258
#endif
262-
}
259+
// One handler per event, a second assignment would silently drop the first
260+
touchConfig.onPress = []() {
261+
#if HAS_BACKLIGHT
262+
touchBacklightWasOn = graphics::backlightIsLit();
263+
if (!touchBacklightWasOn)
264+
graphics::backlightMomentaryOn();
263265
touchBacklightActive = true;
266+
#endif
267+
#if defined(HAPTIC_FEEDBACK_PIN)
268+
// Blip on touch, second blip when long-press fires (500 ms = touchConfig.longPressTime default).
269+
hapticFeedback->pulse(80);
270+
hapticFeedback->armDelayedPulse(500, 80);
271+
#endif
264272
};
265273
touchConfig.onRelease = []() {
266-
if (touchBacklightActive && !touchBacklightWasOn) {
267-
#if HAS_PWM_BACKLIGHT
274+
#if HAS_BACKLIGHT
275+
if (touchBacklightActive && !touchBacklightWasOn)
268276
graphics::backlightOff();
269-
#else
270-
digitalWrite(PIN_EINK_EN, LOW);
271-
#endif
272-
}
273277
touchBacklightActive = false;
274-
};
275278
#endif
276279
#if defined(HAPTIC_FEEDBACK_PIN)
277-
// Blip on touch, second blip when long-press fires (500 ms = touchConfig.longPressTime default).
278-
touchConfig.suppressLeadUpSound = true;
279-
initHapticFeedback();
280-
touchConfig.onPress = []() {
281-
hapticFeedback->pulse(80);
282-
hapticFeedback->armDelayedPulse(500, 80);
280+
hapticFeedback->cancelDelayedPulse();
281+
#endif
283282
};
284-
touchConfig.onRelease = []() { hapticFeedback->cancelDelayedPulse(); };
285283
#endif
286284
TouchButtonThread->initButton(touchConfig);
287285
#endif

variants/esp32s3/ELECROW-ThinkNode-M5/variant.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@
7070

7171
#define USE_EINK
7272
// Note: this is really just backlight power
73-
#define PCA_PIN_EINK_EN 5 // This is the pin number on the GPIO expander
73+
#define PCA_PIN_EINK_EN 5 // This is the pin number on the GPIO expander
74+
#define GPIO_BACKLIGHT_DEFAULT_ON // matches the previous screen_brightness > 0 wake behaviour
7475
#define PIN_EINK_CS 39
7576
#define PIN_EINK_BUSY 42
7677
#define PIN_EINK_DC 40

0 commit comments

Comments
 (0)