Thomas Pain :: blog

Adding indicator LEDs to a Let's Split keyboard

Modifying hardware and software to add new stuff

2022-01-27 :: 903 words


I recently switched to using a custom-built, split keyboard. It's built from a design by u/wootpatoot called the Let's Split keyboard. As far as mechanical, split keyboards go, it's pretty run of the mill - two halves, each with their own ProMicro microcontroller, connected by a 4-pin TRRS cable. One side connects to the computer via a USB cable and provides power to the other, which in turn provides data about button presses back to the first.

Typically, people use the QMK firmware to power their boards, and I did the same. It lets you define your own keymaps, and includes a variety of features (most of which I ignore). On my Let's Split keymap, I have one modifier key that switches to things like my arrow-key cluster, and other random utility buttons. I've chosen to make this key toggle when I tap it multiple times, and act as a momentary pushbutton when I press and hold it.

Sometimes I'd accidentally trigger the toggle to this other layer, go to type something and become very confused about what was going on, as the Let's Split doesn't include any indicators to tell you what's going on, so I thought I'd add some.

The first thing to do is to check that it's actually possible to do. This'd hinge on how many I/O pins are used by the keyboard on each microcontroller. I planned to use a shift register to drive multiple LEDs based on as few signal wires as possible, so all I needed were three empty I/O pins.

Luckily, the KiCAD project files for the Let's Split are on GitHub. A cursory look at the board layout shows six disconnected I/O pins that we can use (it would have been easier to look at the schematic, but loadsa footprints were missing and I couldn't be bothered to work that out).

board view in KiCAD

Great! I can add some indicator LEDs. Now to design a circuit.

The whole thing is based around a SN74HC595N Serial-In Parallel-Out (SIPO) shift register. It'll sit on a sheet of perfboard and'll be handwired to four 3mm LEDs. Why four? The maximum number of layers that QMK support is 16, which conveniently fits into a 4-bit binary number.

A more detailed run-down of how the 74HC595 works can be found here. All we need is to connect the SER, RCLK and SRCLK pins to one of the ProMicros, add power and some LEDs, and we have a working set of LEDs connected to our keyboard.

Howeverrrrr, there's one small issue, concerning real-world space requirements. There's pretty limited space underneath the circuit board in my Let's Split build, on account of the acrylic case I use. If I were to solder the shift register IC into the perfboard as normal, it wouldn't fit into the case at all. Once there's a piece of perfboard sitting flat on the bottom, there's only about 5mm between that and the underside of the keyboard PCB. That's not enough room to have solder joints sticking out of the bottom and to have a IC sticking out the top, so I need to find another solution.

... which turned out to be cutting a hole in the perfboard and bending the legs on the shift register out flat. It's hacky, but it does work!

splayed shift register

Additionally, to prevent the LEDs from blinding me, I'm running them each in series with a 9.7k ohm resistor. It makes them nice and dim, much like my brain.

All the power and signal pins are routed to a set of 5 header pins soldered to the side of the perfboard, which'll let me easily disassemble the keyboard once this new board is fitted.

The finished circuit board looked like this. I might replace it with a PCB eventually, but this has a certain charm to it that you don't get otherwise.

finished circuit board

The next thing to do is consider testing it (which I did, with a Raspberry Pi Pico), then wire it into the Let's Split. You should connect it to whichever side is your master side. In my case, that's the left half.

I connected SER to A2, RCLK to D5 and SRCLK to D4, and power and ground to their respective pins. Once it was all screwed together and plugged in, it was time to modify my QMK keymap.

Now, QMK doesn't have great documentation. In fact, I'd say QMK has quite bad documentation. It caused some delays.

Eventually, I found a GitHub issue that linked to a (working) documentation page that described functions that can be added to your keymap that are called on layer update, which could be used for indicator lights.

The additions to my keymap ended up looking like this:

 1#include "quantum.h"
 2
 3#define SR_LATCH C6 // ProMicro D5
 4#define SR_CLOCK D4 // ProMicro D4
 5#define SR_DATA F5  // ProMicro A5
 6
 7#define CLOCK_DELAY 10 // microseconds
 8#define STUPID_DELAY 100 // milliseconds
 9
10void shiftOut(uint8_t data) {
11    writePin(SR_LATCH, 0);
12
13    // only four bits on the output LEDs
14    for (uint8_t bit = 0; bit < 4; bit++) {
15        writePin(SR_DATA, (data & (1 << bit)) >> bit);
16
17        writePin(SR_CLOCK, 1);
18        wait_us(CLOCK_DELAY);
19
20        writePin(SR_CLOCK, 0);
21        wait_us(CLOCK_DELAY);
22    }
23
24    writePin(SR_LATCH, 1);
25}
26
27void matrix_init_user(void) {
28    // Init shift register pins and set output to be blank,
29    // otherwise it all lights up
30    setPinOutput(SR_LATCH);
31    setPinOutput(SR_CLOCK);
32    setPinOutput(SR_DATA);
33
34    shiftOut(0b1111);
35    wait_ms(STUPID_DELAY);
36    shiftOut(0b1110);
37    wait_ms(STUPID_DELAY);
38    shiftOut(0b1100);
39    wait_ms(STUPID_DELAY);
40    shiftOut(0b1000);
41    wait_ms(STUPID_DELAY);
42    shiftOut(0b0000);
43}
44
45layer_state_t layer_state_set_user(layer_state_t state) {
46    shiftOut(get_highest_layer(state));
47    return state;
48}

Since the shift register starts with all the outputs high, I also have a little logic to make a nice animation to clear them when initialising the keyboard. You can find my whole keymap here.

Reflash the keyboard, and it works! Even managed to get the code working first try, which I'm impressed about. My lack of C skills and the lack of good documentation for QMK didn't inspire confidence, but it didn't turn out to be too tricky, in the end.

in use

This is layer two, 0b0010.

closed up back

View from the rear, once the case is closed. The bottom of the shift register is touching the acrylic case.