initial commit
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
---
|
||||
title: "Lilac's Website"
|
||||
description: "Computer Engineer & [Certified Punk Rocker](https://youtu.be/z9LiPuVRyU8&t=261)"
|
||||
---
|
||||
# About Me
|
||||
|
||||
Howdy! My name is Lilac, and this is my website. I am interested in low-level
|
||||
computing, networking, and Linux. When not tinkering with 'puters, I birdwatch,
|
||||
crochet, and garden.
|
||||
|
||||
# Technology
|
||||
|
||||
I write embedded firmware and POSIX/Linux applications using C and several
|
||||
dialects of assembly. I do the server administration for all my own web services
|
||||
using Linux and Nix.
|
||||
|
||||
Here is some of the software I've written:
|
||||
|
||||
[Tanager](https://code.grasswren.net/Lilac/Tanager)
|
||||
: QMK keyboard firmware with flashy backlighting
|
||||
|
||||
[grasswren.net](https://code.grasswren.net/Lilac/grasswren.net)
|
||||
: Content and Hugo theme for this website
|
||||
|
||||
# Music
|
||||
|
||||
My favourite genres of music are grunge, punk, hyperpop, breakcore, and digital
|
||||
hardcore. I really enjoy the music scene where I live; I think everybody should
|
||||
get out there and interact with their local artists.
|
||||
|
||||
I self-host my music library, so I don't have opportunities to discover new
|
||||
music very often. If you are visiting this site for the first time, [shoot me an
|
||||
email](mailto:lilac@grasswren.net) telling me what I should listen to next!
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: Lilac's Birds
|
||||
description: They're cute little guys, aren't they?
|
||||
---
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Lilac's Blog"
|
||||
description: "I love pretending I know what's going on!"
|
||||
---
|
||||
@@ -0,0 +1,154 @@
|
||||
---
|
||||
title: "Building Custom Keyboard Backlighting"
|
||||
description: "Upgrading my [Ergodox EZ](https://ergodox-ez.com/) using [QMK](https://github.com/qmk/qmk_firmware)"
|
||||
date: 2026-07-09
|
||||
---
|
||||
# Intro
|
||||
|
||||
I own an Ergodox EZ, a very comfortable, split, columnar keyboard. I bought it
|
||||
because I gave myself carpal tunnel typing on my last keyboard, and I wanted to
|
||||
see if I could type any faster on a split board.[^1] Ergodox keyboards run
|
||||
open-source QMK firmware, meaning they are extensively configurable for a
|
||||
programmer like me! I love my keyboard, and I would recommend a split keyboard
|
||||
to anybody looking for something more comfortable than a standard keyboard.
|
||||
|
||||
# Key Layout
|
||||
|
||||
My key layout isn't the main focus of this post, but I wanted to highlight it
|
||||
because it uses some cool features QMK offers.
|
||||
|
||||

|
||||
|
||||
The first thing to notice is that I have plenty of thumb keys for common
|
||||
keycodes. I never really liked that common keys like Enter, Backspace, and
|
||||
Escape[^2] were off to the side, so I moved them to be more easily accessible.
|
||||
|
||||
On the bottom left side, there is a key labelled Layer. QMK has a concept called
|
||||
[layers](https://docs.qmk.fm/feature_layers), not unlike how a phone keyboard
|
||||
works: tap or hold a layer key, and access different keys. I use this for my
|
||||
function keys because I use them so infrequently, and I don't want them wasting
|
||||
space normally.
|
||||
|
||||
Finally, I have a key labelled Macro. I commonly type `->` when programming, so
|
||||
I made this key type both characters in a single stroke. I also modified the
|
||||
handling of the Backspace key so it deletes both characters if the previous
|
||||
keycode was Macro.
|
||||
|
||||
# Backlighting
|
||||
|
||||
All the default backlighting options provided by QMK are too boring for me, so I
|
||||
rolled my own. It works like this, with each keyboard half operating
|
||||
independently:
|
||||
|
||||
- Every keystroke changes the backlight colour
|
||||
- When all keys are released, the backlight LEDs fade out nicely
|
||||
- Backlight LEDs closer to the most recently pressed key are brighter than
|
||||
those further from it
|
||||
|
||||
Every time a keystroke is detected, the following function runs, tracking which
|
||||
key was pressed and updating the colours:
|
||||
|
||||
```c
|
||||
void post_process_record_user(uint16_t keycode, keyrecord_t *record)
|
||||
{
|
||||
if (!backlight || !record->event.pressed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (record->event.key.row < 7) {
|
||||
hue_left = rand();
|
||||
brightness_left = 255;
|
||||
|
||||
if (record->event.key.col != 5) {
|
||||
position_left = 29 - 2 * record->event.key.row;
|
||||
} else {
|
||||
position_left = 15;
|
||||
}
|
||||
|
||||
color(hue_left, brightness_left, position_left, 15);
|
||||
} else {
|
||||
hue_right = rand();
|
||||
brightness_right = 255;
|
||||
|
||||
if (record->event.key.col != 5) {
|
||||
position_right = 26 - 2 * record->event.key.row;
|
||||
} else {
|
||||
position_right = 14;
|
||||
}
|
||||
|
||||
color(hue_right, brightness_right, position_right, 0);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This function is rather long because I have to control the keyboard halves
|
||||
independently, but the main idea is simple. Every keystroke:
|
||||
|
||||
- Randomize the colour hue
|
||||
- Maximize the brightness
|
||||
- Record the column the key is in
|
||||
- Turn on the LEDs using those values
|
||||
|
||||
In another function, I keep track of how many keys are pressed at any given
|
||||
time, so I can dim the LEDs once no more are pressed:
|
||||
|
||||
```c
|
||||
if (record->event.key.row < 7) {
|
||||
count_left += record->event.pressed ? 1 : -1;
|
||||
} else {
|
||||
count_right += record->event.pressed ? 1 : -1;
|
||||
}
|
||||
```
|
||||
|
||||
## Brightness Calculation
|
||||
|
||||
Now that I track these values, I need to use them to calculate the brightness of
|
||||
each LED:
|
||||
|
||||
```c
|
||||
static void color(uint8_t hue, uint8_t brightness, uint8_t position,
|
||||
uint8_t index)
|
||||
{
|
||||
for (int i = index; i < (index + 15); ++i) {
|
||||
const uint32_t cube = (uint32_t)brightness * brightness * brightness;
|
||||
const uint8_t distance = position > i ? position - i : i - position;
|
||||
sethsv(hue, 255, cube / 65025 * 5 / (5 + distance), i);
|
||||
}
|
||||
rgblight_set();
|
||||
}
|
||||
```
|
||||
|
||||
This function takes in a hue, brightness, position, and starting index. There
|
||||
are 30 LEDs on an Ergodox EZ, meaning that I can pass either `0` or `15` as the
|
||||
starting index to have the function loop over the right or left LEDs
|
||||
respectively.
|
||||
|
||||
The `brightness` and `position` parameters contribute to the brightness of the
|
||||
LED. First, the brightness is passed through a cubic ease-out function. This
|
||||
produces a more pleasing dimming as opposed to linearly dimming the LED, as the
|
||||
human eye senses brightness on a logarithmic scale. Second, the brightness is
|
||||
scaled down by an amount proportional to its distance from the last key pressed.
|
||||
The scaling function being used is `5 / (5 + n)` where `n` is the distance in
|
||||
LED index from the brightest LED, which is not dimmed because its scaling
|
||||
fraction evaluates to `1`.
|
||||
|
||||
The `sethsv` function can then be passed a hue (given as a parameter and
|
||||
unchanging as the LED is dimmed), a saturation (always the maximum), the value
|
||||
(or brightness, as just calculated), and the index of the LED. Finally, I flush
|
||||
my LED settings out to the LED peripheral with the `rgblight_set` function.
|
||||
|
||||
# Result
|
||||
|
||||
With a little logic, the backlights on each keyboard half act just as I wanted,
|
||||
lighting up reactively and fading when no more keys are pressed.
|
||||
|
||||
Take a look!
|
||||
|
||||

|
||||
|
||||
# Footnotes
|
||||
|
||||
[^1]: Quite a bit!
|
||||
|
||||
[^2]: I use the [Helix](https://helix-editor.com/) editor, meaning I type
|
||||
Escape almost as much as I type Space.
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 117 KiB |
Reference in New Issue
Block a user