Putting custom firmware on the WASD CODE v2
I have a WASD CODE v2 tenkeyless keyboard, which has been my daily driver for work since about 2017. It’s a great keyboard… mechanically. But its control electronics are fixed-function and don’t quite work the way I want — to say nothing about fancy features like additional key layers.
So I fixed it. Now it runs QMK. (Or some bare-metal custom Rust firmware.) This has let me…
- Try out QMK without the expense (or waste!) of buying a whole new keyboard.
- Add features that were missing in the original, like USB n-key rollover (NKRO), 1 kHz polling, backlight fade effects, additional key layers, and key macros.
- Customize the part of the computer I interact with the most!
You can fix your keyboard, too:
- Buy reCODE from my Tindie store for folks in the US, or
- Buy reCODE from my Lectronz store for folks in the EU/UK.
(If you’re from the rest of the world and would like one, contact me! I have to set up each country separately, and I haven’t had time to do all two-hundred-and-some-odd.)
If it’s out of stock on either store, join the waitlist and I’ll make more.
The user manual is pretty basic, but I’ll flesh it out further if the boards sell!
Read on below for more background.
Born during quarantine
In spring of 2020 I decided I wanted to learn how USB worked, from the physical layer up. What better way to do it, I thought, than to make a keyboard from nothing!
But making a keyboard from scratch combines two different problems: laying out and building the mechanical bits of the keyboard, and writing the firmware. I decided to short-circuit the mechanical aspects by hacking an existing keyboard. I chose my WASD CODE v2B tenkeyless, mostly because it had cool DIP switches on the back.
You may recall that something else was happening in spring of 2020, so let’s just say I wound up with a lot of time to work on this.
It took me a few months, but I eventually got my Rust keyboard firmware to work, including low-power scanning of the key matrix at 1 kHz using DMA. I used the STM32L412, because it’s fairly similar to some other STM32 parts that I know well…and also it was one of the only microcontrollers I could actually buy at the time. (Turns out global supply chains are fragile!)
I released the board designs and firmware on Github as Keybrain. For the past four years, I’ve been using various versions of Keybrain in my main keyboard on a daily basis. It’s been rock solid, but in hindsight, I did several silly things in the design that I wanted to improve. Also, the firmware is very minimal and not at all configurable.
Enter QMK
QMK existed when I did the initial Keybrain work, but I didn’t use it, for the following reasons:
-
I was doing this to learn, which meant I specifically did not want a completed off-the-shelf solution.
-
It was only starting to be ported to ARM microcontrollers at the time, and STM32L4 support (if I recall correctly) was missing.
-
It’s C. Life is too short to deal with C for hobby projects.
Fast forward to 2024. I revisited QMK when I bought my Framework Laptop 16, because its built-in keyboard uses it. It’s come a long way! And in particular, it now has first-class support for STM32L412 microcontrollers.
QMK is very configurable, and is used by a lot more people (at least thousands) compared to Keybrain (two users). I figured an updated version of the hardware plus a nice QMK port might be useful.
Porting QMK to your keyboard for total n00bs
QMK, in general, has excellent documentation. However, there’s a lot of contradictory information on how to define a new keyboard, in part because the correct method has changed a few times recently.
As a total newcomer to the codebase, here is the key thing I had to learn the hard way:
Start by writing an info.json
. Many keyboards call this keyboard.json
;
either name will work and I’m not sure what the difference is. The secret that
isn’t explicitly called out in the docs: QMK will use this JSON file to
generate a bunch of C code and Makefiles for you. You can get a useful keyboard
up and running with no actual code written or rules.mk
files updated.
Here are the official docs on the info.json
format.
The code generation makes it particularly important to get the matrix definition in the JSON file correct, so the firmware can tell which key is which! If your keyboard, like mine, uses GPIO pins to scan its matrix and has diodes at each keyswitch, you need to have three things at minimum in the file:
matrix_pins
withcols
androws
entry listing the pins for columns/rows, by name (e.g."B3"
).diode_direction
indicating which way the diodes go. The WASD CODE has the diodes “backwards,” so I had to set this to"ROW2COL"
.- At least one layout in the
layouts
section, which needs to have correctmatrix
coordinates for each key.
From this, QMK will generate the matrix handling code, as well as the LAYOUT
macro used to define keymaps. You do not have to write this yourself. It took me
a while to figure this out, because the checked-in keyboards form something of a
geological history of different ways QMK has done things over the past five
years — they don’t update them all to the newest methods, so reading existing
keyboards is a bad way to learn.
I believe my port of QMK to reCODE is pretty minimal. I only had to write actual
C code for one thing, which is handling the DIP switches on the back of the
keyboard. (I could have avoided doing that, too, but I wanted the DIP switches
to exactly match the original behavior.) In particular, compared to most
tutorials (and even QMK’s own docs!), notice that I didn’t have to modify any of
the rules.mk
files. The QMK build system handled it, based on the contents of
the info.json
file.
Turning an LED into a tiny flamethrower
My WASD CODE v2 is a “tenkeyless” 87-key model, that is, it lacks the numeric keypad. That’s the version that I reverse-engineered to build reCODE.
I recently got my hands on a “full-size” 104-key model. I expected that the numeric keypad wouldn’t work — my firmware doesn’t know about it — but I wanted to see if everything else worked. So, I installed reCODE and plugged it into USB.
And it began smoking.
Turns out, while the two keyboard sizes have the same controller layout, they use slightly different circuits. A pin that goes to ground on the tenkeyless CODE is the cathode of the numlock LED on the full-size.
This means that reCODE connected that LED directly from +5V to ground, feeding it up to 500 milliamps very briefly. (It normally wants about 16 milliamps.) As the saying goes, “any diode can be a light-emitting diode once!”
This test keyboard no longer has a numlock LED, and I’ve learned my lesson. I am carefully revising reCODE to support the fullsize model. So if you have the larger keyboard, stay tuned.
Selling out
You could build reCODE yourself, if you wanted. An older version of the KiCad files is on my Github, and the firmware is open source.
If you choose to buy one from me instead:
-
I’ve taken care of the component sourcing. (You’d have a hard time making one for less than I’m charging, most likely.)
-
I’ve done all the fiddly surface-mount soldering. (You will still need to do some light through-hole soldering to put the pins on.)
-
I’ve tested that the board fundamentals work. There are no surprise short-circuits, the power regulation is good, the microcontroller runs code, etc.
-
I reinvest any money I get from selling electronics widgets into the next batch of electronics widgets. So each board sold on Tindie or Lectronz enables me to make more products. I have some cool ideas for upcoming products, but I can’t spend unlimited time on it, since I have a fairly demanding day job.
-
You get warm fuzzies for supporting independent hardware development! But seriously, I would love to someday make this my job, and every sale helps.
Whither Keybrain?
Believe it or not, I’m using QMK to write this blog entry, not my custom Rust firmware. It’s nice. So does that mean I’m giving up on my fragile hackish firmware?
Nope! I’m getting a lot of good ideas on how to improve it, actually. I’ll get back around to it one of these days….