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 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.
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
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 emptyrules.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 messageThe 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
andrules.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 commandutil/new_project.sh <project_name>
I used earlier to create my test folder. It’s populated with files from/quantum/template/avr
. Theconfig.h
andrules.mk
files here provide the base defaults for this keyboard. The hardware maker will set up these defaults for you./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 ittest
, but you can choose any name you’d like.When you add a
config.h
and arules.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.
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:
I added a
rules.mk
file to my keymap to add the extra flags, and modified theconfig.h
with the suggestions above. Now let’s compile the barebones sample again. This time, the result saysThe 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: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:Furthermore, it’s suggested to disable features you’re not using, like
COMMAND_ENABLE
orMOUSEKEY_ENABLE
. The tip to defineDISABLE_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.
#define NO_ACTION_LAYER
#define NO_ACTION_TAPPING
#define NO_ACTION_ONESHOT
#define NO_ACTION_MACRO
#define NO_ACTION_FUNCTION
#define PERMISSIVE_HOLD
#define IGNORE_MOD_TAP_INTERRUPT
#define TAPPING_FORCE_HOLD
#define RETRO_TAPPING
EXTRAKEY_ENABLE
AUDIO_ENABLE
#define NO_MUSIC_MODE
#define AUDIO_CLICKY
AUTO_SHIFT_ENABLE
AUTO_SHIFT_MODIFIERS
BACKLIGHT_ENABLE
#define BACKLIGHT_BREATHING
BLUETOOTH_ENABLE
BLUETOOTH = RN42
BLUETOOTH = AdafruitEZKey
BLUETOOTH = AdafruitBLE
BOOTMAGIC_ENABLE = full
BOOTMAGIC_ENABLE = lite
COMBO_ENABLE
COMMAND_ENABLE
CONSOLE_ENABLE
ENCODER_ENABLE
KEY_LOCK_ENABLE
LEADER_ENABLE
MIDI_ENABLE
#define MIDI_BASIC
#define MIDI_ADVANCED
MOUSEKEY_ENABLE
POINTING_DEVICE_ENABLE
RGBLIGHT_ENABLE
#define RGBLIGHT_ANIMATIONS
RGB_MATRIX_ENABLE = IS31FL3731
RGB_MATRIX_ENABLE = IS31FL3733
SLEEP_LED_ENABLE
SPLIT_KEYBOARD
STENO_ENABLE
SWAP_HANDS_ENABLE
TAP_DANCE_ENABLE
TERMINAL_ENABLE
UNICODE_ENABLE
UNICODEMAP_ENABLE
UCIS_ENABLE
NKRO_ENABLE
NO_USB_STARTUP_CHECK
WAIT_FOR_USB
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.
Share this post: