Skip to content

Commit 178bcba

Browse files
committed
Implement display sleep
- Set PWM_MIN to 15% instead of 1% to ensure display is always visible - Force 20% brightness on wake in ALS mode before ALS thread adjusts - Set sleeping=false before blank_off() to prevent race condition with ALS thread - Remove LED debug code Fixes issue where display would wake but appear off in dark rooms due to 1% backlight being invisible. Display now wakes with visible brightness that ALS adjusts within 150ms.
1 parent 67425b7 commit 178bcba

File tree

6 files changed

+208
-4
lines changed

6 files changed

+208
-4
lines changed

Kconfig

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,18 @@ config PROSPECTOR_FIXED_BRIGHTNESS
1414
int "Fixed display brightness"
1515
default 50
1616
range 1 100
17-
depends on !PROSPECTOR_USE_AMBIENT_LIGHT_SENSOR
17+
depends on !PROSPECTOR_USE_AMBIENT_LIGHT_SENSOR
18+
19+
config PROSPECTOR_DISPLAY_SLEEP_ENABLE
20+
bool "Turn off display on idle"
21+
default n
22+
23+
config PROSPECTOR_DISPLAY_IDLE_TIMEOUT_MS
24+
int "Display idle timeout in ms"
25+
default 30000
26+
depends on PROSPECTOR_DISPLAY_SLEEP_ENABLE
27+
28+
config PROSPECTOR_DISPLAY_SLEEP_USE_PM
29+
bool "Use Zephyr PM to suspend the display (SLEEP_IN)"
30+
default n
31+
depends on PROSPECTOR_DISPLAY_SLEEP_ENABLE && PM_DEVICE

README.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,21 @@ To customize, add config options to your `config/[YOUR KEYBOARD SHIELD].conf` li
6969
```ini
7070
CONFIG_PROSPECTOR_USE_AMBIENT_LIGHT_SENSOR=n
7171
CONFIG_PROSPECTOR_FIXED_BRIGHTNESS=80
72+
CONFIG_PROSPECTOR_DISPLAY_SLEEP_ENABLE=y
73+
CONFIG_PROSPECTOR_DISPLAY_IDLE_TIMEOUT_MS=30000
74+
# Optional: put display controller into sleep
75+
# CONFIG_PM_DEVICE=y
76+
# CONFIG_PROSPECTOR_DISPLAY_SLEEP_USE_PM=y
7277
```
7378

7479
### Available config options:
7580
| Name | Description | Default |
7681
| ------------------------------------------------- | --------------------------------------------------------------------------| ------------ |
7782
| `CONFIG_PROSPECTOR_USE_AMBIENT_LIGHT_SENSOR` | Use ambient light sensor for auto brightness, set to `n` if building without one | y |
78-
| `CONFIG_PROSPECTOR_FIXED_BRIGHTESS` | Set fixed display brightess when not using ambient light sensor | 50 (1-100) |
83+
| `CONFIG_PROSPECTOR_FIXED_BRIGHTNESS` | Set fixed display brightess when not using ambient light sensor | 50 (1-100) |
7984
| `CONFIG_PROSPECTOR_PROSPECTOR_ROTATE_DISPLAY_180` | Rotate the display 180 degrees | n |
80-
| `CONFIG_PROSPECTOR_LAYER_ROLLER_ALL_CAPS` | Convert layer names to all caps | n |
85+
| `CONFIG_PROSPECTOR_LAYER_ROLLER_ALL_CAPS` | Convert layer names to all caps | n |
86+
| `CONFIG_PROSPECTOR_DISPLAY_SLEEP_ENABLE` | Turn off display and backlight on idle | n |
87+
| `CONFIG_PROSPECTOR_DISPLAY_IDLE_TIMEOUT_MS` | Idle timeout before turning display off (ms) | 30000 |
88+
| `CONFIG_PROSPECTOR_DISPLAY_SLEEP_USE_PM` | Use Zephyr PM to suspend display controller (requires `CONFIG_PM_DEVICE`) | n |
89+

boards/shields/prospector_adapter/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ if(CONFIG_SHIELD_PROSPECTOR_ADAPTER)
1212
zephyr_library_sources(src/brightness.c)
1313
zephyr_library_sources(src/custom_status_screen.c)
1414
zephyr_library_sources(src/display_rotate_init.c)
15+
zephyr_library_sources(src/display_idle.c)
1516
zephyr_library_sources(src/widgets/layer_roller.c)
1617
zephyr_library_sources(src/widgets/battery_bar.c)
1718
zephyr_library_sources_ifdef(CONFIG_DT_HAS_ZMK_BEHAVIOR_CAPS_WORD_ENABLED src/widgets/caps_word_indicator.c)

boards/shields/prospector_adapter/src/brightness.c

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
#include <zephyr/logging/log.h>
99
LOG_MODULE_REGISTER(als, 4);
1010

11+
#include "prospector/display_power.h"
12+
1113
static const struct device *pwm_leds_dev = DEVICE_DT_GET_ONE(pwm_leds);
1214
#define DISP_BL DT_NODE_CHILD_IDX(DT_NODELABEL(disp_bl))
1315

@@ -17,7 +19,7 @@ static uint8_t current_brightness = 100;
1719

1820
#define SENSOR_MIN 0 // Minimum sensor reading
1921
#define SENSOR_MAX 100 // Maximum sensor reading
20-
#define PWM_MIN 1 // Minimum PWM duty cycle (%) - keep display visible
22+
#define PWM_MIN 15 // Minimum PWM duty cycle (%) - keep display visible even in dark
2123
#define PWM_MAX 100 // Maximum PWM duty cycle (%)
2224

2325
#define FADE_STEP 1
@@ -94,6 +96,15 @@ extern void als_thread(void *d0, void *d1, void *d2) {
9496

9597
while (1) {
9698

99+
if (prospector_display_is_sleeping()) {
100+
if (current_brightness != 0) {
101+
led_set_brightness(pwm_leds_dev, DISP_BL, 0);
102+
current_brightness = 0;
103+
}
104+
k_msleep(NORMAL_SAMPLE_SLEEP_MS);
105+
continue;
106+
}
107+
97108
k_msleep(NORMAL_SAMPLE_SLEEP_MS);
98109

99110

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
#include <zephyr/kernel.h>
2+
#include <zephyr/device.h>
3+
#include <zephyr/drivers/display.h>
4+
#include <zephyr/drivers/led.h>
5+
#include <zephyr/logging/log.h>
6+
LOG_MODULE_REGISTER(display_idle, CONFIG_ZMK_LOG_LEVEL);
7+
8+
#include <zmk/event_manager.h>
9+
#include <zmk/events/keycode_state_changed.h>
10+
#include <zmk/events/position_state_changed.h>
11+
12+
#if IS_ENABLED(CONFIG_PM_DEVICE)
13+
#include <zephyr/pm/device.h>
14+
#endif
15+
16+
#include "prospector/display_power.h"
17+
18+
static const struct device *display_dev;
19+
static const struct device *pwm_leds_dev;
20+
21+
#define DISP_BL DT_NODE_CHILD_IDX(DT_NODELABEL(disp_bl))
22+
23+
static struct k_work_delayable idle_work;
24+
static atomic_t sleeping = ATOMIC_INIT(0);
25+
26+
static void prospector_display_blank_on(void) {
27+
if (!display_dev) {
28+
return;
29+
}
30+
31+
#if IS_ENABLED(CONFIG_PROSPECTOR_DISPLAY_SLEEP_USE_PM) && IS_ENABLED(CONFIG_PM_DEVICE)
32+
pm_device_action_run(display_dev, PM_DEVICE_ACTION_SUSPEND);
33+
#else
34+
display_blanking_on(display_dev);
35+
#endif
36+
}
37+
38+
static void prospector_display_blank_off(void) {
39+
if (!display_dev) {
40+
return;
41+
}
42+
43+
#if IS_ENABLED(CONFIG_PROSPECTOR_DISPLAY_SLEEP_USE_PM) && IS_ENABLED(CONFIG_PM_DEVICE)
44+
pm_device_action_run(display_dev, PM_DEVICE_ACTION_RESUME);
45+
#else
46+
display_blanking_off(display_dev);
47+
#endif
48+
}
49+
50+
void prospector_display_set_sleeping(bool s) {
51+
atomic_set(&sleeping, s ? 1 : 0);
52+
}
53+
54+
bool prospector_display_is_sleeping(void) {
55+
return atomic_get(&sleeping) != 0;
56+
}
57+
58+
static void display_go_to_sleep(void) {
59+
if (prospector_display_is_sleeping()) {
60+
return;
61+
}
62+
63+
prospector_display_blank_on();
64+
65+
if (pwm_leds_dev) {
66+
led_set_brightness(pwm_leds_dev, DISP_BL, 0);
67+
}
68+
69+
prospector_display_set_sleeping(true);
70+
}
71+
72+
static void display_wake_up(void) {
73+
if (!prospector_display_is_sleeping()) {
74+
return;
75+
}
76+
77+
prospector_display_set_sleeping(false);
78+
79+
prospector_display_blank_off();
80+
81+
#if !IS_ENABLED(CONFIG_PROSPECTOR_USE_AMBIENT_LIGHT_SENSOR)
82+
if (pwm_leds_dev) {
83+
led_set_brightness(pwm_leds_dev, DISP_BL, CONFIG_PROSPECTOR_FIXED_BRIGHTNESS);
84+
}
85+
#else
86+
/* For ALS mode: force minimum visible brightness on wake (20%)
87+
* ALS thread will adjust to proper level shortly after */
88+
if (pwm_leds_dev) {
89+
led_set_brightness(pwm_leds_dev, DISP_BL, 20);
90+
}
91+
/* Give ALS thread time to react and adjust brightness */
92+
k_msleep(150);
93+
#endif
94+
}
95+
96+
static void idle_work_handler(struct k_work *work) {
97+
ARG_UNUSED(work);
98+
if (!IS_ENABLED(CONFIG_PROSPECTOR_DISPLAY_SLEEP_ENABLE)) {
99+
return;
100+
}
101+
display_go_to_sleep();
102+
}
103+
104+
static int display_idle_init(const struct device *unused) {
105+
ARG_UNUSED(unused);
106+
107+
display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));
108+
if (!device_is_ready(display_dev)) {
109+
return 0;
110+
}
111+
112+
pwm_leds_dev = DEVICE_DT_GET_ONE(pwm_leds);
113+
if (!device_is_ready(pwm_leds_dev)) {
114+
pwm_leds_dev = NULL;
115+
}
116+
117+
k_work_init_delayable(&idle_work, idle_work_handler);
118+
119+
#if IS_ENABLED(CONFIG_PROSPECTOR_DISPLAY_SLEEP_ENABLE)
120+
k_work_schedule(&idle_work, K_MSEC(CONFIG_PROSPECTOR_DISPLAY_IDLE_TIMEOUT_MS));
121+
#endif
122+
123+
return 0;
124+
}
125+
SYS_INIT(display_idle_init, APPLICATION, 70);
126+
127+
static int on_any_key_event(const zmk_event_t *eh) {
128+
if (!IS_ENABLED(CONFIG_PROSPECTOR_DISPLAY_SLEEP_ENABLE)) {
129+
return ZMK_EV_EVENT_BUBBLE;
130+
}
131+
132+
const struct zmk_keycode_state_changed *kc = as_zmk_keycode_state_changed(eh);
133+
const struct zmk_position_state_changed *pos = as_zmk_position_state_changed(eh);
134+
135+
bool pressed = false;
136+
if (kc) {
137+
pressed = kc->state;
138+
} else if (pos) {
139+
pressed = pos->state;
140+
}
141+
142+
if (!pressed) {
143+
return ZMK_EV_EVENT_BUBBLE;
144+
}
145+
146+
display_wake_up();
147+
#if IS_ENABLED(CONFIG_PROSPECTOR_DISPLAY_SLEEP_ENABLE)
148+
k_work_reschedule(&idle_work, K_MSEC(CONFIG_PROSPECTOR_DISPLAY_IDLE_TIMEOUT_MS));
149+
#endif
150+
151+
return ZMK_EV_EVENT_BUBBLE;
152+
}
153+
154+
ZMK_LISTENER(prospector_display_idle, on_any_key_event);
155+
ZMK_SUBSCRIPTION(prospector_display_idle, zmk_keycode_state_changed);
156+
ZMK_SUBSCRIPTION(prospector_display_idle, zmk_position_state_changed);
157+
158+

include/prospector/display_power.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* Prospector display power state helper
3+
*/
4+
#pragma once
5+
6+
#include <stdbool.h>
7+
8+
void prospector_display_set_sleeping(bool sleeping);
9+
bool prospector_display_is_sleeping(void);
10+
11+

0 commit comments

Comments
 (0)