Skip to content

MacroBoard – Configuration


As a follow-up to the MacroBoard project I mentioned in the post MacroBoard – Design and Assembly, I will now explain the process of configuring and programming the keyboard and macros.

There are several well-known firmware options for DIY mechanical keyboards, including QMK, ZMK, and KMK. These firmwares run on 8-bit microcontrollers like the ATmega and 32-bit microcontrollers like the ARM. Most of the firmwares are written in C, C++, or Python. After considering the options and evaluating the installation and setup processes, I decided to use KMK Firmware. This firmware runs on CircuitPython, making it easy to set up and edit the code. It’s important to note that all XIAO boards are pin-compatible, so the MacroBoard can be used with any XIAO board that supports CircuitPython (such as the SAMD21, RP2040, or nRF52840).

To start, we flash CircuitPython’s firmware on the XIAO RP2040. The firmware can be found on CircuitPython‘s download page. I found the firmware for my board by searching for ‘Seeed Studio XIAO RP2040’, and the available version at the time was 7.3.3.

To flash the firmware, we simply press the BOOT button while connecting the XIAO board to the computer with a USB cable. The operating system will then detect a new mass storage unit called RPI-RP2. We copy the .uf2 firmware file to this storage unit, and after a few seconds the XIAO will restart and appear as a storage unit called CIRCUITPY.

Next, we go to KMK Firmware‘s GitHub repository and download it as a ZIP file. After decompressing the folder, we copy the kmk folder and the file into the CIRCUITPY unit.

Using a Python editor such as Thonny or Mu, we create a or file and save it in the CIRCUITPY unit. The file must be located in the same folder where we pasted the file from the KMK Firmware repository. In this file, we will configure the key mapping, operation layers, and LED animations.

Initially we import the libraries used by the keyboard, some libraries belong to CircuitPython, and others belong to the KMK firmware.

import board
from kmk.kmk_keyboard import KMKKeyboard
from kmk.keys import KC
from kmk.scanners import DiodeOrientation
from kmk.extensions.RGB import RGB, AnimationModes
from kmk.extensions.media_keys import MediaKeys
from kmk.modules.encoder import EncoderHandler

We create a KMKKeyboard object, this object represents the keyboard’s logic configuration. Then we select the XIAO pins for columns and rows in the key matrix. We also select the diode polarity between the columns and rows to avoid ‘ghost pulses’.

keyboard = KMKKeyboard()

# Basic matrix settings
keyboard.col_pins = (COL1, COL2, COL3)
keyboard.row_pins = (ROW1, ROW2, ROW3)
keyboard.diode_orientation = DiodeOrientation.COL2ROW

Then we map the functions to each key using the keymap attribute. The key mapping should be defined as a matrix following the physical arrangement of the keys on the keyboard.

# Keymap
keyboard.keymap = [

As the MacroBoard includes a rotary encoder, we can create an EncoderHandler object to assign the pin-out and the key mapping for the encoder. In the encoder_handler we assign the actions for the right rotation, left rotation and button activation.

# Encoder settings
encoder_handler = EncoderHandler()
encoder_handler.pins = ((ROTA, ROTB, PUSHBUTTON, False),) = (((KC.VOLD, KC.VOLU, KC.MUTE),),)

To control the RGB LEDs we create a RGB object, in the object we define the pins used to control the addressable LEDs, the amount of LEDs used, and other parameters like brightness, animation or animation speed. Then the object is added to the keyboard object. To control de RGB LEDs, we need CircuitPython’s NeoPixel library, after downloading the file we copy it into the CIRCUITPY unit, in the same folder were the is.

# RGB LEDs settings
rgb_ext = RGB( 
    pixel_pin = NEOPIXEL,
    num_pixels = 9,
    val_limit = 50,
    val_default = 50,
    animation_speed = 1,
    refresh_rate = 30,

It’s important to remember the pin-out and the function of each pin, as well as remember the columns and rows order.

At the end we add the condition to run the go method indefinitely if the program is running as the main program.

if __name__ == '__main__':

I also modified the file to avoid the XIAO enumeration as a mass storage device, as a virtual serial port or as a midi device when connected to the computer. Also added a parameter that allows the keyboard to be detected while the OS is starting. This kind of configuration implies that the keyboard can’t be configured directly when plugged to the computer using a CircuitPython editor, to enter the configuration mode or the development mode is necessary to keep pressed the encoder push button while the keyboard is being connected to the computer, after that the program and its parameters can be modified using Thonny o Mu.

# If the encoder push-button is held during boot, don't run the code which hides the storage and disables serial and midi
button = digitalio.DigitalInOut(board.D0)
button.direction = digitalio.Direction.INPUT
button.pull = digitalio.Pull.UP

if button.value:
    storage.disable_usb_drive() # Hides device storage
    usb_cdc.disable() # Disables serial comunication
    usb_midi.disable() # Disables midi
    usb_hid.enable(boot_device=1) # Enables keyboard before OS boot

The KMK firmware allow us to set up more functions, and the whole documentation is worth a look. I tested the keyboard on Windows 10 and on macOS Big Sur, and all the commands and functions work flawlessly. The complete configuration code can be found in the repository, and it has the commentaries to make clear each function.

The project files, including the schematic, 3D models, and firmware, can be found on the GitHub repository.

GitHub: MacroBoard

For more information:
Getting Started with KMK
Seeed XIAO RP2040
Seeed Studio XIAO RP2040 – Wiiki

If you have any questions, feel free to ask.

📢 This post is an entry for the Seeed Fusion DIY XIAO Mechanical Keyboard Contest and is partially sponsored by Seeed Studio.

Leave a Reply