QMK

Reducing firmware size in QMK

QMK is a very popular framework for custom keyboards. It has ample possibilities, providing hobbyists and professionals alike the ability to customize a lot more about keyboards than just the hardware. However, given any microcontroller, there’s only so much functionality you can fit on it.

So far, I’ve used the ATmega32U4 microcontroller in my builds. It’s the microcontroller that’s supplied with the Arduino Pro Micro, one of the more popular microcontroller chips for keyboard builds. On the datasheet for the ATmega32U4, you can find that it has 32K bytes of in-system self-programmable flash memory. This means that any program you want to flash to it can have a maximum size of 32KB. The bootloader also takes up some space, which is about 2KB.

It’s not easy to find how much memory you need in order to add any given feature, so with this article I intend to find the answer.

The setup

I have followed the Complete Newbs Guide To QMK in order to get a development environment running. Then, I followed the Getting Some Basic Firmware Set Up guide (part of the Hand Wiring guide) in order to make firmware with minimal configuration, in order to minimize the effects.

I added my own keymap by copying the /keymaps/default/ directory in the newly made project to /keymaps/test/, and added an empty rules.mk file that I’ll need for overriding keyboard settings with later on.

Throughout this post, I used Windows 10, using avr-gcc version 5.4.0. You may update the toolchain by running util/msys_install.sh. It won’t hurt to do so: newer avr-gcc versions may be able to further optimize the firmware size.

The target microprocessor is an ATmega32U4 with a Caterina bootloader.

Compiling this basic setup with make <project_name>, or in my case, make thomasbaart_test:test, results in the message The firmware size is fine - 21340/28672 (7332 bytes free). In other words, the most basic setup without any additional features costs a little over 21KB. Keep in mind, there are some features enabled by default, so this isn’t all that bad.

What features are enabled by default?

The documentation Configuring QMK states that each feature can have a default setting, possibily defined in one or more of four levels, from lowest to highest priority: QMK default, Keyboard, Folders (up to 5 levels deep) and the keymap.

This means that QMK provides for some default settings. If a hardware maker decides to provide a feature as default, he or she can do so in the keyboard folder. If a hardware maker has multiple revisions of a board, such as one with LEDs and one without, then the feature can be assigned a default in a folder within the keyboard folder. And finally, the end user has the final say by overriding those defaults. All of this happens in config.h and rules.mk files.

Let’s check out the files relevant for setting the defaults:

  • /keyboards/thomasbaart_test/: This is the folder that’s created with the command util/new_project.sh <project_name> I used earlier to create my test folder. It’s populated with files from
    /quantum/template/avr. The config.h and rules.mk files here provide the base defaults for this keyboard. The hardware maker will set up these defaults for you.
  • There can be other folders, such as 
    /keyboards/thomasbaart_test/revisions/rev1/. They’re not added by the default script, but can provide defaults depending on which revision of the keyboard you have. The hardware maker will set up these defaults for you.
  • /keyboards/thomasbaart_test/keymaps/default/: This subfolder is populated with the files from
    /quantum/template/base/keymaps/default/. It’s meant to be the starting point for users: you can copy the contents into your own keymap folder to add your own customizations and alter the defaults, ending up with…
  • /keyboards/thomasbaart_test/keymaps/test/: Your own keymap. I named it test, but you can choose any name you’d like.

When you add a config.h and a rules.mk file, any settings cascade onto the files below that, finally resulting in a specific configuration. When using the base keymap with the default settings from setting up the new keyboard, QMK provides the following default configuration:

Build optionEnabled
BOOTMAGIC_ENABLEno
MOUSEKEY_ENABLEyes
EXTRAKEY_ENABLEyes
CONSOLE_ENABLEyes
SLEEP_LED_ENABLEno
NKRO_ENABLEno
BACKLIGHT_ENABLEno
RGBLIGHT_ENABLEno
MIDI_ENABLEno
UNICODE_ENABLEno
BLUETOOTH_ENABLEno
AUDIO_ENABLEno
FAUXCLICKY_ENABLEno
HD44780_ENABLEno

It looks like only three features are enabled by default: mouse keys, extra keys and the console. You can find a complete list of features in the QMK documentation. It looks like all other features are disabled by default. Not all the features listed in the documentation have an impact on the firmware size, though they’re useful to know about.

In the issue Running out of space, anything I can delete to make more room? on GitHub, contributor Drashna suggests to disable two features and add some extra flags:

Add these flags to your rules.mk file:

EXTRAFLAGS += -flto

This enables Link Time Optimization, saving a significant amount of space. Because the Macro and Function features are incompatible with Link Time Optimization, disable those features in config.h:

#define NO_ACTION_MACRO
#define NO_ACTION_FUNCTION

Drashna, on QMK issue 3224, paraphrased

I added a rules.mk file to my keymap to add the extra flags, and modified the config.h with the suggestions above. Now let’s compile the barebones sample again. This time, the result says The firmware size is fine - 19536/28672 (9136 bytes free). This tip alone brought the size down from 21340 bytes to 19536 bytes, saving 1804 bytes, about 6% of the total flash size!

Another tip Drashna gave was to add the following code to config.h while you’re not debugging:

#ifndef NO_DEBUG
#define NO_DEBUG
#endif // !NO_DEBUG
#if !defined(NO_PRINT) && !defined(CONSOLE_ENABLE)
#define NO_PRINT
#endif // !NO_PRINT

Adding this brings the firmware to 19294 bytes, shaving off an additional 242 bytes. If you have already disabled the Console feature, you won’t need to add the above snippet since \tmk_core\common.mk will do it for you:

ifeq ($(strip $(CONSOLE_ENABLE)), yes)
    TMK_COMMON_DEFS += -DCONSOLE_ENABLE
else
    TMK_COMMON_DEFS += -DNO_PRINT
    TMK_COMMON_DEFS += -DNO_DEBUG
endif

Furthermore, it’s suggested to disable features you’re not using, like COMMAND_ENABLE or MOUSEKEY_ENABLE. The tip to define DISABLE_LEADER does not apply anymore, since the Leader key has been moved to a feature instead of being in the QMK core.

Memory cost per feature

There are more features that can be disabled, per the section Features That Can Be Disabled in the Configuring QMK documentation. In the table below, I’ll note how much space can be saved per disabled feature, and also how much space each enabled feature will cost.

The sizes provided are with link time optimization enabled and with debugging statements disabled (see above). Each time, only a single feature was enabled; all the defaults as noted above were also disabled, resulting in a base size of 12220 bytes (16452 bytes free).

The savings or costs from toggling features may not add up completely: some functionality is shared or depends on other features. The sizes are supposed to give an indication given an ATmega32U4 microcontroller with the Caterina bootloader and compilation with avr-gcc version 5.4.0, and thus may vary in your setup.

If a value is negative, toggling it with the statement in the “How to use” column will save space, if the value is positive, it’ll cost space.

FeatureHow to useSize in bytes
Layers#define NO_ACTION_LAYER-1368
Tapping features#define NO_ACTION_TAPPING-2186
One-shot modifiers#define NO_ACTION_ONESHOT-382
Macro handling#define NO_ACTION_MACRON/A (1)
Action function#define NO_ACTION_FUNCTIONN/A (1)
Permissive hold#define PERMISSIVE_HOLD104
Ignore Mod Tap Interrupt#define IGNORE_MOD_TAP_INTERRUPT-12
Tapping Force Hold#define TAPPING_FORCE_HOLD-54
Retro Tapping#define RETRO_TAPPING138
Audio and system controlEXTRAKEY_ENABLE470
Audio subsystemAUDIO_ENABLE4430
Audio subsystem, without music mode#define NO_MUSIC_MODE534
Audio subsystem, with clicky keys#define AUDIO_CLICKY5418
Auto ShiftAUTO_SHIFT_ENABLE1968
Auto Shift with Auto Shift ModifiersAUTO_SHIFT_MODIFIERS1960
BacklightBACKLIGHT_ENABLE920
Backlight with Backlight breathing#define BACKLIGHT_BREATHING1346
Legacy bluetoothBLUETOOTH_ENABLE-82
Bluetooth RN42BLUETOOTH = RN42-46
Bluetooth Adafruit EZKeyBLUETOOTH = AdafruitEZKey-58
Bluetooth Adafruit BLEBLUETOOTH = AdafruitBLE4154
BootmagicBOOTMAGIC_ENABLE = full666
BootmagicBOOTMAGIC_ENABLE = lite40
CombosCOMBO_ENABLE784
CommandCOMMAND_ENABLE408
ConsoleCONSOLE_ENABLE1152
Dynamic MacrosSee documentation548
EncoderENCODER_ENABLE156
Key LockKEY_LOCK_ENABLE2082
Leader KeyLEADER_ENABLE352
MIDI outputMIDI_ENABLE2068
MIDI output with basic MIDI#define MIDI_BASIC2822
MIDI output with advanced MIDI#define MIDI_ADVANCED3384
MIDI output with basic and advanced MIDISee above4184
Mouse keysMOUSEKEY_ENABLE1482
Pointing DevicePOINTING_DEVICE_ENABLE282
PS/2 Mouse BusywaitSee documentation1572
PS/2 Mouse InterruptSee documentation1478
PS/2 Mouse USARTSee documentation1420
RGB lightsRGBLIGHT_ENABLE1764
RGB animations (addition to RGB lights)#define RGBLIGHT_ANIMATIONS5608 (2)
RGB Matrix IS31FL3731RGB_MATRIX_ENABLE = IS31FL3731N/A (3)
RGB Matrix IS31FL3733RGB_MATRIX_ENABLE = IS31FL3733N/A (3)
Sleep LED (breathing during USB suspend)SLEEP_LED_ENABLE26
Split keyboards with dual MCUsSPLIT_KEYBOARD526
StenoSTENO_ENABLE1686
Swap-Hands actionSWAP_HANDS_ENABLE330
Tap DanceTAP_DANCE_ENABLE1060
TerminalTERMINAL_ENABLE4828
Thermal PrinterUndocumentedN/A (4)
Unicode (up to 0xFFFF)UNICODE_ENABLE546
Unicode (up to 0xFFFFFFFF)UNICODEMAP_ENABLE786
Unicode (up to 0xFFFFFFFF)UCIS_ENABLE908
USB N-Key RolloverNKRO_ENABLE390
USB startup checkNO_USB_STARTUP_CHECK-250
Wait for USBWAIT_FOR_USB0
  • (1): Flags are disabled because of incompatibility with link time optimization.
  • (2): The noted size is for all enabled RGBlight animations. You may enable individual animations instead, saving considerable space.
  • (3): I encountered compile time errors while implementing the samples that I wasn’t able to fix within a reasonable amount of time. Your mileage may vary.
  • (4): The thermal printer should be supported, but wasn’t documented. Finding out how it works is not within the scope of this post.

If you decide to use RGB Matrix functionality, the documentation lists some effects that can individually be disabled which may save space.

Additional tips

In an additional comment by Drashna, you may be able to save more space by flashing another bootloader to your microprocessor. The Teensy Halfkay bootloader and the Pro Micro’s Caterina bootloader seem to require a few workarounds by QMK, adding to the required firmware size.

Conclusion

There are numerous ways to save space, the easiest of which is to enable link time optimization. You might be able to toggle some features off when you don’t use them.

If you haven’t already, take some time to check the version of your compiler. Updating it may provide some quick-win space savings.