This is my personal userspace for QMK Firmware. It is setup as a self-contained folder that avoids placing keymap.c
source files deep inside QMK's sub-directories. All customisation required to build firmwares are configured within this space in the following manner:
- Store QMK Configurator exported layouts or wrapper based macro key map in JSON format.
- Create
rules.mk
,config.h
and shared source codes in this folder, with#ifdef
preprocessors for unique keyboard or feature specific functions. - Run
qmk flash
on JSON layout files to build a custom firmware for each board. - See my standalone userspace guide for more details.
Clone the QMK firmware, followed by this repository into users/filterpaper
:
git clone https://github.com/qmk/qmk_firmware qmk_firmware
git clone https://github.com/filterpaper/qmk_userspace qmk_firmware/users/filterpaper
- 3w6, Architeuthis Dux, Cradio — Minimalist choc split-keyboards.
- Corne (CRKBD) — OLED animation and graphical indicators.
- Technik — RGB Matrix and modifier indicators.
- The Mark: 65 — RGB under glow layer and modifier indicators.
File | Description |
---|---|
rules.mk | QMK compile rules and hardware feature selection |
config.h | QMK configuration variables and options, see configuring QMK |
combos.h | Wrapper macros for building combo source codes from combos.def |
combos.c | Place holder C file for combos.h macros |
filterpaper.h | User specific variables and options |
filterpaper.c | Main source with macro functions, see custom quantum functions |
layout.h | Key map macro wrapper for shared ortholinear and split layouts |
oled-icons.c | Graphical layer and modifier status indicators (adds ~4018 bytes) |
oled-luna.c | Luna and Felix the dog WPM animation and modifier indicators for primary OLED (adds ~6202 bytes) |
oled-bongocat.c | Bongocat animation using run-length encoded bytes |
oledfont.c | Corne logo, コルネ katakana name, fonts and icon images |
rgb-matrix.c | RGB matrix effect and custom codes, see RGB matrix lighting |
keymaps/ | Folder of supported keyboard keymaps |
animation_frames/ | Folder of Bongocat animation images |
archive/ | Archived files of original codes and layouts |
if (get_highest_layer(layer_state); > COLEMAK) {
uint8_t layer = get_highest_layer(layer_state);
for (uint8_t row = 0; row < MATRIX_ROWS; ++row) {
for (uint8_t col = 0; col < MATRIX_COLS; ++col) {
if (g_led_config.matrix_co[row][col] != NO_LED &&
keymap_key_to_keycode(layer, (keypos_t){col, row}) != KC_TRNS) {
rgb_matrix_set_color(g_led_config.matrix_co[row][col], RGB_LAYER);
}
}
}
}
Code loops through every row and column on a per-key RGB board, scanning for configured keys (not KC_TRANS
) and lighting that index location. It is configured to activate on non-default layers. This can be further customised using layer switch
condition inside the last if
statement.
#define W_TH LT(0, KC_W)
bool process_record_user(uint16_t keycode, keyrecord_t *record) {
switch (keycode) {
case W_TH:
// Unmatched return on tap
if (record->tap.count) { return true; }
// Send macro string on hold
else if (record->event.pressed) { SEND_STRING(":wq"); }
return false;
}
return true; // continue with unmatched keycodes
}
Tap hold shortcut can be found in QMK's tap dance feature but replicated here inside process_record_user()
with layer tap (LT()
) and tapping term delay. It uses less firmware space than TAP_DANCE_ENABLE
(~35 bytes per macro). Key macro W_TH
replaces KC_W
on the key map (keymap[]
). The if-else
statements are in a #define TAP_HOLD(_tap_, _hold_)
macro.
void process_caps_word(uint16_t keycode) {
// Get base key code of mod or layer tap with bitmask
switch (keycode) {
case QK_MOD_TAP...QK_MOD_TAP_MAX:
case QK_LAYER_TAP...QK_LAYER_TAP_MAX:
keycode &= 0xFF;
}
// Toggle caps lock with the following key codes
switch (keycode) {
case KC_ESC:
case KC_SPC:
case KC_ENT:
case KC_TAB:
case KC_DOT:
case KC_COMM:
case KC_GESC:
tap_code(KC_CAPS);
}
}
Function is called inside process_record_user
when caps lock is enabled to turn it off after completing a word—because caps lock is rarely used beyond capitalising one word. The first switch
statement performs a bitwise AND to filter base key codes (that ranges from 0x00-0xFF) from mod/layer taps to support toggle keys on a different layer. Conceived by the #ergonomics
enthusiasts of splitkb.com discord.
The QMK combo header file combos.h
is modified from Germ's helper macros to help simplify addition of combo shortcuts. New shortcuts can be appended to combos.def
and the preprocessor macros in combos.h
will generate required QMK combo source codes at compile time.
QMK will read "keyboard" and "keymap" values from the JSON file to build the firmware:
qmk flash ~/qmk_firmware/users/filterpaper/keymaps/technik.json
qmk flash ~/qmk_firmware/users/filterpaper/keymaps/corne.json
qmk flash ~/qmk_firmware/users/filterpaper/keymaps/mark65.json
All split keyboards are configured with EE_HANDS
for controllers to read left or right handedness from EEPROM, allowing USB-C cable to be used on either side. These are one-time flash suffix commands with -bl
to write left and right handedness:
qmk flash -kb cradio -km default -bl dfu-split-left
qmk flash -kb cradio -km default -bl dfu-split-right
Subsequently, the same firmware binary can be flashed normally to both sides. See split keyboard features for details.
Corne is configured with a few modular build options in rules.mk
:
Minimal firmware with no OLED and RGB support is the default:
qmk compile corne.json
The -OLED=
option will build support for pet animation on primary OLED with status icons on the secondary. Animation are key stroke driven by tap_timer
. To use WPM (at the expense of size), add -e WPM_ENABLE=yes
to the compile commands:
Build and flash each side with the corresponding options for left and right aligned Bongocat:
qmk compile -e OLED=LEFTCAT corne.json
qmk compile -e OLED=RIGHTCAT corne.json
Build for Luna (outline) or Felix (filled) the dog:
qmk compile -e OLED=LUNA corne.json
qmk compile -e OLED=FELIX corne.json
The -KB=
option will add support for RGB matrix lighting. IMK
value will use under glow LEDs as indicators:
qmk compile -e KB=LP corne.json
qmk compile -e KB=IMK corne.json
Combine -e OLED=
and -e KB=
options to support both features.
Images in glcdfont.c
can be viewed and edited with:
Text-based key map layout (in keymap.c
format) using JSON file is supported with the use of preprocessor wrapper macros. Create each layer as a C preprocessor macro, in a layout.h
file. Here is an example of a "number" layer for Corne:
#define _NUMB \
_______, _______, KC_1, KC_2, KC_3, _______, KC_HOME, KC_PGDN, KC_PGUP, KC_END, KC_DQUO, _______, \
_______, _______, KC_4, KC_5, KC_6, _______, KC_LEFT, KC_DOWN, KC_UP, KC_RGHT, KC_QUOT, _______, \
_______, _______, KC_7, KC_8, KC_9, KC_0, KC_INS, _______, _______, _______, _______, _______, \
_______, MO(FNC), _______, _______, _______, _______
Next, create a wrapper name in layout.h
that points to the actual layout used by the keyboard, example:
#define CORNE_wrapper(...) LAYOUT_split_3x6_3(__VA_ARGS__)
Finally create the keyboard's JSON file with macro names of each layer, together the layout wrapper name in the following format:
{
"author": "",
"documentation": "Wrapper based keymap",
"keyboard": "crkbd/rev1",
"keymap": "filterpaper",
"layers": [
[ "_BASE" ],
[ "_NUMB" ],
[ "_SYMB" ],
[ "_FUNC" ]
],
"layout": "CORNE_wrapper",
"notes": "",
"version": 1
}
Finally, add #include layout.h
into config.h
. The build process will construct a transient keymap.c
from JSON file that includes config.h
, and C preprocessor will use macros defined in layout.h
to expand them into the real layout structure in the compile process.
Home row mods feature can be placed over the layout macros. A home row mod macro is defined below following the keyboard's matrix layout (crkbd/rev1) with home letters wrapped by a mod-tap:
#define HRM(a) HRM_SACG(a)
#define HRM_SACG( \
k01, k02, k03, k04, k05, k06, k07, k08, k09, k10, k11, k12, \
k13, k14, k15, k16, k17, k18, k19, k20, k21, k22, k23, k24, \
k25, k26, k27, k28, k29, k30, k31, k32, k33, k34, k35, k36 \
k37, k38, k39, k40, k41, k42 \
) \
k01, k02, k03, k04, k05, k06, k07, k08, k09, k10, k11, k12, \
k13, SFT_T(k14), ALT_T(k15), CTL_T(k16), GUI_T(k17), k18, \
k19, GUI_T(k20), CTL_T(k21), ALT_T(k22), SFT_T(k23), k24, \
k25, k26, k27, k28, k29, k30, k31, k32, k33, k34, k35, k36, \
k37, k38, k39, k40, k41, k42
Next, wrap the layer that requires home-row mods with HRM()
in the JSON file, making it convenient to apply and change mods on multiple layouts:
"layers": [
[ "HRM(_BASE)" ],
[ "HRM(_COLE)" ],
[ "_NUMB" ],
[ "_SYMB" ],
[ "_FUNC" ]
],
Depending compatibility, layouts can be adapted with macros. Corne's split 3x6_3 (6-column, 3-thumb) can be reduced to a split 34-key 3x5_2 (5-column, 2-thumb) with a simple wrapper macro to exclude the outer column and thumb keys:
#define _34key_wrapper(...) LAYOUT(__VA_ARGS__)
// Corne to 34-key layout conversion
#define C_34(k) SPLIT_3x6_3_TO_3x5_2(k)
#define SPLIT_3x6_3_TO_3x5_2( \
k01, k02, k03, k04, k05, k06, k07, k08, k09, k10, k11, k12, \
k13, k14, k15, k16, k17, k18, k19, k20, k21, k22, k23, k24, \
k25, k26, k27, k28, k29, k30, k31, k32, k33, k34, k35, k36, \
k37, k38, k39, k40, k41, k42 \
) \
k02, k03, k04, k05, k06, k07, k08, k09, k10, k11, \
k14, k15, k16, k17, k18, k19, k20, k21, k22, k23, \
k26, k27, k28, k29, k30, k31, k32, k33, k34, k35, \
k38, k39, k40, k41
The JSON layout for 34-key Cradio keyboard uses the macro above to adapt 3x6_3 for 3x5_2:
{
"author": "",
"documentation": "Wrapper based keymap",
"keyboard": "Cradio",
"keymap": "filterpaper",
"layers": [
[ "C_34(HRM(_BASE))" ],
[ "C_34(HRM(_COLE))" ],
[ "C_34(_NUMB)" ],
[ "C_34(_SYMB)" ],
[ "C_34(_FUNC)" ]
],
"layout": "_34key_wrapper",
"notes": "",
"version": 1
}
- USBasp Programmer
- Breadboard
- Jumper wires
- Sockets and breadboard
- Pro-Micro controller
Connect the USBasp programmer to the controller in this manner:
USBasp RST <-> Promicro RST
USBasp SCLK <-> Promicro 15/B1 (SCLK)
USBasp MOSI <-> Promicro 16/B2 (MOSI)
USBasp MISO <-> Promicro 14/B3 (MISO)
USBasp VCC <-> Promicro VCC
USBasp GND <-> Promicro GND
See the QMK ISP Flashing Guide. Replace the Pro-Micro's default Caterina boot loader with Atmel-DFU using the following command and fuses argument:
avrdude -c usbasp -P usb -p atmega32u4 \
-U flash:w:bootloader_atmega32u4_1.0.0.hex:i \
-U lfuse:w:0x5E:m -U hfuse:w:0xD9:m -U efuse:w:0xC3:m
Clone @sigprof's nanoBoot fork, and run git checkout string-descriptors
followed by make
to build the updated boot loader. Replace the current boot loader with nanoBoot using the following command and fuses:
avrdude -c usbasp -P usb -p atmega32u4 \
-U flash:w:nanoBoot.hex:i \
-U lfuse:w:0xFF:m -U hfuse:w:0xD6:m -U efuse:w:0xC7:m
Use the following rules.mk
options for nanoBoot:
BOOTLOADER = qmk-hid
BOOTLOADER_SIZE = 512
- Seniply 34 key layout
- Callum-style mods
- Architeuthis dux PCB
- Hypergolic PCB
- Sockets
- Git Purr
- Pro-Micro C
- Mill-Max low profile sockets
- Mill-Max pins
- PJ320A audio jack
- TRRS cable
- Silicone bumper feet
- Kailh gchoc v1 switches