Flounder Keyboard

A low-profile mechanical keyboard

Similar projects worth following

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.


PCB design

x-fritzing-fzz - 222.04 kB - 04/24/2020 at 11:43


  • A Year Later

    deʃhipu09/16/2020 at 10:01 0 comments

    I must say I'm a bit disappointed with this project. I was expecting it to become my default keyboard, but somehow it never did. It just always felt ever so slightly wrong. Initially I thought it was the minimally tighter key spacing, or the improvised stabilizers, and I think they do contribute to the weirdness a little bit, but after building the #Dorsch 40k Keyboard I can confidently say the main reason for this keyboard being uncomfortable a the switches.

    Those sunken low-profile switches are very different from the regular (not sunken) chocolate switches — not only they are not nearly as clicky, but they also have this very annoying resistance — as if they didn't fit together very well.

    And since the switches are really the bulk of the keyboard's cost, I don't see much chance for rescuing this project.

  • Details

    deʃhipu12/17/2019 at 15:55 0 comments

    I finished up a couple of details recently, so that I can say now the project is finished.

    First, I added support for the CapsLock key to the firmware. It's still a hack for now, so the change is not merged upstream, but it works well enough for me, so I decided to keep it. I will work on getting that merged to CircuitPython with a proper API.

    Second, I replaced the USB cable with a flat one that just arrived. Flat keyboard — flat cable. It also doesn't get tangled as much.

    I also disabled all the USB endpoints except for HID keyboard, so that it's visible only as a regular keyboard when connected to a computer.

    Finally, I stuck some nice padding to the back of the keyboard. Those are sticky pads for something called "fingerboard", whatever that is. They work well enough. I had to do two layers in the middle, to prevent it from wobbling, because it's a bit thinner there, with no stabilizers in that area.

  • Firmware

    deʃhipu12/02/2019 at 23:23 0 comments

    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:

    import board
    import digitalio
    import usb_hid
    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 = 0
        while True:
            changed = False
            for x, col in enumerate(cols):
                col.value = 1
                bits = 0
                for y, row in enumerate(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)
                            if row.value:
                                report_mod_keys[0] |= modifier
                                report_mod_keys[0] &= ~modifier
                            if row.value:
                                for i, value in enumerate(report_no_mod_keys):
                                    if value == 0x00:
                                        report_no_mod_keys[i] = code
                                for i, value in enumerate(report_no_mod_keys):
                                    if value in (matrix[0][y][x], matrix[1][y][x]):
                                        report_no_mod_keys[i] = 0x00
                col.value = 0
                last_state[x] = bits
            if changed:

    Of course you need a file to run this:

    import flounder
    MATRIX = (
     (b'\x1e\x1f !"#$%&\'-.\x00*I',
    , flounder.ROWS, MATRIX)

    Yes, it's not very human-readable. The MATRIX actually defines the codes of all the keys in two layers. Of course I didn't write it like that. I generated it with this code:

    from micropython import const
    import pprint
    _A = const(4)
    _B = const(5)
    _C = const(6)
    _D = const(7)
    _E = const(8)
    _F = const(9)
    _G = const(10)
    _H = const(11)
    _I = const(12)
    _J = const(13)
    _K = const(14)
    _L = const(15)
    _M = const(16)
    _N = const(17)
    _O = const(18)
    _P = const(19)
    _Q = const(20)
    _R = const(21)
    _S = const(22)
    _T = const(23)
    _U = const(24)
    _V = const(25)
    _W = const(26)
    _X = const(27)
    _Y = const(28)
    _Z = const(29)
    _1 = const(30)
    _2 = const(31)
    _3 = const(32)
    _4 = const(33)
    _5 = const(34)
    _6 = const(35)
    _7 = const(36)
    _8 = const(37)
    _9 = const(38)
    _0 = const(39)
    _ENT = const(40) # Enter
    _ESC = const(41) # Esc
    _BS = const(42)  # Backspace
    _TAB = const(43)...
    Read more »

  • A Moment of Truth

    deʃhipu12/02/2019 at 23:05 3 comments

    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:

    Next time, more about the firmware.

  • Wouldhavebeens

    deʃhipu11/17/2019 at 09:04 0 comments

    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.

  • Paperclips!

    deʃhipu11/17/2019 at 07:01 0 comments

    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.

  • My Space is Too Big

    deʃhipu11/13/2019 at 08:55 0 comments

    The PCBs arrived today! They look great:

    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.

  • Low-profile Switches

    deʃhipu10/27/2019 at 21:33 0 comments

    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.)

  • PCB

    deʃhipu10/10/2019 at 17:42 0 comments

    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.

  • Caps

    deʃhipu10/10/2019 at 09:51 0 comments

    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.

View all 13 project logs

Enjoy this project?



Kosma wrote 04/27/2020 at 07:16 point

Can You publish layout generator?

  Are you sure? yes | no

deʃhipu wrote 04/27/2020 at 08:25 point

  Are you sure? yes | no

Kosma wrote 04/24/2020 at 16:47 point

What You think about this layout?

  Are you sure? yes | no

deʃhipu wrote 04/24/2020 at 18:03 point

Horrible. I much prefer having the accented characters available with a meta key or with compose.

  Are you sure? yes | no

Kosma wrote 04/27/2020 at 07:18 point

What You think about rotors for volume settings or 3d object manipulation?

  Are you sure? yes | no

deʃhipu wrote 04/27/2020 at 08:27 point

I love physical knobs, but there is one important thing: they need to be absolute. That is not possible with the USB HID, so it's pointless.

  Are you sure? yes | no

deʃhipu wrote 04/28/2020 at 17:58 point

Right, but all this is sending is either keypresses or mouse events, and that means you can send signal to "lower" "rise" a value, but you can't really set it to an absolute value, which is the whole point.

Unless you do something like setting it to 0 with one keypress, and then setting to an absolute value by sending a specific number of keypresses, every single time the knob is moved, which is not practical and may lead to weird artifacts.

  Are you sure? yes | no

Hans wrote 04/23/2020 at 23:09 point

Hi and sorry if this is a silly question, but when I have played with circuit design software (total newb) like KiCad I need to find and download the part definition so the footprint for the switches are correct.  Lotsa Cherry, even the Chocs, but did you just DIY for the 1232s? Or do you have a source for such a thing?

  Are you sure? yes | no

deʃhipu wrote 04/24/2020 at 11:45 point

Yes, I made the footprint myself — it's very easy in Fritzing. I have just attached my design file to files in this project, if you want to try it.

  Are you sure? yes | no

Hans wrote 04/24/2020 at 15:35 point


  Are you sure? yes | no

Morning.Star wrote 11/17/2019 at 12:17 point

I was very impressed with the 'plaice bar' mechanism ;-p

  Are you sure? yes | no

deʃhipu wrote 11/17/2019 at 15:06 point

I learned from the best.

  Are you sure? yes | no

Makerfabs wrote 10/11/2019 at 08:36 point  for reference...

  Are you sure? yes | no

deʃhipu wrote 10/11/2019 at 09:14 point

Yeah, no. That kind of keyboard is completely unusable to me, as it's too high and has too much key travel. I also wanted to go with a classical layout — I'm doing the fancy split one at #5plit Keyboard Clone.

  Are you sure? yes | no

Dan Maloney wrote 10/07/2019 at 16:18 point

Are you having the switch holes cut by the PCB fab? Or are you cutting them later with a laser cutter or something like that?

  Are you sure? yes | no

deʃhipu wrote 10/07/2019 at 16:44 point

They will be part of the gerber files, cut by the fab. I don't think you can use a laser cutter with FR4.

And yes, I'm putting holes in the corners.

  Are you sure? yes | no

Dan Maloney wrote 10/08/2019 at 15:47 point

Guess that begs the question of how they cut PCBs in the fab. CNC router perhaps? @mihirshah from Royal Circuits will be at Supercon, so maybe I'll ask him then.

  Are you sure? yes | no

deʃhipu wrote 10/08/2019 at 16:02 point

Yes, they use CNC routers, you can find videos on youtube of the PCB production process.

  Are you sure? yes | no

Bharbour wrote 11/13/2019 at 12:44 point

FR4 does not cut well at all with a laser cutter. We tried it on a prototype board, and it was a stinky mess.

  Are you sure? yes | no

Tillo wrote 11/17/2019 at 11:54 point

There's also PCB fabs that use lasers, at least for extremely small vias. But I guess that's a completely different game than those lasers at your local FabLab, probably more like the ones they use to cut metal.

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates