From 2ffb08843ba59f0af0870335934bdb277fc85620 Mon Sep 17 00:00:00 2001 From: Nathan Gray Date: Tue, 10 Mar 2020 12:50:01 -0700 Subject: Feature: RGBLight layers (#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. --- docs/config_options.md | 2 ++ docs/feature_rgblight.md | 62 ++++++++++++++++++++++++++++++++++++++++++ quantum/rgblight.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++++ quantum/rgblight.h | 27 ++++++++++++++++++ 4 files changed, 162 insertions(+) diff --git a/docs/config_options.md b/docs/config_options.md index f19df022af..661cfccce6 100644 --- a/docs/config_options.md +++ b/docs/config_options.md @@ -190,6 +190,8 @@ If you define these options you will enable the associated feature, which may in * pin the DI on the WS2812 is hooked-up to * `#define RGBLIGHT_ANIMATIONS` * run RGB animations +* `#define RGBLIGHT_LAYERS` + * 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. * `#define RGBLED_NUM 12` * number of LEDs * `#define RGBLIGHT_SPLIT` diff --git a/docs/feature_rgblight.md b/docs/feature_rgblight.md index 69a6aaaed6..a000241f8b 100644 --- a/docs/feature_rgblight.md +++ b/docs/feature_rgblight.md @@ -172,6 +172,62 @@ const uint8_t RGBLED_KNIGHT_INTERVALS[] PROGMEM = {127, 63, 31}; const uint8_t RGBLED_GRADIENT_RANGES[] PROGMEM = {255, 170, 127, 85, 64}; ``` +## Lighting Layers + +By including `#define RGBLIGHT_LAYERS` in your `config.h` file you can enable lighting layers. These make +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. + +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: + +```c +// Light LEDs 6 to 9 and 12 to 15 red when caps lock is active. Hard to ignore! +const rgblight_segment_t PROGMEM my_capslock_layer[] = RGBLIGHT_LAYER_SEGMENTS( + {6, 4, HSV_RED}, // Light 4 LEDs, starting with LED 6 + {12, 4, HSV_RED} // Light 4 LEDs, starting with LED 12 +); +// Light LEDs 9 & 10 in cyan when keyboard layer 1 is active +const rgblight_segment_t PROGMEM my_layer1_layer[] = RGBLIGHT_LAYER_SEGMENTS( + {9, 2, HSV_CYAN} +); +// Light LEDs 11 & 12 in purple when keyboard layer 2 is active +const rgblight_segment_t PROGMEM my_layer2_layer[] = RGBLIGHT_LAYER_SEGMENTS( + {11, 2, HSV_PURPLE}, +); +// etc.. +``` + +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: + +```c +// Now define the array of layers. Later layers take precedence +const rgblight_segment_t* const PROGMEM my_rgb_layers[] = RGBLIGHT_LAYERS_LIST( + my_capslock_layer, + my_layer1_layer, // Overrides caps lock layer + my_layer2_layer // Overrides other layers +); + +void keyboard_post_init_user(void) { + // Enable the LED layers + rgblight_layers = my_rgb_layers; +} +``` + +Finally, we enable and disable the lighting layers whenever the state of the keyboard changes: + +```c +layer_state_t layer_state_set_user(layer_state_t state) { + // Both layers will light up if both kb layers are active + rgblight_set_layer_state(1, layer_state_cmp(state, 1)); + rgblight_set_layer_state(2, layer_state_cmp(state, 2)); + return state; +} + +bool led_update_user(led_t led_state) { + rgblight_set_layer_state(0, led_state.caps_lock); + return true; +} +``` + ## Functions 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 |`rgblight_sethsv(h, s, v)` |Set effect range LEDs to the given HSV value where `h`/`s`/`v` are between 0 and 255 | |`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) | +#### layer functions +|Function |Description | +|--------------------------------------------|-------------| +|`rgblight_get_layer_state(i)` |Returns `true` if lighting layer `i` is enabled | +|`rgblight_set_layer_state(i, is_on)` |Enable or disable lighting layer `i` based on value of `bool is_on` | + #### query |Function |Description | |-----------------------|-----------------| diff --git a/quantum/rgblight.c b/quantum/rgblight.c index f072ae8ca9..b3f0f18d42 100644 --- a/quantum/rgblight.c +++ b/quantum/rgblight.c @@ -38,17 +38,23 @@ # include "velocikey.h" #endif +#ifndef MIN +# define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif + #ifdef RGBLIGHT_SPLIT /* for split keyboard */ # define RGBLIGHT_SPLIT_SET_CHANGE_MODE rgblight_status.change_flags |= RGBLIGHT_STATUS_CHANGE_MODE # define RGBLIGHT_SPLIT_SET_CHANGE_HSVS rgblight_status.change_flags |= RGBLIGHT_STATUS_CHANGE_HSVS # define RGBLIGHT_SPLIT_SET_CHANGE_MODEHSVS rgblight_status.change_flags |= (RGBLIGHT_STATUS_CHANGE_MODE | RGBLIGHT_STATUS_CHANGE_HSVS) +# define RGBLIGHT_SPLIT_SET_CHANGE_LAYERS rgblight_status.change_flags |= RGBLIGHT_STATUS_CHANGE_LAYERS # define RGBLIGHT_SPLIT_SET_CHANGE_TIMER_ENABLE rgblight_status.change_flags |= RGBLIGHT_STATUS_CHANGE_TIMER # define RGBLIGHT_SPLIT_ANIMATION_TICK rgblight_status.change_flags |= RGBLIGHT_STATUS_ANIMATION_TICK #else # define RGBLIGHT_SPLIT_SET_CHANGE_MODE # define RGBLIGHT_SPLIT_SET_CHANGE_HSVS # define RGBLIGHT_SPLIT_SET_CHANGE_MODEHSVS +# define RGBLIGHT_SPLIT_SET_CHANGE_LAYERS # define RGBLIGHT_SPLIT_SET_CHANGE_TIMER_ENABLE # define RGBLIGHT_SPLIT_ANIMATION_TICK #endif @@ -97,6 +103,10 @@ LED_TYPE led[RGBLED_NUM]; # define LED_ARRAY led #endif +#ifdef RGBLIGHT_LAYERS +rgblight_segment_t const * const *rgblight_layers = NULL; +#endif + static uint8_t clipping_start_pos = 0; static uint8_t clipping_num_leds = RGBLED_NUM; 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 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); } #endif // ifndef RGBLIGHT_SPLIT +#ifdef RGBLIGHT_LAYERS +void rgblight_set_layer_state(uint8_t layer, bool enabled) { + uint8_t mask = 1 << layer; + if (enabled) { + rgblight_status.enabled_layer_mask |= mask; + } else { + rgblight_status.enabled_layer_mask &= ~mask; + } + RGBLIGHT_SPLIT_SET_CHANGE_LAYERS; + // Static modes don't have a ticker running to update the LEDs + if (rgblight_status.timer_enabled == false) { + rgblight_mode_noeeprom(rgblight_config.mode); + } +} + +bool rgblight_get_layer_state(uint8_t layer) { + uint8_t mask = 1 << layer; + return (rgblight_status.enabled_layer_mask & mask) != 0; +} + +// Write any enabled LED layers into the buffer +static void rgblight_layers_write(void) { + uint8_t i = 0; + // For each layer + for (const rgblight_segment_t * const *layer_ptr = rgblight_layers; i < RGBLIGHT_MAX_LAYERS; layer_ptr++, i++) { + if (!rgblight_get_layer_state(i)) { + continue; // Layer is disabled + } + const rgblight_segment_t * segment_ptr = pgm_read_ptr(layer_ptr); + if (segment_ptr == NULL) { + break; // No more layers + } + // For each segment + while (1) { + rgblight_segment_t segment; + memcpy_P(&segment, segment_ptr, sizeof(rgblight_segment_t)); + if (segment.index == RGBLIGHT_END_SEGMENT_INDEX) { + break; // No more segments + } + // Write segment.count LEDs + LED_TYPE * const limit = &led[MIN(segment.index + segment.count, RGBLED_NUM)]; + for (LED_TYPE *led_ptr = &led[segment.index]; led_ptr < limit; led_ptr++) { + sethsv(segment.hue, segment.sat, segment.val, led_ptr); + } + segment_ptr++; + } + } +} +#endif + #ifndef RGBLIGHT_CUSTOM_DRIVER void rgblight_set(void) { LED_TYPE *start_led; uint16_t num_leds = clipping_num_leds; +# ifdef RGBLIGHT_LAYERS + if (rgblight_layers != NULL) { + rgblight_layers_write(); + } +# endif + if (!rgblight_config.enable) { for (uint8_t i = effect_start_pos; i < effect_end_pos; i++) { led[i].r = 0; @@ -652,6 +718,11 @@ void rgblight_get_syncinfo(rgblight_syncinfo_t *syncinfo) { /* for split keyboard slave side */ void rgblight_update_sync(rgblight_syncinfo_t *syncinfo, bool write_to_eeprom) { +# ifdef RGBLIGHT_LAYERS + if (syncinfo->status.change_flags & RGBLIGHT_STATUS_CHANGE_LAYERS) { + rgblight_status.enabled_layer_mask = syncinfo->status.enabled_layer_mask; + } +# endif if (syncinfo->status.change_flags & RGBLIGHT_STATUS_CHANGE_MODE) { if (syncinfo->config.enable) { rgblight_config.enable = 1; // == rgblight_enable_noeeprom(); diff --git a/quantum/rgblight.h b/quantum/rgblight.h index 39c4c2784f..40f267daa2 100644 --- a/quantum/rgblight.h +++ b/quantum/rgblight.h @@ -170,6 +170,29 @@ enum RGBLIGHT_EFFECT_MODE { # include # endif +# ifdef RGBLIGHT_LAYERS +typedef struct { + uint8_t index; // The first LED to light + uint8_t count; // The number of LEDs to light + uint8_t hue; + uint8_t sat; + uint8_t val; +} rgblight_segment_t; + +# define RGBLIGHT_END_SEGMENT_INDEX (255) +# define RGBLIGHT_END_SEGMENTS {RGBLIGHT_END_SEGMENT_INDEX, 0, 0, 0} +# define RGBLIGHT_MAX_LAYERS 8 +# define RGBLIGHT_LAYER_SEGMENTS(...) { __VA_ARGS__, RGBLIGHT_END_SEGMENTS } +# define RGBLIGHT_LAYERS_LIST(...) { __VA_ARGS__, NULL } + +// Get/set enabled rgblight layers +void rgblight_set_layer_state(uint8_t layer, bool enabled); +bool rgblight_get_layer_state(uint8_t layer); + +// Point this to an array of rgblight_segment_t arrays in keyboard_post_init_user to use rgblight layers +extern const rgblight_segment_t * const * rgblight_layers; +# endif + extern LED_TYPE led[RGBLED_NUM]; extern const uint8_t RGBLED_BREATHING_INTERVALS[4] PROGMEM; @@ -199,6 +222,9 @@ typedef struct _rgblight_status_t { # ifdef RGBLIGHT_SPLIT uint8_t change_flags; # endif +# ifdef RGBLIGHT_LAYERS + uint8_t enabled_layer_mask; +# endif } rgblight_status_t; /* === Utility Functions ===*/ @@ -313,6 +339,7 @@ void rgblight_timer_toggle(void); # define RGBLIGHT_STATUS_CHANGE_HSVS (1 << 1) # define RGBLIGHT_STATUS_CHANGE_TIMER (1 << 2) # define RGBLIGHT_STATUS_ANIMATION_TICK (1 << 3) +# define RGBLIGHT_STATUS_CHANGE_LAYERS (1 << 4) typedef struct _rgblight_syncinfo_t { rgblight_config_t config; -- cgit v1.2.3