Instant Macropad: Just Add QMK

I recently picked up one of those cheap macropads (and wrote about it, of course). It is surprisingly handy and quite inexpensive. But I felt bad about buying it. Something like that should be easy to build yourself. People build keyboards all the time now, and with a small number of keys, you don’t even have to scan a matrix. Just use an I/O pin per switch.

The macropad had some wacky software on it that, luckily, people have replaced with open-source alternatives. But if I were going to roll my own, it would be smart to use something like QMK, just like a big keyboard. But that made me wonder, how much trouble it would be to set up QMK for a simple project. Spoiler: It was pretty easy.

The Hardware

Simple badge or prototype macropad? Why not both?

Since I just wanted to experiment, I was tempted to jam some switches in a breadboard along with a Raspberry Pi Pico. But then I remembered the “simple badge” project I had up on a nearby shelf. It is simplicity itself: an RP2040-Plus (you could just use a regular Pi Pico) and a small add-on board with a switch “joystick,” four buttons, and a small display. You don’t really need the Plus for this project since, unlike the badge, it doesn’t need a battery. The USB cable will power the device and carry keyboard (or even mouse) commands back to the computer.

Practical? No. But it would be easy enough to wire up any kind of switches you like. I didn’t use the display, so there would be no reason to wire one up if you were trying to make a useful copy of this project.

The Software

There are several keyboard firmware choices out there, but QMK is probably the most common. It supports the Pico, and it’s well supported. It is also modular, offering a wide range of features.

The first thing I did was clone the Git repository and start my own branch to work in. There are a number of source files, but you won’t need to do very much with most of them.

There is a directory called keyboards. Inside that are directories for different types of keyboards (generally, brands of keyboards). However, there’s also a directory called handwired for custom keyboards with a number of directories inside.

There is one particular directory of interest: onekey. This is sort of a “Hello World” for QMK firmware. Inside, there are directories for different CPUs, including the RP2040 I planned to use. There are many other choices, though, if you prefer something else.

Surprise!

Quick guide to the files of interest.

So, that directory probably has a mess of files in it, right? Not really. There are five files, including a readme, and that’s it. Of those, there are only two I was going to change: config.h and keyboard.json. In addition, there are a few files that may be important in the parent directory: config.h, onekey.c, and info.json.

I didn’t want to interfere with the stock options, so I created a directory at ~/qmk_firmware/keyboards/handwired/hackaday. I copied the files from onekey to this directory, along with the rp2040 and keymap directories (that one is important). I renamed onekey.c to hackaday.c.

It seems confusing at first, but maybe the diagram will help. This document will help, too. The good news is that most of these files you won’t even need to change. Essentially, info.json is for any processor, keyboard.json is for a specific processor, and keymap.json goes with a particular keymap.

Changes

The root directory config.h didn’t need any changes, although you can disable certain features here if you care. The hackaday.c file had some debugging options set to true, but since I wanted to keep it simple, I set them all to false.

The info.json file was the most interesting. You can do things like set the keyboard name and USB IDs there. I didn’t change the rest, even though the diode_direction key in this file won’t be used for this project. For that matter, the locking section is only needed if you have physical keys that actually lock, but I left it in since it doesn’t hurt anything.

In the rp2040 directory, there are more changes. The config.h file allows you to set pin numbers for various things, and I also put some mouse parameters there (more on that later). I didn’t actually use any of these things (SPI and the display), so I could have deleted most of this.

But the big change is in the keyboard.json file. Here you set the processor type. But the big thing is you set up keys and some feature flags. Usually, you describe how your keyboard rows and columns are configured, but this simple device just has direct connections. You still set up fake rows and columns. In this case, I elected to make two rows of five columns. The first row is the four buttons (and a dead position). The second row is the joystick buttons. You can see that in the matrix_pins section of the file.

The layouts section is very simple and gives a name to each key. I also set up some options to allow for fake mouse keys and media keys (mousekey and extrakey set to true). Here’s the file:

{
    "keyboard_name": "RP2040_Plus_Pad",
    "processor": "RP2040",
    "bootloader": "rp2040",
    "matrix_pins": {
        "direct": [
            ["GP15", "GP17", "GP19", "GP21", "NO_PIN"],
            ["GP2", "GP18", "GP16", "GP20", "GP3"]
        ]
    },
    "features": {
        "mousekey": true,
        "extrakey": true,
        "nkro": false, 
        "bootmagic": false
    },
    "layouts": {
        "LAYOUT": {
            "layout": [
                { "label":"K00", "matrix": [0, 0], "x": 0, "y": 0 },
                { "label": "K01", "matrix": [0, 1], "x": 1, "y": 0 },
                { "label": "K02", "matrix": [0, 2], "x": 2, "y": 0 },
                { "label": "K03", "matrix": [0, 3], "x": 3, "y": 0 },
                { "label": "K10", "matrix": [1, 0], "x": 0, "y": 1 },
                { "label": "K11", "matrix": [1, 1], "x": 1, "y": 1 },
                { "label": "K12", "matrix": [1, 2], "x": 2, "y": 1 },
                { "label": "K13", "matrix": [1, 3], "x": 3, "y": 1 },
                { "label": "K14", "matrix": [1, 4], "x": 4, "y": 1 }
            ]
        }
    }
}

The Keymap

It still seems like there is something missing. The keycodes that each key produces. That’s in the ../hackaday/keymaps/default directory. There’s a json file you don’t need to change and a C file:

#include QMK_KEYBOARD_H

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
    [0] = LAYOUT(
        // 4 buttons
        KC_KB_VOLUME_UP, KC_KB_MUTE, KC_KB_VOLUME_DOWN, KC_MEDIA_PLAY_PAUSE,
        // Mouse
        QK_MOUSE_CURSOR_UP, QK_MOUSE_CURSOR_DOWN, 
        QK_MOUSE_CURSOR_LEFT, QK_MOUSE_CURSOR_RIGHT,
        QK_MOUSE_BUTTON_1 
    ),
};
. . .

Mousing Around

I didn’t add the mouse commands until later. When I did, they didn’t seem to work. Of course, I had to enable the mouse commands, but it still wasn’t working. What bit me several times was that the QMK flash script (see below) doesn’t wait for the Pi Pico to finish downloading. So you sometimes think it’s done, but it isn’t. There are a few ways of solving that, as you’ll see.

Miscellaneous and Building

Installing QMK is simple, but varies depending on your computer type. The documentation is your friend. Meanwhile, I’ve left my fork of the official firmware for you. Be sure to switch to the rp2040 branch, or you won’t see any differences from the official repo.

There are some build options you can add to rules.mk files in the different directories. There are plenty of APIs built into QMK if you want to play with, say, the display. You can also add code to your keymap.c (among other places) to run code on startup, for example. You can find out more about what’s possible in the documentation. For example, if you wanted to try an OLED display, there are drivers ready to go.

The first time you flash, you’ll want to put your Pico in bootloader mode and then try this:

qmk flash -kb handwired/hackaday/rp2040 -km default

If you aren’t ready to flash, try the compile command. You can also use clean to wipe out all the binaries. The binaries wind up in qmk_firmware/.build.

Once the bootloader is installed the first time (assuming you didn’t change the setup), you can get back in bootloader mode by double-tapping the reset button. The onboard LED will light so you know it is in bootloader mode.

It is important to wait for the Pi to disconnect, or it may not finish programming. Adding a sync command to the end of your flash command isn’t a bad idea. Or just be patient and wait for the Pi to disconnect itself.

Usually, the device will reset and become a keyboard automatically. If not, reset it yourself or unplug it and plug it back in.  Then you’ll be able to use the four buttons to adjust the volume and mute your audio. The joystick fakes being a mouse. Don’t like that? Change it in keymap.c.

There’s a lot more, of course, but this will get you started. Keeping it all straight can be a bit confusing at first, but once you’ve done it once, you’ll see there’s not much you have to change. If you browse the documentation, you’ll see there’s plenty of support for different kinds of hardware.

What about debugging? Running some user code? I’ll save that for next time.

Now you can build your dream macropad or keyboard, or even use this to make fake keyboard devices that feed data from something other than user input. Just remember to drop us a note with your creations.

Read more from this series:
Instant Macropad

2 thoughts on “Instant Macropad: Just Add QMK

  1. I’d love to see a series or a detailed tutorial on QMK. I used it to build a replacement PCB for an old IBM M-122 to USB-ize it, and was very impressed at the power and flexiblity of QMK. The documentation, though…. not so much. A start-to-finish tutorial project for a brand new keyboard controller would have been immensely helpful.

Leave a Reply

Please be kind and respectful to help make the comments section excellent. (Comment Policy)

This site uses Akismet to reduce spam. Learn how your comment data is processed.