Dorsch 40k — low-profile, 40-key ortholinear, CircuitPython-based keyboard

Every year comes this time when I get a sudden urge to acquire a new keyboard. I used to raid electronics shops to try and find something not too horrible, then I switched to online shops. Fortunately by the time mechanical keyboards became really popular (and expensive) I learned that I can make my own keyboards, so now it’s a new build. This year I decided to do a minimal one, so as to not spend too much on the switches. In fact, I decided to try and see how small I can make it without going for multiple layers or crazy chord schemes.

Of course I started with the layout editor. Based on my previous experiments with the 5plit keyboard, I came up with something like this:

The keys on the bottom row have two functions, depending whether you press and release them (tap) or use them as modifiers for other keys (hold). It’s a bit similar to the Space Cadet parentheses on shifts, except it’s not based on timing, but only on what other keys you press.

Apart from being minimal, I also wanted it to appeal to my preferences, which include it being as flat as possible and without any bezel. I fired up Fritzing and quickly whipped up a PCB for it:

I decided to use a SAMD21 ARM Cortex M0 chip, because it requires a minimal number of extra components — literally just two capacitors and a voltage regulator. It also runs CircuitPython, which makes it much easier for me to add the extra logic I will need for the hold/tap mechanism. I’m familiar with it from previous projects too, which makes things much easier.

The schematic for all the connections is pretty simple:

Then I ordered the PCBs at the usual Chinese fab, and went to Kailh’s Aliexpress shop to order 40 white choc switches, and a set of white double-shot key caps for them. Fast-forward two weeks until everything arrived:

After soldering it all up and testing I discovered that I made a few mistakes in the PCB design. The diode for the bottom left key was shorted by a rogue trace, the D+ and D- USB pins were mis-labelled (but connected correctly to the USB socket, which I ended up not using), and I forgot to connect the LED’s ground to the rest of the ground. All that was fixed easily enough with a little bit of solder mask scraping and bodge wires. Then came the programming.

I flashed it with UF2 bootloader and a CircuitPython image, and wrote a simple 100-line Python program to scan the keys and send USB HID reports. All seemed to work fine, so I sat down and added a little bit more of state management for debouncing and for handling the hold/tap logic. In the process I discovered that I need to send the modifier keys as soon as the key is pressed, even if later on I end up deciding it was a tap and not a hold, so that I can shift-click things. That’s fine, because pressing and releasing modifier keys usually doesn’t do anything. After using the keyboard for two days and trying to learn to properly touch-type on it, I discovered that the Shift key is really badly places, so I came up with a modified layout, realizing that I can use the hold/tap mechanism on all keys, not just bottom row:

(Ultra is the layer switching key here.)

I’m using that keyboard for a few weeks now, and I’m pretty happy with it. It’s minimalism is especially good for forcing me to touch-type, since there is simply no room to move my hands, so they have to stay at the home row. I ended up not making use of the status LEDs at all (my CapsLock is remapped as Compose).

But of course there can be always improvement. Someone remarked on Twitter, that it would be great to be able to program colorful light effects in CircuitPython. So yeah, why not add some LEDs in there? I fired up Fritzing again, added a string of 40 APA102 smart LEDs in there, zig-zagging through all the keys, and ordered the PCB again. Two weeks later, it came, and I started assembling it, starting with the LEDs:

That turned out to be a very good decision, because it took me 4 hours to get all the LEDs to light up properly — turns out soldering tiny SMD footprints by hand is a bit tricky. But that worked, and this time I used transparent key caps to make it look a bit more cool:

(No, I don’t know why the last 6 LEDs are different color, I suspect quality problems.)

I still didn’t program any cool effects for it, but it works as a keyboard, and I can imagine it could also work as a nice macro pad with light indicators, or even a MIDI device (CircuitPython supports USB MIDI). I will probably be experimenting with it a little bit in the future, for now I don’t have the energy left for much programming.

The project is described at Dorsch 40k Keyboard | if you want some more technical details (and a 47-key Planck version that I also made).


Do you have the height with the keycaps on? I’m trying to think of a single keyboard that might be thinner, but none come to mind at the moment aside from maybe one of those split 40s with choc switches (might be thicker than this though?)

THIS is such a tight low profile build. I guess as long as you don’t use it on a metal surface you’re fine right?

Also major props for putting the USB header stuff on the top side of the board crammed among the switches. Looks like a PITA, but probably feels and looks awesome once its built up :sunglasses:

It’s exactly 14mm high, from the surface of the desk to the top of the keycap. I actually made a thinner keyboard previously, using the sunken low-profile switches, shaving off about 2mm more, but I made some design mistakes in the design and that keyboard is not as usable. Also, the sunken switches have really crappy feel.

I can use it on a metal surface without problems, because the bottom is covered by a sticker foam, something they use for “fingerboards”. So it really sits very stable on the desk.

I was thinking I could shave off the corners of the switches to fit an USB socket in there, but in the end I decided to simply solder a USB cable directly to the headers instead. Then I used some glue-soaked cloth ribbon to make a stress relief and attachment for the cable.


Is the code available on GitHub or elsewhere?

I did a macropad with a samd21 recently and my circuitpython is pretty terrible so far. It’d be cool to see some more robust code.

I did post my code at, but I plan to eventually make it a proper library at some point. The relevant logs are Code | Details | and Final Code | Details |

1 Like

Cool. Thanks for the direct links!

I’d scrolled they the hackaday stuff but was on my phone and didn’t see ALL the logs I guess.

Today I had some time energy to tackle the LEDs, and I managed to fix the last 6 of them, by replacing one of them with a new one. Must have been faulty, or something. I programmed a very simple static gradient, but I plan to do some nicer animations, like a water ripple effects, maybe?

I also put the black double-shot key caps on it, since the completely transparent ones don’t look that great with the backlight.


A couple of news. First, I changed the layout again, to bring it more in-line with the Planck-like layout I use on my main keyboard now:


I moved the arrow keys to a layer and added a second layer switching key. Also, the Fx keys, when pressed with that second key, work as Alt+Fx instead of Shift+Fx, as that is more common for them.

Second, I got some simple animations going:

And finally, I cleaned up the code a bit, added support for any number of layers and for media keys, and released the code as a library at GitHub - deshipu/ukeeb

There is no documentation yet, but I included the examples from my keyboards. The code is small enough to run on CircuitPython on SAMD21 without external flash.


Since Adafruit is now selling their own keypad kits, they improved keyboard support in the recent version of CircuitPython.
Starting with the upcoming version 7.0.0 there is a “keypad” module that does matrix scanning in the background, freeing our Python code to do other things, and making sure we never miss a key stroke. Using it with this keyboard allowed me to make the LED animations much smoother.
Next, there is the possibility of enabling and disabling USB devices at startup, which means I can now make the CIRCUITPY USB drive and the Python console available only when the upper left key of the keyboard was held down while it was being connected.
Finally, we can now define our own custom HID descriptors, which means we can do NKRO. For now I just made this keyboard use bitmap reports without a fallback for when the host doesn’t support it — I might add that later, but I don’t really need it myself.
Exciting times ahead.


Great to hear that !

1 Like