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.
Table of Contents
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.
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
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
rules.mkfiles 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:
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
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
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.
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|
|Macro handling||N/A (1)|
|Action function||N/A (1)|
|Ignore Mod Tap Interrupt||-12|
|Tapping Force Hold||-54|
|Audio and system control||470|
|Audio subsystem, without music mode||534|
|Audio subsystem, with clicky keys||5418|
|Auto Shift with Auto Shift Modifiers||1960|
|Backlight with Backlight breathing||1346|
|Bluetooth Adafruit EZKey||-58|
|Bluetooth Adafruit BLE||4154|
|Dynamic Macros||See documentation||548|
|MIDI output with basic MIDI||2822|
|MIDI output with advanced MIDI||3384|
|MIDI output with basic and advanced MIDI||See above||4184|
|PS/2 Mouse Busywait||See documentation||1572|
|PS/2 Mouse Interrupt||See documentation||1478|
|PS/2 Mouse USART||See documentation||1420|
|RGB animations (addition to RGB lights)||5608 (2)|
|RGB Matrix IS31FL3731||N/A (3)|
|RGB Matrix IS31FL3733||N/A (3)|
|Sleep LED (breathing during USB suspend)||26|
|Split keyboards with dual MCUs||526|
|Thermal Printer||Undocumented||N/A (4)|
|Unicode (up to 0xFFFF)||546|
|Unicode (up to 0xFFFFFFFF)||786|
|Unicode (up to 0xFFFFFFFF)||908|
|USB N-Key Rollover||390|
|USB startup check||-250|
|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.
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.
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.