How to build a keyboard with KMK and Adafruit KB2040
In this guide I will show you how to build a keyboard with KMK/CircuitPython as an alternative to the QMK firmware. The benefit is the ease of keymap update by copying or dropping your keymap onto the board appearing as a new drive. All you need is a compatible controller, e.g. the KB2040, and some free software.
What you'll need:
- CircuitPython compatible board/chip (e.g. Pi Pico, Adafruit KB2040 Keeboar)
- USB-C data(!) cable
- CircuitPython installer
- KMK boundle
- example code or your keyboard files (no panic, example provided)
Adafruit KB2040 Keeboar
You can use any compatible board. My first such board was the KB2040 Keeboar by Adafruit, a development board in the Pro Micro form factor – designed with keyboard builders in mind.
So in this write-up I'll install CircuitPython and KMK on a Keeboar like this:
KB2040 out of the box
The Keeboar comes with pin headers but without a cable so your first task is to grab a USB-C data cable.
To avoid confusion, make sure you use a tested DATA cable you know works well (and not something for charging only).
Connect the board to your PC/laptop with the cable: the green power-on light goes on + the RBG neopixel LED plays a rainbow animation (at least in my case – this may change with later batches).
The rainbow animation is generated by an Arduino example program on the board. This is not CircuitPython yet! You can't see a new drive appearing in Explorer or your favorite file manager.
Wait for Windows 10 to install/configure the new device: "PicoArduino". (A pop-up inticates this.)
(After waiting for some time a new serial USB device (COM6) appeared in Device Manager / Ports.)
Let's put CircuitPython on the board.
What is CircuitPython and how is it related to QMK?
The best comparison I found:
- CircuitPython lives on the board whereas
- Arduino or QMK are a set of tools on your computer that generate a binary file that then lives on the board.
Download the CircuitPython installer (a .UF2 file): https://circuitpython.org/board/adafruit_kb2040/
(The file is adafruit-circuitpython-adafruit_kb2040-en_US-7.1.0-beta.1.uf2 as of writing this guide.)
How to enter KB2040's bootloader mode?
To be able to put CircuitPython on your board you have to enter bootloader mode.
There are two buttons on the board: BOOT and RST (reset).
To enter bootloader mode on a KB2040 press & hold BOOT then press RST. (Then you can release both.)
The board reboots and a new driver appears in Device Manager or your file manager: RP2 BOOT / RPI-RP2 (E:) (the drive label may be different in your case).
You are in bootloader mode now.
Open Windows Explorer or your favorite file manager and copy/drag the CircuitPython .UF2 file to the RPI-RP2 drive. (This is the file you've just downloaded.)
Wait for CircuitPythin to be installed. A Win10 message will pop up indicating the OS is setting up KB2040.
In device manager a new device appears: KB2040 / Adafruit KB2040 USB Device
The easiest way to tell if CircuitPython was successfully installed on your board is to check the drive label:
Instead of RPI-RP2 (bootloader mode) the drive should be CIRCUITPY now.
1.) Download KMK: https://github.com/KMKfw/kmk_firmware/archive/refs/heads/master.zip
2.) Unzip and copy the KMK directory and the boot.py file to the root of CIRCUITPY (E: or similar).
Good job! KMK is installed.
(In fact, KMK is a bunch of CircuitPython code, an additional library to extend CircuitPython and simplify its keyboard handling.)
Testing KMK: Example code
To test KMK there is an example snippet on their page. ...which doesn't work with KB2040 due to differences in pin names.
Here is a working example of a 2x2 macropad matrix with updated pin references:
from kmk.kmk_keyboard import KMKKeyboard
from kmk.keys import KC
from kmk.scanners import DiodeOrientation
keyboard = KMKKeyboard()
keyboard.col_pins = (board.D0, board.D1)
keyboard.row_pins = (board.D2, board.D3)
keyboard.diode_orientation = DiodeOrientation.COL2ROW
keyboard.keymap = [
if __name__ == '__main__':
from kmk.kmk_keyboard import KMKKeyboard from kmk.keys import KC from kmk.scanners import DiodeOrientation
keyboard = KMKKeyboard()
keyboard.col_pins = (board.D0, board.D1) keyboard.row_pins = (board.D2, board.D3) keyboard.diode_orientation = DiodeOrientation.COL2ROW
keyboard.keymap = [ [ KC.A, KC.B, KC.C, KC.D, ] ]
if __name__ == '__main__': keyboard.go()
Copy this to your board (CIRCUITPY drive), into a code.py file.
The board restarts automatically.
Short a column and row pin with a piece of wire, paperclip, screwdriver, whatever. Or hook up a switch as I did it.
Shorting any column pin (TX=D0, RX=D1) with any row pin (D2, D3) should produce one of the characters defined in the keymap: A, B, C or D.
(If the neopixel LED flashes red, there's an error in your code.)
Your own keymap
Now that KMK is working, all you have to do is edit the code.py file, update the row and column pins and extend the keymap.
Alternatively, you can start with an existing keymap from KMK's boards folder.
As soon as you save/copy the file to CIRCUITPY drive, the board restarts and your keymap is updated. (No need to compile it and write the controller e.g. with AVRDUDE.)
That's it! Congratulations! You have a working keyboard controller with CircuitPython and KMK!
You can install the MU editor (recommended by Adafruit) for further tests. I personally found it huge, slow, and absolutely unnecessary – if you know what you're doing and until there are no errors in your code.
However, Mu will come in handy when something goes wrong. But keep in mind tthat this is optional and can make things even more confusing sometimes...
You can create a code.py file with the content: print("Hello, world!") And check the output in the serial monitor of MU.
The default blink example won't work, it's for a good old vanilla LED, not a neopixel RGB one.
To be able to play with the neopixel you have to download Download the latest Adafruit CircuitPython bundle from: https://circuitpython.org/libraries
Unzip and/or copy the neopixel.mpy and adafruit_pixel_framebuf into the lib directory on your board.
Create and copy a code.py file to the CIRCUITPY drive with the following code:
pixels = neopixel.NeoPixel(board.NEOPIXEL, 1)
pixels.fill((30, 0, 0))
pixels.fill((0, 0, 0))
pixels = neopixel.NeoPixel(board.NEOPIXEL, 1)
while True: pixels.fill((30, 0, 0)) time.sleep(0.1) pixels.fill((0, 0, 0)) time.sleep(2)
Why does the default KMK example code fail?
What's wrong with the general example program on the KMK page? It's for general purposes, and the KB2040 (board object) doesn't have GP0 or GP1 attributes – referenced in that snippet. You have to use pin names that actually exist in CircuitPython's board object.
Let's see how to list valid pin names of any CiruitPython compatible board!
How to list pin names of a CircuitPython board?
You can list the valid pin names of any CircuitPython board by entering and executing the "dir(board)" command in the serial monitor (e.g of MU).
This will return the elements in the "board" object.
(On this screenshot you can see the general GP0 and GP1 pins of the example code and why it won't work – those aren't valid pin names on a KB2040.)
Since CircuitPython "knows" what board it resides on, this is a valid list of pins.
Don't want to install MU? No problem. Here you go:
KB2040 pin names
To address the pins of the KB2040 board you use the Arduino pin names indicated on the silkscreen of the PCB. E.g. TX, D3, A1, etc.
Those starting with "A" can be analogue pins as well. Those indicated with numbers only are digital pins. Put a "D" before them to reference them in the code.
Here are the valid KB2040 pin names output by the "dir(board)" command (in alphabetical oder):
A0, A1, A2, A3, BUTTON, D0, D1, D10, D11, D12, D13, D2, D3, D4, D5, D6, D7, D8, D9, I2C, MISO, MOSI, NEOPIXEL, RX, SCK, SCL, SDA, SPI, TX, UART
And here are the relevant ones according to their position on the PCB:
A3, A2, A1, A0, SCK, MISO, MOSI, D10
TX=D0, RX=D1, D2, D3, D4, D5, D6, D7, D8, D9
Neopixel color codes (CircuitPython 7 or later)
The KB2040's neopixel LED helps you with debugging and diagnostics:
|rainbow animation||default Arduino example code on the board (factory state without CircuitPython installed)|
|a burst of yellow flashest||Booting. Starting up with CircuitPython code (and not e.g. Arduino which starts immediately). By pressing reset durint this 1000ms period you can enter Safe mode to try to fix an otherwise bricked KB2040.|
|two red flashes repeated every 5 sec||error in your code (code.py or aliases like code.txt, main.py, main.txt). Debug with MU or other tool.|
|green flash repeated every 5 sec||no code.py|
|continuous white||REPL mode (Read-Evaluate-Print-Loop). A serial communication mode e.g. in MU.|
|nothing (but the green power LED is on)||everything is fine|
If all these didn't help, try the troubleshooting page at Adafruit.
- KB2040 pinout:https://cdn-blog.adafruit.com/uploads/2021/11/pinlabels.png