QMK Firmware
Over the years, I have owned a few mechanical keyboards. I’m quite fond of them. I’ve also built my own keyboard from scratch years ago. Hot-swappable back then was still in its easy stages and the sockets weren’t that good. Alas, we’re in 2021 and I’ve recently purchased the Keychron Q1 keyboard.
I’ve chosen this keyboard for many reasons, but the one you most care about is the topic that brought you here. It’s a QMK Firmware compatible keyboards. Do you know what that means ?
That means that we’re going to be digging into qmk_firmware
. Tag along !
Quantum Mechanical Keyboard Firmware
The QMK Firmware is
a keyboard firmware based on the tmk_keyboard firmware with some useful features for Atmel AVR and ARM controllers, and more specifically, the OLKB product line, the ErgoDox EZ keyboard, and the Clueboard product line.
It goes beyond saying, the QMK Firmware is open sourced. So let’s hack it.
Building QMK Firmware
The first step to flashing your keyboard starts here. We need to get the source
code of qmk_firmware
from Github.
$ git clone https://github.com/qmk/qmk_firmware.git
# Wait a while...
# Yup, I know !
# Okay finally...
Cloning into 'qmk_firmware'...
remote: Enumerating objects: 295442, done.
remote: Counting objects: 100% (34/34), done.
remote: Compressing objects: 100% (27/27), done.
remote: Total 295442 (delta 13), reused 17 (delta 5), pack-reused 295408
Receiving objects: 100% (295442/295442), 178.92 MiB | 7.10 MiB/s, done.
Resolving deltas: 100% (178414/178414), done.
Updating files: 100% (27916/27916), done.
$ cd qmk_firmware
Once the repository is clone, we can start with installing the dependencies to
build qmk
.
I’m not a big fan of auto-installers or installers scripts
(util/install/arch.sh
), and here’s why.
python3 -m pip install --user -r $QMK_FIRMWARE_DIR/requirements.txt
This is how the installer of the qmk_firmware
concludes the round. I would
hate to use pip to install willy nilly like that.
Otherwise, I don’t have objections to what it does on arch
at least.
It does the following, I see no reason not to follow it.
$ sudo pacman -S \
base-devel clang diffutils gcc git unzip wget zip python-pip \
avr-binutils arm-none-eabi-binutils arm-none-eabi-gcc \
arm-none-eabi-newlib avrdude dfu-programmer dfu-util
$ sudo pacman -U https://archive.archlinux.org/packages/a/avr-gcc/avr-gcc-8.3.0-1-x86_64.pkg.tar.xz
$ sudo pacman -S avr-libc # Must be installed after the above, or it will bring in the latest avr-gcc instead
$ sudo pacman -S hidapi # This will fail if the community repo isn't enabled
Now that all the dependencies required by the system are installed, let’s
install the python
dependencies.
$ git checkout 0.14.9 # Checkout the latest version
$ vf new qmk_firmware # Create a new python virtualenv and activate it
$ pip install -r requirements.txt # Install python requirements
$ pip install qmk
$ make git-submodule
Finally, we can build our keyboard firmware.
$ qmk compile -kb keychron/q1/rev_0100 -km default
Ψ Compiling keymap with make --jobs=1 keychron/q1/rev_0100:default [31/494]
QMK Firmware 0.14.16
Making keychron/q1/rev_0100 with keymap default
avr-gcc (GCC) 11.2.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Compiling: keyboards/keychron/q1/q1.c [OK]
Compiling: keyboards/keychron/q1/rev_0100/rev_0100.c [OK]
Compiling: keyboards/keychron/q1/rev_0100/keymaps/default/keymap.c [OK]
Compiling: quantum/quantum.c [OK]
Compiling: quantum/send_string.c [OK]
Compiling: quantum/bitwise.c [OK]
Compiling: quantum/led.c [OK]
Compiling: quantum/action.c [OK]
Compiling: quantum/action_layer.c [OK]
Compiling: quantum/action_macro.c [OK]
Compiling: quantum/action_tapping.c [OK]
Compiling: quantum/action_util.c [OK]
Compiling: quantum/eeconfig.c [OK]
Compiling: quantum/keyboard.c [OK]
Compiling: quantum/keymap_common.c [OK]
Compiling: quantum/keycode_config.c [OK]
Compiling: quantum/logging/debug.c [OK]
Compiling: quantum/logging/sendchar.c [OK]
Compiling: quantum/bootmagic/bootmagic_lite.c [OK]
Compiling: quantum/bootmagic/magic.c [OK]
Compiling: quantum/matrix_common.c [OK]
Compiling: quantum/matrix.c [OK]
Compiling: quantum/debounce/sym_defer_g.c [OK]
Compiling: quantum/main.c [OK]
Compiling: quantum/color.c [OK]
Compiling: quantum/rgb_matrix/rgb_matrix.c [OK]
Compiling: quantum/rgb_matrix/rgb_matrix_drivers.c [OK]
Compiling: lib/lib8tion/lib8tion.c [OK]
Compiling: drivers/led/issi/is31fl3733.c [OK]
Compiling: quantum/process_keycode/process_rgb.c [OK]
Compiling: quantum/led_tables.c [OK]
Compiling: quantum/dip_switch.c [OK]
Compiling: quantum/process_keycode/process_space_cadet.c [OK]
Compiling: quantum/process_keycode/process_magic.c [OK]
Compiling: quantum/process_keycode/process_grave_esc.c [OK]
Compiling: platforms/avr/drivers/i2c_master.c [OK]
Archiving: .build/obj_keychron_q1_rev_0100_default/i2c_master.o [OK]
Compiling: tmk_core/common/host.c [OK]
Compiling: tmk_core/common/report.c [OK]
Compiling: tmk_core/common/sync_timer.c [OK]
Compiling: tmk_core/common/usb_util.c [OK]
Compiling: tmk_core/common/avr/platform.c [OK]
Compiling: tmk_core/common/avr/suspend.c [OK]
Compiling: tmk_core/common/avr/timer.c [OK]
Compiling: tmk_core/common/avr/bootloader.c [OK]
Assembling: tmk_core/common/avr/xprintf.S [OK]
Compiling: tmk_core/common/avr/printf.c [OK]
Compiling: tmk_core/protocol/lufa/lufa.c [OK]
Compiling: tmk_core/protocol/usb_descriptor.c [OK]
Compiling: lib/lufa/LUFA/Drivers/USB/Class/Common/HIDParser.c [OK]
Compiling: lib/lufa/LUFA/Drivers/USB/Core/AVR8/Device_AVR8.c [OK]
Compiling: lib/lufa/LUFA/Drivers/USB/Core/AVR8/EndpointStream_AVR8.c [OK]
Compiling: lib/lufa/LUFA/Drivers/USB/Core/AVR8/Endpoint_AVR8.c [OK]
Compiling: lib/lufa/LUFA/Drivers/USB/Core/AVR8/Host_AVR8.c [OK]
Compiling: lib/lufa/LUFA/Drivers/USB/Core/AVR8/PipeStream_AVR8.c [OK]
Compiling: lib/lufa/LUFA/Drivers/USB/Core/AVR8/Pipe_AVR8.c [OK]
Compiling: lib/lufa/LUFA/Drivers/USB/Core/AVR8/USBController_AVR8.c [OK]
Compiling: lib/lufa/LUFA/Drivers/USB/Core/AVR8/USBInterrupt_AVR8.c [OK]
Compiling: lib/lufa/LUFA/Drivers/USB/Core/ConfigDescriptors.c [OK]
Compiling: lib/lufa/LUFA/Drivers/USB/Core/DeviceStandardReq.c [OK]
Compiling: lib/lufa/LUFA/Drivers/USB/Core/Events.c [OK]
Compiling: lib/lufa/LUFA/Drivers/USB/Core/HostStandardReq.c [OK]
Compiling: lib/lufa/LUFA/Drivers/USB/Core/USBTask.c [OK]
Compiling: tmk_core/protocol/lufa/usb_util.c [OK]
Linking: .build/keychron_q1_rev_0100_default.elf [OK]
Creating load file for flashing: .build/keychron_q1_rev_0100_default.hex [OK]
Copying keychron_q1_rev_0100_default.hex to qmk_firmware folder [OK]
Checking file size of keychron_q1_rev_0100_default.hex [OK]
* The firmware size is fine - 23302/28672 (81%, 5370 bytes free)
Look at tha, easy as pie ! You got yourself a compiled firmware.
Before we move on, let’s look at the command again and figure out what the hell I did, just in case you’re running a different keyboard.
If you look into the keyboards/
, you’ll be able to find a big list of
keyboards supported. The keychron/q1/rev_0100
is simply a directory in there
that matches my keyboard. Inside that directory, we can find the keymaps/
directory. This is where all the keymaps live. We chose the default
keymap
which is a directory in there as well.
Remapping the keyboard
At this stage, we were able to succesfully compile the keyboard firmware. But the whole point of this is to modify the layout of the keyboard so let’s go right ahead.
There are commands suggested on the QMK
docs but I didn’t go that far, I
simply copied the default
directory and went down to business. For the sake of
this blog post, I’ll assume I called the directory functions
.
The keymap.c
file looks as follows.
#include QMK_KEYBOARD_H
enum layers{
MAC_BASE,
MAC_FN,
WIN_BASE,
WIN_FN
};
#define KC_TASK LGUI(KC_TAB)
#define KC_FLXP LGUI(KC_E)
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
[MAC_BASE] = LAYOUT_ansi_82(
KC_ESC, KC_BRID, KC_BRIU, KC_F3, KC_F4, RGB_VAD, RGB_VAI, KC_MPRV, KC_MPLY, KC_MNXT, KC_MUTE, KC_VOLD, KC_VOLU, KC_DEL, KC_INS,
KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS, KC_EQL, KC_BSPC, KC_PGUP,
KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_LBRC, KC_RBRC, KC_BSLS, KC_PGDN,
KC_CAPS, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT, KC_ENT, KC_HOME,
KC_LSFT, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_RSFT, KC_UP,
KC_LCTL, KC_LALT, KC_LGUI, KC_SPC, KC_RGUI, MO(MAC_FN),KC_RCTL, KC_LEFT, KC_DOWN, KC_RGHT),
[MAC_FN] = LAYOUT_ansi_82(
KC_TRNS, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, KC_TRNS, KC_TRNS,
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
RGB_TOG, RGB_MOD, RGB_VAI, RGB_HUI, RGB_SAI, RGB_SPI, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
KC_TRNS, RGB_RMOD, RGB_VAD, RGB_HUD, RGB_SAD, RGB_SPD, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS),
[WIN_BASE] = LAYOUT_ansi_82(
KC_ESC, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, KC_DEL, KC_INS,
KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS, KC_EQL, KC_BSPC, KC_PGUP,
KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_LBRC, KC_RBRC, KC_BSLS, KC_PGDN,
KC_CAPS, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT, KC_ENT, KC_HOME,
KC_LSFT, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_RSFT, KC_UP,
KC_LCTL, KC_LGUI, KC_LALT, KC_SPC, KC_RALT, MO(WIN_FN),KC_RCTL, KC_LEFT, KC_DOWN, KC_RGHT),
[WIN_FN] = LAYOUT_ansi_82(
KC_TRNS, KC_BRID, KC_BRIU, KC_TASK, KC_FLXP, RGB_VAD, RGB_VAI, KC_MPRV, KC_MPLY, KC_MNXT, KC_MUTE, KC_VOLD, KC_VOLU, KC_TRNS, KC_TRNS,
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
RGB_TOG, RGB_MOD, RGB_VAI, RGB_HUI, RGB_SAI, RGB_SPI, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
KC_TRNS, RGB_RMOD, RGB_VAD, RGB_HUD, RGB_SAD, RGB_SPD, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS)
};
If you read this you will understand that the keyboard originally comes with 4
layers. Two for windows and two for Mac. The 0
and 1
layers are toggled
using a physical switch. The rest are toggled with the Fn
key.
Now let’s change the Mac layout to have the Function
keys to be on the main
layer while the media keys to be toggled with the Fn
key. The final version
should look like the following.
#include QMK_KEYBOARD_H
enum layers{
MAC_BASE,
MAC_FN,
WIN_BASE,
WIN_FN
};
#define KC_TASK LGUI(KC_TAB)
#define KC_FLXP LGUI(KC_E)
const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
[MAC_BASE] = LAYOUT_ansi_82(
KC_ESC, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, KC_TRNS, KC_TRNS,
KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS, KC_EQL, KC_BSPC, KC_PGUP,
KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_LBRC, KC_RBRC, KC_BSLS, KC_PGDN,
KC_CAPS, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT, KC_ENT, KC_HOME,
KC_LSFT, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_RSFT, KC_UP,
KC_LCTL, KC_LALT, KC_LGUI, KC_SPC, KC_RGUI, MO(MAC_FN),KC_RCTL, KC_LEFT, KC_DOWN, KC_RGHT),
[MAC_FN] = LAYOUT_ansi_82(
KC_TRNS, KC_BRID, KC_BRIU, KC_F3, KC_F4, RGB_VAD, RGB_VAI, KC_MPRV, KC_MPLY, KC_MNXT, KC_MUTE, KC_VOLD, KC_VOLU, KC_DEL, KC_INS,
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
RGB_TOG, RGB_MOD, RGB_VAI, RGB_HUI, RGB_SAI, RGB_SPI, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
KC_TRNS, RGB_RMOD, RGB_VAD, RGB_HUD, RGB_SAD, RGB_SPD, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS),
[WIN_BASE] = LAYOUT_ansi_82(
KC_ESC, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, KC_DEL, KC_INS,
KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS, KC_EQL, KC_BSPC, KC_PGUP,
KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_LBRC, KC_RBRC, KC_BSLS, KC_PGDN,
KC_CAPS, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT, KC_ENT, KC_HOME,
KC_LSFT, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_RSFT, KC_UP,
KC_LCTL, KC_LGUI, KC_LALT, KC_SPC, KC_RALT, MO(WIN_FN),KC_RCTL, KC_LEFT, KC_DOWN, KC_RGHT),
[WIN_FN] = LAYOUT_ansi_82(
KC_TRNS, KC_BRID, KC_BRIU, KC_TASK, KC_FLXP, RGB_VAD, RGB_VAI, KC_MPRV, KC_MPLY, KC_MNXT, KC_MUTE, KC_VOLD, KC_VOLU, KC_TRNS, KC_TRNS,
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
RGB_TOG, RGB_MOD, RGB_VAI, RGB_HUI, RGB_SAI, RGB_SPI, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
KC_TRNS, RGB_RMOD, RGB_VAD, RGB_HUD, RGB_SAD, RGB_SPD, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS,
KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS)
};
Now that that’s done, we need to compile to check that we didn’t forget anything.
$ qmk compile -kb keychron/q1/rev_0100 -km functions
We seem to have successfully compiled our now keyboard layout.
Flashing your keyboard
If you’re reached this stage, you’ll need to locate the reset
button on your
keyboard. Once located, follow your keyboard’s manual on how to reset the
board and getting ready it for flashing.
Once the keyboard is ready to be flashed, you basically change one thing in your previous command.
$ qmk flash -kb keychron/q1/rev_0100 -km functions
If this step succeeds, your keyboard should be ready to use in the newly configured layout. Check it out !
Conclusion
It’s pretty awesome to see keyboards like these hit the market. Whether you’re fan of the mechanical switches they come with or not, one thing is certain. You cannot deny the fact that they are very customisable. If you don’t like something with your keyboard, simply change it. The beauty of it all is that the firmware is open sourced. The community delivers, yet again !