Programming Firmware for a Custom Mechanical Keyboard with QMK
A custom mechanical keyboard is one of the few DIY projects where the “final product” is something you type on every single day. The most interesting part for a coder isn’t soldering switches — it’s the firmware, where you turn a pile of physical keys into an input device with its own logic.
What QMK is
QMK (Quantum Mechanical Keyboard) is open-source firmware that runs on most microcontrollers used in DIY keyboards (AVR, ARM). Instead of each key sending exactly one character, QMK lets you define layers, macros (one key that sends out a whole sequence), and custom logic in C.
Anatomy of a keymap
A minimal keymap.c looks like this, defining two layers — a base layer and a function (Fn) layer:
#include QMK_KEYBOARD_H
enum layers { _BASE, _FN };
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
[_BASE] = LAYOUT(
KC_ESC, KC_Q, KC_W, KC_E, KC_R, KC_T,
KC_TAB, KC_A, KC_S, KC_D, KC_F, KC_G,
KC_LSFT, KC_Z, KC_X, KC_C, KC_V, KC_B,
KC_LCTL, KC_LGUI, KC_LALT, MO(_FN), KC_SPC
),
[_FN] = LAYOUT(
KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5,
KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6,
_______, KC_MPRV, KC_MPLY, KC_MNXT, _______, _______,
_______, _______, _______, _______, _______
),
};
MO(_FN) means “hold this key to switch to the Fn layer” — this is exactly how 40-60% keyboards still type numbers and F-keys despite missing an entire physical row.
Macros: one key, many actions
Want one key to type out a whole string or a complex key combo? Define a macro in process_record_user:
enum custom_keycodes { MY_EMAIL = SAFE_RANGE };
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
if (keycode == MY_EMAIL && record->event.pressed) {
SEND_STRING("hello@coderdiy.com");
return false;
}
return true;
}
Custom logic: context-aware keys
This is where a keyboard truly becomes “software running on hardware.” For example, a key that sends Cmd+Tab on macOS but Alt+Tab elsewhere, based on a flag you set at build time:
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
switch (keycode) {
case KC_APP_SWITCH:
if (record->event.pressed) {
#ifdef OS_MACOS
register_code(KC_LGUI);
tap_code(KC_TAB);
unregister_code(KC_LGUI);
#else
register_code(KC_LALT);
tap_code(KC_TAB);
unregister_code(KC_LALT);
#endif
}
return false;
}
return true;
}
Build and flash
qmk compile -kb your_keyboard -km your_keymap
qmk flash -kb your_keyboard -km your_keymap
Why it’s worth trying
Unlike most other DIY projects, a custom keyboard is something you interact with thousands of times a day — every macro and layer you write directly changes your own typing experience. It’s also a gentle way to get into embedded C programming, without the sensors or complex wiring of a typical IoT project.
Comments