Ramblings of an aging IT geek
← Ramblings of an aging IT geek
hardware

i built a keyboard and then spent a week in the firmware

Building a small mechanical keyboard, where the soldering ends and the real project begins, and why the QMK firmware and its layers turned out to be the part that actually mattered.

Switches and a keyboard PCB laid out on a bench before assembly

I built a keyboard, which is the kind of thing that sounds like a hardware project and is actually a software one wearing a soldering iron as a disguise. The physical build, a compact 40% board, took an afternoon: solder a switch into each hole, seat the controller, screw the case together, test that every key registers. Satisfying, fiddly in places, but not where the interesting part lives. The interesting part started after the last joint cooled, when I plugged it in and the keyboard did, as designed, almost nothing useful.

A close-up of the keyboard PCB with switches half-soldered

A 40% board has roughly half the keys of a normal keyboard. There's no number row, no function row, no arrow cluster, no dedicated symbols. Those keys haven't gone away, they've moved into layers: hold a modifier and the whole board becomes a different keyboard. The hardware can't give you keys you don't have room for, so the firmware gives you the same keys multiple meanings instead. Which means the layout, the thing that decides what every key does and what each layer holds, is the actual product. The soldering just makes a grid of switches. QMK makes it a keyboard.

QMK is the open firmware that most of these boards run, and it compiles your layout into the controller from a C file. That sounds more frightening than it is. For simple things you edit a keymap, a set of arrays where each entry is one layer, and flash it:

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
    // base layer
    [0] = LAYOUT(
        KC_Q, KC_W, KC_E, KC_R, KC_T,   KC_Y, KC_U, KC_I,    KC_O,   KC_P,
        KC_A, KC_S, KC_D, KC_F, KC_G,   KC_H, KC_J, KC_K,    KC_L,   KC_SCLN,
        KC_Z, KC_X, KC_C, KC_V, KC_B,   KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH,
        KC_LCTL, KC_LGUI, MO(1), KC_SPC,   KC_SPC, MO(2), KC_LALT, KC_RCTL
    ),
    // lower: numbers and symbols
    [1] = LAYOUT(
        KC_1, KC_2, KC_3, KC_4, KC_5,   KC_6, KC_7, KC_8, KC_9, KC_0,
        // ... and so on
    ),
};

MO(1) is the magic: momentarily switch to layer 1 while held. That one mechanism is how a board with thirty-odd keys covers everything a full keyboard does. Hold one thumb key, the letters become numbers and symbols. Hold the other, they become arrows and navigation. Let go, you're back to letters. After the muscle memory lands it's genuinely faster than reaching for a number row, because your fingers never leave the home position.

I spent a week iterating on that file, far longer than I spent with the iron. Move a symbol, flash, type for a day, realise it's in the wrong place, move it again. QMK has a feature called tap-hold that I leaned on hard: a key that sends one thing when tapped and acts as a modifier when held, so my home-row keys double as Ctrl, Shift and the like. It's fiddly to tune the timing, but when it's right you stop noticing it, which is the whole goal.

The thing I'd tell anyone tempted by one of these builds: budget your time for the firmware, not the soldering. The hardware is an afternoon and a steady hand. The keyboard, the thing that actually fits your hands and your habits, is a layout you'll keep tweaking for weeks, and that's not a flaw in the project. That's the project.