Skip to content

Commit 2ffb088

Browse files
authored
Feature: RGBLight layers (qmk#7768)
* New feature: RGBLIGHT_LAYERS This feature allows users to define multiple independent layers of lighting that can be toggled on and off individually, making it easy to use your RGB lighting to indicate things like active keyboard layer & modifier state. * Demonstrate built in functions for layer state checking Also link the video in the docs. * Follow existing pattern for setting rgblight_status flags * Eliminate rgblight_is_static_mode since it's not needed Just check to see if the timer is enabled directly.
1 parent 2a8ccaf commit 2ffb088

File tree

4 files changed

+162
-0
lines changed

4 files changed

+162
-0
lines changed

docs/config_options.md

+2
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,8 @@ If you define these options you will enable the associated feature, which may in
190190
* pin the DI on the WS2812 is hooked-up to
191191
* `#define RGBLIGHT_ANIMATIONS`
192192
* run RGB animations
193+
* `#define RGBLIGHT_LAYERS`
194+
* Lets you define [lighting layers](feature_rgblight.md) that can be toggled on or off. Great for showing the current keyboard layer or caps lock state.
193195
* `#define RGBLED_NUM 12`
194196
* number of LEDs
195197
* `#define RGBLIGHT_SPLIT`

docs/feature_rgblight.md

+62
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,62 @@ const uint8_t RGBLED_KNIGHT_INTERVALS[] PROGMEM = {127, 63, 31};
172172
const uint8_t RGBLED_GRADIENT_RANGES[] PROGMEM = {255, 170, 127, 85, 64};
173173
```
174174
175+
## Lighting Layers
176+
177+
By including `#define RGBLIGHT_LAYERS` in your `config.h` file you can enable lighting layers. These make
178+
it easy to use your underglow LEDs as status indicators to show which keyboard layer is currently active, or the state of caps lock, all without disrupting any animations. [Here's a video](https://youtu.be/uLGE1epbmdY) showing an example of what you can do.
179+
180+
To define a layer, we modify `keymap.c` to list out LED ranges and the colors we want to overlay on them using an array of `rgblight_segment_t` using the `RGBLIGHT_LAYER_SEGMENTS` macro. We can define multiple layers and enable/disable them independently:
181+
182+
```c
183+
// Light LEDs 6 to 9 and 12 to 15 red when caps lock is active. Hard to ignore!
184+
const rgblight_segment_t PROGMEM my_capslock_layer[] = RGBLIGHT_LAYER_SEGMENTS(
185+
{6, 4, HSV_RED}, // Light 4 LEDs, starting with LED 6
186+
{12, 4, HSV_RED} // Light 4 LEDs, starting with LED 12
187+
);
188+
// Light LEDs 9 & 10 in cyan when keyboard layer 1 is active
189+
const rgblight_segment_t PROGMEM my_layer1_layer[] = RGBLIGHT_LAYER_SEGMENTS(
190+
{9, 2, HSV_CYAN}
191+
);
192+
// Light LEDs 11 & 12 in purple when keyboard layer 2 is active
193+
const rgblight_segment_t PROGMEM my_layer2_layer[] = RGBLIGHT_LAYER_SEGMENTS(
194+
{11, 2, HSV_PURPLE},
195+
);
196+
// etc..
197+
```
198+
199+
We combine these layers into an array using the `RGBLIGHT_LAYERS_LIST` macro, and assign it to the `rgblight_layers` variable during keyboard setup. Note that you can only define up to 8 lighting layers. Any extra layers will be ignored. Since the different lighting layers overlap, the order matters in the array, with later layers taking precedence:
200+
201+
```c
202+
// Now define the array of layers. Later layers take precedence
203+
const rgblight_segment_t* const PROGMEM my_rgb_layers[] = RGBLIGHT_LAYERS_LIST(
204+
my_capslock_layer,
205+
my_layer1_layer, // Overrides caps lock layer
206+
my_layer2_layer // Overrides other layers
207+
);
208+
209+
void keyboard_post_init_user(void) {
210+
// Enable the LED layers
211+
rgblight_layers = my_rgb_layers;
212+
}
213+
```
214+
215+
Finally, we enable and disable the lighting layers whenever the state of the keyboard changes:
216+
217+
```c
218+
layer_state_t layer_state_set_user(layer_state_t state) {
219+
// Both layers will light up if both kb layers are active
220+
rgblight_set_layer_state(1, layer_state_cmp(state, 1));
221+
rgblight_set_layer_state(2, layer_state_cmp(state, 2));
222+
return state;
223+
}
224+
225+
bool led_update_user(led_t led_state) {
226+
rgblight_set_layer_state(0, led_state.caps_lock);
227+
return true;
228+
}
229+
```
230+
175231
## Functions
176232

177233
If you need to change your RGB lighting in code, for example in a macro to change the color whenever you switch layers, QMK provides a set of functions to assist you. See [`rgblight.h`](https://github.com/qmk/qmk_firmware/blob/master/quantum/rgblight.h) for the full list, but the most commonly used functions include:
@@ -263,6 +319,12 @@ rgblight_sethsv(HSV_GREEN, 2); // led 2
263319
|`rgblight_sethsv(h, s, v)` |Set effect range LEDs to the given HSV value where `h`/`s`/`v` are between 0 and 255 |
264320
|`rgblight_sethsv_noeeprom(h, s, v)` |Set effect range LEDs to the given HSV value where `h`/`s`/`v` are between 0 and 255 (not written to EEPROM) |
265321

322+
#### layer functions
323+
|Function |Description |
324+
|--------------------------------------------|-------------|
325+
|`rgblight_get_layer_state(i)` |Returns `true` if lighting layer `i` is enabled |
326+
|`rgblight_set_layer_state(i, is_on)` |Enable or disable lighting layer `i` based on value of `bool is_on` |
327+
266328
#### query
267329
|Function |Description |
268330
|-----------------------|-----------------|

quantum/rgblight.c

+71
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,23 @@
3838
# include "velocikey.h"
3939
#endif
4040

41+
#ifndef MIN
42+
# define MIN(a, b) (((a) < (b)) ? (a) : (b))
43+
#endif
44+
4145
#ifdef RGBLIGHT_SPLIT
4246
/* for split keyboard */
4347
# define RGBLIGHT_SPLIT_SET_CHANGE_MODE rgblight_status.change_flags |= RGBLIGHT_STATUS_CHANGE_MODE
4448
# define RGBLIGHT_SPLIT_SET_CHANGE_HSVS rgblight_status.change_flags |= RGBLIGHT_STATUS_CHANGE_HSVS
4549
# define RGBLIGHT_SPLIT_SET_CHANGE_MODEHSVS rgblight_status.change_flags |= (RGBLIGHT_STATUS_CHANGE_MODE | RGBLIGHT_STATUS_CHANGE_HSVS)
50+
# define RGBLIGHT_SPLIT_SET_CHANGE_LAYERS rgblight_status.change_flags |= RGBLIGHT_STATUS_CHANGE_LAYERS
4651
# define RGBLIGHT_SPLIT_SET_CHANGE_TIMER_ENABLE rgblight_status.change_flags |= RGBLIGHT_STATUS_CHANGE_TIMER
4752
# define RGBLIGHT_SPLIT_ANIMATION_TICK rgblight_status.change_flags |= RGBLIGHT_STATUS_ANIMATION_TICK
4853
#else
4954
# define RGBLIGHT_SPLIT_SET_CHANGE_MODE
5055
# define RGBLIGHT_SPLIT_SET_CHANGE_HSVS
5156
# define RGBLIGHT_SPLIT_SET_CHANGE_MODEHSVS
57+
# define RGBLIGHT_SPLIT_SET_CHANGE_LAYERS
5258
# define RGBLIGHT_SPLIT_SET_CHANGE_TIMER_ENABLE
5359
# define RGBLIGHT_SPLIT_ANIMATION_TICK
5460
#endif
@@ -97,6 +103,10 @@ LED_TYPE led[RGBLED_NUM];
97103
# define LED_ARRAY led
98104
#endif
99105

106+
#ifdef RGBLIGHT_LAYERS
107+
rgblight_segment_t const * const *rgblight_layers = NULL;
108+
#endif
109+
100110
static uint8_t clipping_start_pos = 0;
101111
static uint8_t clipping_num_leds = RGBLED_NUM;
102112
static uint8_t effect_start_pos = 0;
@@ -604,11 +614,67 @@ void rgblight_sethsv_master(uint8_t hue, uint8_t sat, uint8_t val) { rgblight_se
604614
void rgblight_sethsv_slave(uint8_t hue, uint8_t sat, uint8_t val) { rgblight_sethsv_range(hue, sat, val, (uint8_t)RGBLED_NUM / 2, (uint8_t)RGBLED_NUM); }
605615
#endif // ifndef RGBLIGHT_SPLIT
606616

617+
#ifdef RGBLIGHT_LAYERS
618+
void rgblight_set_layer_state(uint8_t layer, bool enabled) {
619+
uint8_t mask = 1 << layer;
620+
if (enabled) {
621+
rgblight_status.enabled_layer_mask |= mask;
622+
} else {
623+
rgblight_status.enabled_layer_mask &= ~mask;
624+
}
625+
RGBLIGHT_SPLIT_SET_CHANGE_LAYERS;
626+
// Static modes don't have a ticker running to update the LEDs
627+
if (rgblight_status.timer_enabled == false) {
628+
rgblight_mode_noeeprom(rgblight_config.mode);
629+
}
630+
}
631+
632+
bool rgblight_get_layer_state(uint8_t layer) {
633+
uint8_t mask = 1 << layer;
634+
return (rgblight_status.enabled_layer_mask & mask) != 0;
635+
}
636+
637+
// Write any enabled LED layers into the buffer
638+
static void rgblight_layers_write(void) {
639+
uint8_t i = 0;
640+
// For each layer
641+
for (const rgblight_segment_t * const *layer_ptr = rgblight_layers; i < RGBLIGHT_MAX_LAYERS; layer_ptr++, i++) {
642+
if (!rgblight_get_layer_state(i)) {
643+
continue; // Layer is disabled
644+
}
645+
const rgblight_segment_t * segment_ptr = pgm_read_ptr(layer_ptr);
646+
if (segment_ptr == NULL) {
647+
break; // No more layers
648+
}
649+
// For each segment
650+
while (1) {
651+
rgblight_segment_t segment;
652+
memcpy_P(&segment, segment_ptr, sizeof(rgblight_segment_t));
653+
if (segment.index == RGBLIGHT_END_SEGMENT_INDEX) {
654+
break; // No more segments
655+
}
656+
// Write segment.count LEDs
657+
LED_TYPE * const limit = &led[MIN(segment.index + segment.count, RGBLED_NUM)];
658+
for (LED_TYPE *led_ptr = &led[segment.index]; led_ptr < limit; led_ptr++) {
659+
sethsv(segment.hue, segment.sat, segment.val, led_ptr);
660+
}
661+
segment_ptr++;
662+
}
663+
}
664+
}
665+
#endif
666+
607667
#ifndef RGBLIGHT_CUSTOM_DRIVER
608668
void rgblight_set(void) {
609669
LED_TYPE *start_led;
610670
uint16_t num_leds = clipping_num_leds;
611671

672+
# ifdef RGBLIGHT_LAYERS
673+
if (rgblight_layers != NULL) {
674+
rgblight_layers_write();
675+
}
676+
# endif
677+
612678
if (!rgblight_config.enable) {
613679
for (uint8_t i = effect_start_pos; i < effect_end_pos; i++) {
614680
led[i].r = 0;
@@ -652,6 +718,11 @@ void rgblight_get_syncinfo(rgblight_syncinfo_t *syncinfo) {
652718

653719
/* for split keyboard slave side */
654720
void rgblight_update_sync(rgblight_syncinfo_t *syncinfo, bool write_to_eeprom) {
721+
# ifdef RGBLIGHT_LAYERS
722+
if (syncinfo->status.change_flags & RGBLIGHT_STATUS_CHANGE_LAYERS) {
723+
rgblight_status.enabled_layer_mask = syncinfo->status.enabled_layer_mask;
724+
}
725+
# endif
655726
if (syncinfo->status.change_flags & RGBLIGHT_STATUS_CHANGE_MODE) {
656727
if (syncinfo->config.enable) {
657728
rgblight_config.enable = 1; // == rgblight_enable_noeeprom();

quantum/rgblight.h

+27
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,29 @@ enum RGBLIGHT_EFFECT_MODE {
170170
# include <avr/pgmspace.h>
171171
# endif
172172

173+
# ifdef RGBLIGHT_LAYERS
174+
typedef struct {
175+
uint8_t index; // The first LED to light
176+
uint8_t count; // The number of LEDs to light
177+
uint8_t hue;
178+
uint8_t sat;
179+
uint8_t val;
180+
} rgblight_segment_t;
181+
182+
# define RGBLIGHT_END_SEGMENT_INDEX (255)
183+
# define RGBLIGHT_END_SEGMENTS {RGBLIGHT_END_SEGMENT_INDEX, 0, 0, 0}
184+
# define RGBLIGHT_MAX_LAYERS 8
185+
# define RGBLIGHT_LAYER_SEGMENTS(...) { __VA_ARGS__, RGBLIGHT_END_SEGMENTS }
186+
# define RGBLIGHT_LAYERS_LIST(...) { __VA_ARGS__, NULL }
187+
188+
// Get/set enabled rgblight layers
189+
void rgblight_set_layer_state(uint8_t layer, bool enabled);
190+
bool rgblight_get_layer_state(uint8_t layer);
191+
192+
// Point this to an array of rgblight_segment_t arrays in keyboard_post_init_user to use rgblight layers
193+
extern const rgblight_segment_t * const * rgblight_layers;
194+
# endif
195+
173196
extern LED_TYPE led[RGBLED_NUM];
174197

175198
extern const uint8_t RGBLED_BREATHING_INTERVALS[4] PROGMEM;
@@ -199,6 +222,9 @@ typedef struct _rgblight_status_t {
199222
# ifdef RGBLIGHT_SPLIT
200223
uint8_t change_flags;
201224
# endif
225+
# ifdef RGBLIGHT_LAYERS
226+
uint8_t enabled_layer_mask;
227+
# endif
202228
} rgblight_status_t;
203229

204230
/* === Utility Functions ===*/
@@ -313,6 +339,7 @@ void rgblight_timer_toggle(void);
313339
# define RGBLIGHT_STATUS_CHANGE_HSVS (1 << 1)
314340
# define RGBLIGHT_STATUS_CHANGE_TIMER (1 << 2)
315341
# define RGBLIGHT_STATUS_ANIMATION_TICK (1 << 3)
342+
# define RGBLIGHT_STATUS_CHANGE_LAYERS (1 << 4)
316343

317344
typedef struct _rgblight_syncinfo_t {
318345
rgblight_config_t config;

0 commit comments

Comments
 (0)