Ever since I saw the low-profile Kailh switches, I couldn't stop thinking about how thin a keyboard would be possible with them, and how it would feel. Now that I found out you can actually buy those switches in small quantities, I decided to try and design a keyboard with them.
In its simplest form, keyboard firmware is not rocket science. You basically need to do three things: scan the key matrix to see which keys are pressed, translate the matrix locations into key codes and modifiers, and send USB HID reports with lists of currently pressed keys. Easy.
So why is the KMK firmware so large? Well, it's written using an "enterprise" approach — everything is a class that has a hierarchy at least four levels deep, with abstract interfaces, pre- and post-event hooks, handlers, validators, layers of abstraction, flexibility and extensibility. Also RGB LEDs and Unicode emoji. Unfortunately, removing all the things I didn't need would require quite a bit of work, as despite having so many layers of abstraction, the code is actually pretty tangled. So instead I decided to just write the most naive code I could, and see if that works. I came up with this:
COLS = (board._C1, board._C2, board._C3, board._C4, board._C5, board._C6,
board._C7, board._C8, board._C9, board._C10, board._C11, board._C12,
board._C14, board._C13, board._C15)
ROWS = (board._R1, board._R2, board._R3, board._R4, board._R5)
def run(cols, rows, matrix):
report = bytearray(8)
report_mod_keys = memoryview(report)[0:1]
report_no_mod_keys = memoryview(report)[2:]
for device in usb_hid.devices:
if device.usage == 0x06 and device.usage_page == 0x01:
raise RuntimeError("no HID keyboard device")
cols = [digitalio.DigitalInOut(pin) for pin in cols]
rows = [digitalio.DigitalInOut(pin) for pin in rows]
for col in cols:
for row in rows:
last_state = bytearray(len(cols))
layer = 0while True:
changed = False
for x, col inenumerate(cols):
bits = 0for y, row inenumerate(rows):
bit = row.value << y
bits |= bit
if row.value != bool(last_state[x] & (1 << y)):
changed = True
code = matrix[layer][y][x]
if code == 0:
elif code == 135:
layer = int(row.value)
elif code > 127:
modifier = 1 << (code - 128)
report_mod_keys |= modifier
report_mod_keys &= ~modifier
for i, valueinenumerate(report_no_mod_keys):
report_no_mod_keys[i] = code
for i, valueinenumerate(report_no_mod_keys):
ifvaluein (matrix[y][x], matrix[y][x]):
report_no_mod_keys[i] = 0x00
col.value = 0
last_state[x] = bits
I finally started working on the firmware for this keyboard, starting with writing a board description and compiling CircuitPython for it. Unfortunately that enabled me to find a number of problem with the PCB.
First, I needed to figure out which pins go to which rows and columns, and let me say that the schematic view of Fritzing is not very helpful with that:
It just piled all the switches and diodes on top of each other, and I really don't have the energy to arrange them all properly. But no problem, we can use the PCB design view, and simply look where each trace goes. If we click the trace, it gets highlighted, together with all things that are connected to it, so that's not too bad. While doing that, I noticed a curious thing:
Apparently I wasted one of the pins, by connecting it directly to the backslash button, before the diode. This isn't a problem, since there are still enough pins (just barely), and there is another pin properly connected to the whole row after that diode, but it shows what my mental state was when I was designing it.
With the CircuitPython compiled and flashed, the next step is to try the KMK firmware. Unfortunately, it doesn't even fit on the 45KB flash disk my tiny SAMD21 has — not even compiled to byte-code. So I started trimming. Removed all the tap-dancing and leader-key code, all the debugging prints, all the RGB LED handling, Unicode keys, and so on. Finally I got it to a size that just barely fits, together with a layout definition. And of course I get MemoryError. Fine, I will write my own keyboard firmware in CircuitPython. With blackjack. And hookers.
The first step was to wite the matrix scanning code, of course. That went fast, but I noticed a weid quirk: whenever I pressed a key from row 3, the key from row 4 would also get pressed. And the other way around, pressing a key from row 4 would also press a key from row 3. Seems like there is a short between the two rows. I couldn't see any obvious problems, so I just touched up the QFN package a bit, to make sure there is no short underneath, but no dice. Took out my trusty ohmmeter, and noticed that the resistance is pretty much that of a solid trace — that doesn't look like some accidental short somewhere. To make sure, I took an unpopulated board, and checked there — sure enough, there is a short as well. That made me look closer at the traces, and sure enough:
The row 4 is dipping under the column traces here, but there is also a hard-to-see trace for the row 3 going right through it on the underside of the PCB. It's hard to see, because I rooted it along a silkscreen line, genius. Cutting that trace on both sides and connecting the two vias with a piece of wire solved this problem.
Next, Caps Lock and the A key seem to be the same key — no matter which one I press, I get the same matrix position. Looking closer, yes, of course I connected both to the same row and column. Fortunately, soldering the diode for Caps Lock up-side down, and then adding a wire leading to Left Ctrl on row 5 fixed the problem. It moved the key to row 5, where there is a free spot. Maybe the layout definition will look a bit confusing, but who cares.
Finally, five of the keys had the diodes soldered only on one side — re-touching them with the iron fixed it.
It all works now mechanically, even the Caps Lock led:
The keyboard is now pretty much assembled mechanically, now I "only need to program it" (haha). But I have learned a bit with this, and I can list things that I would have done differently if I was doing it again:
Standard key spacing. I made the keys denser to make the keyboard smaller, but that introduced a number of problems. The space turned out to be too big. The switches had to be soldered really carefully, to avoid the keys colliding. And also there is something in the muscle memory that expects the standard distances between keys — they keyboard takes a bit to get used to.
Tighter holes. I left generous tolerances both in the sizes of the soldering holes, and the holes that take the body of the switch — to be safe with varying manufacturing processes and my own inaccuracies. Turns out that makes it hard to solder all the switches straight.
Plated holes for the stabilizers. I didn't expect I will have to make my own stabilizers out of paperclips, but if I knew that, the holes for mounting the stabilizers would look completely different — the hooks would be soldered into the PCB.
Pads for USB cable shield. So that I can solder it to the PCB for more robust mechanical connection.
Maybe use a standard 32u4 chip, so that I don't have to write the firmware myself.
All in all, this might have gone much worse, so I'm quite happy about how it came out.
Since the regular plastic key stabilizers were simply too big, no matter how much I whittled them down with a knife, I had to come up with something completely different. The inspiration came from an older stabilizer design that I saw in #Alpen Clack, where the key just had a kind of horizontal slit in which the wire of the stabilizer would move. So first of all, I needed a way to hold the stabilizer wire:
That was easily achieved by a simple hook made of the same paperclip wire. Next, I needed that horizontal channel — I thought can simply use the surface of the key cap, and add something to hold it from the bottom. Gluing a bit of wire in the right place seems to work well enough for this.
Now just bend the stabilizing wire at the ends to hook into those channels, and voila, it works!
I repeated the same process for the shift and enter keys. Enter was a bit tricky, because there wasn't enough room for the channel — I had to cut out the "+" sockets for the "proper" stabilizers to make space. I still need to do this for the backspace key, however that will wait for a better USB cable — right now the temporary cable I have is taking up all the space under the key, actually making it difficult to press, but I already have a thinner one on order.
So of course I had to try and insert all the switches and keycaps to see how they fit. The fit is (intentionally) tight, as I made them a bit denser than on a normal keyboard, but they seem to all fit properly:
With one notable exception: the space bar. You see, since the caps were designed for a looser layout, the space includes a little more room for the gaps between keys, so it's longer than I anticipated. I now how two options: whittle it down a bit with a dremel to fit, or use the alternate, smaller, ALT keys from the keycap set:
For now I will go with the second option, as it's less destructive and can always be undone.
The first 50 switches have arrived, and I can finally compare them with my footprint and also see how they fit in the PCB. They are actually good 1.7mm lower than the "regular" kailh chocolate switches:
(Note that on this photo the switch slipped one step lower than it should be — it doesn't sink into the PCB that far, only as far as the part from witch the legs protrude.)
The footprints and the spacing both look correct, so I will be ordering the PCB in the coming days (I'm waiting for the new plate for #PewPew M4 to arrive, so that I can see if I will need a new PCB for it as well, then I can order them together and save on shipping.)
I decided to go with the stabilizers I currently have, and maybe cut them up a bit—we will see how that works. Having finally measured the key caps, and figured out where the stabilizers should go and whether the caps won't be too close together, all that is left to do is to actually design the PCB. Here it is:
As you can see, it's a tight fit. I will probably end up gluing the stabilizers to the board anyways (after cutting off the parts that go under the board and that normally hold them in place). I had to drop the USB port, and I will instead use a USB cable—there simply wasn't enough room for it at the top of the board, between the switches and the stabilizers. The 48-pin SAMD21 microcontroller is perfect—just the right number of pins for a 5×15 matrix, with an extra pin left for the NumLock LED (also re-used as a status LED).
I will let this design sit there for a while, as I'm sure I will find some errors in it. I might even get rid of all those right angles if I'm bored, but I decided to not care much for them.
The key caps have arrived, so I finally can do the measurements for the placement of stabilizers, figure out where the USB socket and the MCU should go, and maybe finalize the PCB. Turns out the 1.5u keys don't have stabilizers, so that leaves me with just the 5.5u space, 2.5u shift, and 2u enter and backspace keys. I can also test spacing between the keys, to make sure the caps don't collide.
(Sorry for the hair, it's the shedding season.)
I was actually quite surprised that there are many alternate key sizes for some of the keys — specifically the enter, shift, alt and fn keys. That gives me a little bit more flexibility.
The layout I have chosen has a number of 2u, 2.5u keys, and even the 5.5u space. Such keys require additional mechanical stabilization to not feel wobbly when you hit them off-center. That usually achieved with keyboard stabilizers. I have some stabilizers left over from my previous keyboard projects, and I was hoping to use those, but today I actually took them out of the drawer and looked at them next to the chocolate switches that I have, and now I have my doubts:
The outer case of the stabilizer is actually as high as the whole key in the not-pressed state. That is not going to work very well. So what could be done?
Perhaps I would be able to cut the stabilizer a little bit, to make it lower, and to make it fit in there. But I doubt it would be enough.
Of course Kailh has a solution for you: custom low-profile stabilizers that they sell at their store:
Looks great, but wait a minute. Don't they go into the PCB from the bottom, with a lot of space needed there for the moving parts? I don't think this is going to work with the PCB lying flat on the desk, as I was planning to do it.
And I can't really design the PCB before I know what kind of stabilizers I'm going to use, because I have to include the holes for the stabilizers in the design. I will need to do think about this some more, perhaps I will make a one-key PCB first as a test.
When considering the keyboard layout, one has to think about what keycaps are available on the market. Those low-profile switches use non-standard keycaps, so I'm a bit limited. A quick search of Aliexpress reveled that I have a huge choice of 1u caps, but as soon as I need any special keys, I'm limited to those ugly yellow flat caps, and even then I only have 1u, 1.5u, 2u and a spacebar of unspecified size:
This is a bit of a problem, because I wanted a relatively classic layout for this (having a more exotic split-keyboard layout project running already at #5plit Keyboard Clone). So I kept looking, and I found this:
It's a full 104-key cap set, with labels and everything, and at a reasonable price, so what not to like? Unfortunately if you look a bit closer, you will notice some strange things about this keyboard.
In particular, the "QWERTY" row of the keys. See anything weird?
Well, if you look at a standard keyboard, you will notice that this row is shifted from the previous row by 1/4 of the key width. Here it is shifted by 1/2. I initially thought that they did it because they didn't have 1.25u keys, but if you look closely, you will see that both Ctrl keys are 1.25u, so no. So why? It's a mystery. They also switched the ; and ' keys, by the way...
In any case, I don't want a *full* 104 layout, so I have some wiggle room here swapping keys around. Let's see if I can get something classical-looking but compact with those caps:
I dropped the numpad and the function keys row, since I've got used to a tenkeyless keyboard anyways, and the function keys are easily enough done with a function key. The arrow keys got squished into that humongous shift key, and the escape key bumped tilde over the tab to sit next to caps lock. The backslash will be replaced with a 1u key, to make room for shifting that row to its proper position. Unfortunately that creates some gaps, but I think I can live with that.