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.

: This is the folder that’s created with the command I used earlier to create my test folder. It’s populated with files from . The and 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.

. 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…

: This subfolder is populated with the files from . 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 option Enabled BOOTMAGIC_ENABLE no MOUSEKEY_ENABLE yes EXTRAKEY_ENABLE yes CONSOLE_ENABLE yes SLEEP_LED_ENABLE no NKRO_ENABLE no BACKLIGHT_ENABLE no RGBLIGHT_ENABLE no MIDI_ENABLE no UNICODE_ENABLE no BLUETOOTH_ENABLE no AUDIO_ENABLE no FAUXCLICKY_ENABLE no HD44780_ENABLE no

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.

Link Time Optimization and disabling core functionality

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.

Feature How to use Size 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_MACRO N/A (1) Action function #define NO_ACTION_FUNCTION N/A (1) Permissive hold #define PERMISSIVE_HOLD 104 Ignore Mod Tap Interrupt #define IGNORE_MOD_TAP_INTERRUPT -12 Tapping Force Hold #define TAPPING_FORCE_HOLD -54 Retro Tapping #define RETRO_TAPPING 138 Audio and system control EXTRAKEY_ENABLE 470 Audio subsystem AUDIO_ENABLE 4430 Audio subsystem, without music mode #define NO_MUSIC_MODE 534 Audio subsystem, with clicky keys #define AUDIO_CLICKY 5418 Auto Shift AUTO_SHIFT_ENABLE 1968 Auto Shift with Auto Shift Modifiers AUTO_SHIFT_MODIFIERS 1960 Backlight BACKLIGHT_ENABLE 920 Backlight with Backlight breathing #define BACKLIGHT_BREATHING 1346 Legacy bluetooth BLUETOOTH_ENABLE -82 Bluetooth RN42 BLUETOOTH = RN42 -46 Bluetooth Adafruit EZKey BLUETOOTH = AdafruitEZKey -58 Bluetooth Adafruit BLE BLUETOOTH = AdafruitBLE 4154 Bootmagic BOOTMAGIC_ENABLE = full 666 Bootmagic BOOTMAGIC_ENABLE = lite 40 Combos COMBO_ENABLE 784 Command COMMAND_ENABLE 408 Console CONSOLE_ENABLE 1152 Dynamic Macros See documentation 548 Encoder ENCODER_ENABLE 156 Key Lock KEY_LOCK_ENABLE 2082 Leader Key LEADER_ENABLE 352 MIDI output MIDI_ENABLE 2068 MIDI output with basic MIDI #define MIDI_BASIC 2822 MIDI output with advanced MIDI #define MIDI_ADVANCED 3384 MIDI output with basic and advanced MIDI See above 4184 Mouse keys MOUSEKEY_ENABLE 1482 Pointing Device POINTING_DEVICE_ENABLE 282 PS/2 Mouse Busywait See documentation 1572 PS/2 Mouse Interrupt See documentation 1478 PS/2 Mouse USART See documentation 1420 RGB lights RGBLIGHT_ENABLE 1764 RGB animations (addition to RGB lights) #define RGBLIGHT_ANIMATIONS 5608 (2) RGB Matrix IS31FL3731 RGB_MATRIX_ENABLE = IS31FL3731 N/A (3) RGB Matrix IS31FL3733 RGB_MATRIX_ENABLE = IS31FL3733 N/A (3) Sleep LED (breathing during USB suspend) SLEEP_LED_ENABLE 26 Split keyboards with dual MCUs SPLIT_KEYBOARD 526 Steno STENO_ENABLE 1686 Swap-Hands action SWAP_HANDS_ENABLE 330 Tap Dance TAP_DANCE_ENABLE 1060 Terminal TERMINAL_ENABLE 4828 Thermal Printer Undocumented N/A (4) Unicode (up to 0xFFFF) UNICODE_ENABLE 546 Unicode (up to 0xFFFFFFFF) UNICODEMAP_ENABLE 786 Unicode (up to 0xFFFFFFFF) UCIS_ENABLE 908 USB N-Key Rollover NKRO_ENABLE 390 USB startup check NO_USB_STARTUP_CHECK -250 Wait for USB WAIT_FOR_USB 0

(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.