Skip to main content

How to Build a Hand Wired Split Ergonomic Keyboard with Per-Key RGB LEDs



Comments: counting...

A build guide for the Dactyl Minidox Split Ergo Keyboard with per-key RGB LEDs

Quick side note: check out my latest keyboard, The Quokka:

Parts List (*Affiliate links):

Amoeba Royale Per Key PCBs


Source: GitHub

Note: I learned after I finished this project about the Amoeba King PCB, which is a fork of the Royale that uses SK6812MINI-E LEDs that are much easier to solder, if I were to do this again I would use those instead.

Amoeba PCB Pinout


If you don’t have a 3D printer, there are many 3D printing services on the web, even JLCPCB offers it too.



Source: Thingiverse


Assemble PCBs

Each PCB needs a diode, LED, and hot swap socket. The diode’s stripe should face a away from the hot swap socket, and the LED’s long skinny pad should face toward the hot swap socket (see photos).

These LEDs are a bit tricky to solder to the board, so I have a a few tips:

  • Solder the LEDs before the diodes and hot swap sockets.
  • Line them up on the board from the back and push gently so they just hold themselves in place, then flip the PCB and apply pressure against a flat surface so they sit flush against the PCB, this gives them the best alignment with the solder points.
  • I developed a technique of continuing to feed solder over the gap for a split second after the iron is removed, this makes a bit of a blob, but it does the job.
  • In hindsight, I think a better method would be using some helping hands to hold a bit of bus wire (or a single strand from some standard wire) over the gap, and soldering that down, if you try this please let me know if it helps!

Some general tips for this part:

  • If you’ve never soldered SMD components before (hot swap sockets, possibly the diodes as well), apply some solder to one of the pads, then put the component in place and heat the pre-applied solder while pushing down on the component with a pair of tweezers (or your thumb, if you’re brave), then solder the other pad.
  • I used through-hole diodes because I had them on hand, but I honestly would have preferred to use SMD diodes, dealing with the legs of through-hole parts is a bit tedious, but this can be made easier with a lead bender if you have one.
Amoeba PCB soldered - back Amoeba PCB soldered - front

Assemble Housings

Orient the switches so that the pins would face toward you when in use, this way the LEDs are behind the switches and not blinding you, then press the hot swap sockets onto the switches from the back.

Dactyl housing with PCBs installed

The fit is pretty tight for the PCB on the innermost thumb switch, I used some course sand paper to take that edge down just a bit. Be careful not to take too much off the PCB, if you sand into the traces it will likely cause some issues later on.

Close up of PCBs installed in housing Switch not fully seated Sanding PCB edge down

This is how much I had to remove.

PCB sanded down to fit

Now it just barely fits, but the switch sits flat so we are all good.

PCB in housing Switch sitting flat in housing

Wire PCBs

We need to connect all the PCBs, and add leads to connect to the controller boards as per the diagram below.

BlueSwitch ColumnPCB marked C
PurpleSwitch RowPCB marked R
YellowLED DataLED Data out to LED data in
RedLED +Order doesn’t matter, just has to get to each PCB
GreenLED -Order doesn’t matter here either

Red and green are not on the diagram to make it easier to see the important paths, just make sure each PCB has a path for red and green all the way back to the first switch that has the leads coming out.

The blue and purple wires are the columns and rows of our switch matrix, I won’t get into how that works because there are plenty of great resources on that already, here is a good one from the QMK docs .

The yellow wires must start with the lead going to LED Data In, and continue in series from Data Out to Data In. Each LED in the chain reads the first few bits of data to get its color values and then sends the remaining data down the chain to the next LED, which will do the same.

This step is the same for both halves, but pay careful attention to the next step because wiring the Pro Mircros is different on each half.

PCB wiring diagram PCBs wired together

Wire Pro Micros

Alright, hard part is done, now we just need to wire up the controllers, I am using I2C for communication between the two halves, one could also use a serial connection if desired, it is slower but most say they can’t tell the difference.

I’m using two 1kΩ resistors on each half for the I2C pins, but you could only have the resistors on one half if desired, as long as the total resistance is between 2.2kΩ and 10kΩ, ideally 4.7kΩ, more info from the QMK docs .

The reset switch wiring should go from ground to the RESET pin, polarity does not matter.

The TRRS jack wiring order doesn’t much matter either, as long as both sides are wired the same way.

The columns and rows are inverted on the left hand, so please check the diagrams below closely!

Note: You should never plug or unplug the TRRS cable when the keyboard is plugged in to USB, since there is power running through the TRRS cable you could short out and ruin one or both of the Pro Micros!

Controller wiring diagram Key wiring diagram Contraller wired to keys Back of controller

Final Assembly

All that’s left to do is install the panel mount connectors, button up the bottom of the housings, and add some bump-ons so they don’t slide around.

I did have to widen the holes for the TRRS jacks and reset switches slightly, so be aware of that. Also, be careful not to over-tighten the panel mount connectors, they are plastic and will strip easily.

USB port installed USB port front TRRS jack installed Housing back installed


Alright, if you’ve never used QMK before, we need to get that set up on your computer, I recommend creating a fork of the QMK GitHub repository for you to add changes to, since we’ll have to add a new keyboard, here are the installation steps from QMK .

For reference, here is my copy of QMK firmware with this keyboard added in .

Navigate to your qmk_firmware directory and edit the file keyboards/handwired/dactyl_manuform/dactyl_manuform.h, we will add a definition for 3x5_3.h.

dactyl_manuform.h - Define a New Variant

#pragma once

#if defined(KEYBOARD_handwired_dactyl_manuform_4x5)
#    include "4x5.h"
#elif defined(KEYBOARD_handwired_dactyl_manuform_4x6)
#    include "4x6.h"
#elif defined(KEYBOARD_handwired_dactyl_manuform_5x6)
#    include "5x6.h"
#elif defined(KEYBOARD_handwired_dactyl_manuform_5x6_5)
#    include "5x6_5.h"
#elif defined(KEYBOARD_handwired_dactyl_manuform_5x6_2_5)
#    include "5x6_2_5.h"
#elif defined(KEYBOARD_handwired_dactyl_manuform_5x7)
#    include "5x7.h"
#elif defined(KEYBOARD_handwired_dactyl_manuform_6x6)
#    include "6x6.h"
#elif defined(KEYBOARD_handwired_dactyl_manuform_dmote_62key)
#    include "62key.h"
#elif defined(KEYBOARD_handwired_dactyl_manuform_3x5_3)
#    include "3x5_3.h"

#include "quantum.h"

Then we will add a new directory keyboards/handwired/dactyl_manuform/3x5_3, the rest of our work will be creating new files in this directory.

3x5_3.h - Define Key Matrix

#pragma once

#include "dactyl_manuform.h"
#include "quantum.h"

#ifdef USE_I2C
#include <stddef.h>
#ifdef __AVR__
  #include <avr/io.h>
  #include <avr/interrupt.h>

#define LAYOUT_split_3x5_3(\
  L00, L01, L02, L03, L04,                          R00, R01, R02, R03, R04, \
  L10, L11, L12, L13, L14,                          R10, R11, R12, R13, R14, \
  L20, L21, L22, L23, L24,                          R20, R21, R22, R23, R24, \
            L32, L33, L34,                          R30, R31, R32            \
  ) \
  { \
    { L00,   L01,   L02, L03, L04 }, \
    { L10,   L11,   L12, L13, L14 }, \
    { L20,   L21,   L22, L23, L24 }, \
    { KC_NO, KC_NO, L32, L33, L34 }, \
    { R00, R01, R02, R03,   R04   }, \
    { R10, R11, R12, R13,   R14   }, \
    { R20, R21, R22, R23,   R24   }, \
    { R30, R31, R32, KC_NO, KC_NO }, \

config.h Keyboard Configuration Options

The product ID and manufacturer settings define how the keyboard will present itself to your computer, not super important stuff here so feel free to change them.

We define here the key matrix size, Pro Micro pins used, diode direction, some LED details (I’ll explain the matrix center in more detail later on).

At the bottom you will see some undefines, this is because the dactyl_manuform parent configuration has a lot of options that I don’t want, or am not using.

#pragma once

#include "config_common.h"

#define PRODUCT_ID      0x3536
#define DEVICE_VER      0x0003
#define MANUFACTURER    DLFord
#define PRODUCT         Dactyl Minidox (3x5+3)

// Communication
#define USE_I2C

/* key matrix size */
// Rows are doubled-up
#define MATRIX_ROWS 8
#define MATRIX_COLS 5

// wiring of each half
#define MATRIX_COL_PINS { C6, D7, E6, B4, B5 }
#define MATRIX_ROW_PINS { B1, B3, B2, B6 }


// WS2812 RGB LED strip input and number of LEDs
#define RGB_DI_PIN D3
#define RGB_MATRIX_SPLIT { 18, 18 }
#define RGB_MATRIX_CENTER { 133, 54 }

// Remove upward config options
/* Set 0 if debouncing isn't needed */
  #undef DEBOUNCE

/* serial.c configuration for split keyboard */
  #undef USE_SERIAL

/* Mechanical locking support. Use KC_LCAP, KC_LNUM or KC_LSCR instead in keymap */
/* Locking resynchronize hack */

/* Enables This makes it easier for fast typists to use dual-function keys */
#undef PERMISSIVE_HOLD - Keyboard Rules

You can dig these up in the QMK documentation to see what they all do, but these can all be overridden later in your keymaps file, which we will get to.

# Build Options
#   change to "no" to disable the options, or define them in the Makefile in
#   the appropriate keymap folder that will get included automatically
BOOTMAGIC_ENABLE = no       # Virtual DIP switch configuration(+1000)
MOUSEKEY_ENABLE = yes       # Mouse keys(+4700)
EXTRAKEY_ENABLE = yes       # Audio control and System control(+450)
CONSOLE_ENABLE = no         # Console for debug(+400)
COMMAND_ENABLE = no         # Commands for debug and configuration
NKRO_ENABLE = no            # Nkey Rollover - if this doesn't work, see here:
BACKLIGHT_ENABLE = no       # Enable keyboard backlight functionality
MIDI_ENABLE = no            # MIDI controls
AUDIO_ENABLE = no           # Audio output on port C6
UNICODE_ENABLE = no         # Unicode
BLUETOOTH_ENABLE = no       # Enable Bluetooth with the Adafruit EZ-Key HID
RGBLIGHT_ENABLE = no        # Enable WS2812 RGB underlight.
RGB_MATRIX_ENABLE = yes     # Enable WS2812 RGB matrix

# Do not enable SLEEP_LED_ENABLE. it uses the same timer as BACKLIGHT_ENABLE
SLEEP_LED_ENABLE = no    # Breathing sleep LED during USB suspend

3x5_3.c - Setup RGB Matrix

This is where things get a little confusing, but stay with me and we’ll get through it! More information about the RGB Matrix can be found on, you guessed it, QMK's Documentation .

Key Matrix to LED Index

This is defining which LED corresponds to each key on the key matrix, each LED is defined by its position in the chain or “strip” of LEDs, starting at 0. When we get to the actual file, we will define the left half first, then the right.

I laid out the LED strip in the format of the key matrix in a comment in this file to help me keep track, it looks like this (the right half’s first LED starts at the number after the left half’s LEDs end).

17  12  11  06  05          23  24  29  30  35
16  13  10  07  04          22  25  28  31  34
15  14  09  08  03          21  26  27  32  33
            02  01  00  18  19  20

As you can see, this is the same path that we soldered the yellow wires on the keys, if they were soldered in a different path we would have to change that here to match.

LED Index to Physical Position

Here is where we define where each LED is in physical space, we will essentially cast the LEDs to a grid, and record their X,Y positions on that grid.

Looking at the layout from above, we can see there are 12 columns, and 4 rows (some cells don’t have an LED, that’s OK).

The formula for the columns/rows from QMK docs is:


Using that formula, we can see that our columns (X values) are:

20  41  61  81  102 122 143 163 183 204 224 244

And our rows (Y values) are:

21 43 64 85

So we can add that to our layout, and it looks like this:

Physical (Center: 133)
20  41  61  81  102 122 143 163 183 204 224 244
                                                 Physical (Center: 54)
17  12  11  06  05          23  24  29  30  35   21
16  13  10  07  04          22  25  28  31  34   43
15  14  09  08  03          21  26  27  32  33   64
            02  01  00  18  19  20               85

Now we can just start at LED 0 ({122,85}), then LED 1 ({102,85}) and so on.

To get the center, we will take our two numbers closest to the middle and find their median, so 122 and 143 for columns would be ((143 - 122) / 2) + 122 = 132.5, and round that up to 133.

LED Index to Flag

These flags give a bit more info on what each LED is for, check the QMK docs for more detail, but in our case these are all keylight LEDs, so they are all 4. These are again in the order of the LED strip.

Final 3x5_3.c

#include "3x5_3.h"


// LED Layout
// Columns
// 0   1   2   3   4   5   6   7   8   9   10  11
// Physical (Center: 133)
// 20  41  61  81  102 122 143 163 183 204 224 244
//                                                  Rows  Physical (Center: 54)
// 17  12  11  06  05          23  24  29  30  35   0     21
// 16  13  10  07  04          22  25  28  31  34   1     43
// 15  14  09  08  03          21  26  27  32  33   2     64
//             02  01  00  18  19  20               3     85

led_config_t g_led_config = { {
    // Key matrix to LED index
    // Left 1-18
            {17, 12, 11,  6,  5},
            {16, 13, 10,  7,  4},
            {15, 14,  9,  8,  3},
    {NO_LED, NO_LED,  2,  1,  0},
                                    // Right 1-18
                                    {23, 24, 29, 30, 35},
                                    {22, 25, 28, 31, 34},
                                    {21, 26, 27, 32, 33},
                                    {18, 19, 20, NO_LED, NO_LED},
}, {
    // LED index to physical position
    // Left 1-18
    // Right 1-18
}, {
    // LED index to flag
    // Left 1-18
    // Right 1-18
} };

void suspend_power_down_kb(void) {

void suspend_wakeup_init_kb(void) {

keymaps/yourusername/ - User Level Rules

Here is where you will customize your own rules for your keymap, these are all user level files (change yourusername to your github username, or whatever you want).

I would define these rules to start out, and add more as you go.

# MCU name
MCU = atmega32u4

# Bootloader selection
BOOTLOADER = caterina # For Pro Micro
# BOOTLOADER = qmk-dfu # For Elite-C


LAYOUTS = split_3x5_3

keymaps/yourusername/config.h - User Level Config

You will need to choose a method of determining which half of the keyboard is which, easiest is to use #define MASTER_LEFT or #define MASTER_RIGHT, whichever you choose will have to be the half you plug into your computer. I prefer to use EE_HANDS, which lets you plug either half in and still work correctly, but requires you to flash both sides with a special command to save some info in the EEPROM storage.

You’ll also need to define some settings for the RGB Matrix, and which animations to enable, I’ll include the full list here and you can uncomment the ones you want (there isn’t enough space for all of them, but do a web search for freeing up space in QMK and you’ll find some good methods).

#pragma once

// #define MASTER_RIGHT
// #define EE_HANDS

// #  define RGB_DISABLE_TIMEOUT 300000 // number of milliseconds to wait until disabling effects
// #  define RGB_DISABLE_WHEN_USB_SUSPENDED // turn off effects when suspended
#  define RGB_MATRIX_LED_PROCESS_LIMIT (DRIVER_LED_TOTAL + 4) / 5 // limits the number of LEDs to process in an animation per task run (increases keyboard responsiveness)
#  define RGB_MATRIX_LED_FLUSH_LIMIT 16 // limits in milliseconds how frequently an animation will update the LEDs. 16 (16ms) is equivalent to limiting to 60fps (increases keyboard responsiveness)
#  define RGB_MATRIX_MAXIMUM_BRIGHTNESS 150 // limits maximum brightness of LEDs to 150 out of 255. Higher may cause the controller to crash.
#  define RGB_MATRIX_SPD_STEP 10

// Enable animations
// #  define ENABLE_RGB_MATRIX_ALPHAS_MODS                 // Static dual hue speed is hue for secondary hue
// #  define ENABLE_RGB_MATRIX_GRADIENT_UP_DOWN            // Static gradient top to bottom speed controls how much gradient changes
// #  define ENABLE_RGB_MATRIX_GRADIENT_LEFT_RIGHT         // Static gradient left to right speed controls how much gradient changes
// #  define ENABLE_RGB_MATRIX_BREATHING                   // Single hue brightness cycling animation
// #  define ENABLE_RGB_MATRIX_BAND_SAT                    // Single hue band fading saturation scrolling left to right
// #  define ENABLE_RGB_MATRIX_BAND_VAL                    // Single hue band fading brightness scrolling left to right
// #  define ENABLE_RGB_MATRIX_BAND_PINWHEEL_SAT           // Single hue 3 blade spinning pinwheel fades saturation
// #  define ENABLE_RGB_MATRIX_BAND_PINWHEEL_VAL           // Single hue 3 blade spinning pinwheel fades brightness
// #  define ENABLE_RGB_MATRIX_BAND_SPIRAL_SAT             // Single hue spinning spiral fades saturation
// #  define ENABLE_RGB_MATRIX_BAND_SPIRAL_VAL             // Single hue spinning spiral fades brightness
// #  define ENABLE_RGB_MATRIX_CYCLE_ALL                   // Full keyboard solid hue cycling through full gradient
// #  define ENABLE_RGB_MATRIX_CYCLE_LEFT_RIGHT            // Full gradient scrolling left to right
// #  define ENABLE_RGB_MATRIX_CYCLE_UP_DOWN               // Full gradient scrolling top to bottom
// #  define ENABLE_RGB_MATRIX_CYCLE_OUT_IN                // Full gradient scrolling out to in
// #  define ENABLE_RGB_MATRIX_CYCLE_OUT_IN_DUAL           // Full dual gradients scrolling out to in
// #  define ENABLE_RGB_MATRIX_RAINBOW_MOVING_CHEVRON      // Full gradent Chevron shapped scrolling left to right
// #  define ENABLE_RGB_MATRIX_CYCLE_PINWHEEL              // Full gradient spinning pinwheel around center of keyboard
// #  define ENABLE_RGB_MATRIX_CYCLE_SPIRAL                      // Full gradient spinning spiral around center of keyboard
// #  define ENABLE_RGB_MATRIX_DUAL_BEACON                 // Full gradient spinning around center of keyboard
// #  define ENABLE_RGB_MATRIX_RAINBOW_BEACON              // Full tighter gradient spinning around center of keyboard
// #  define ENABLE_RGB_MATRIX_RAINBOW_PINWHEELS           // Full dual gradients spinning two halfs of keyboard
// #  define ENABLE_RGB_MATRIX_RAINDROPS                   // Randomly changes a single key's hue
// #  define ENABLE_RGB_MATRIX_JELLYBEAN_RAINDROPS         // Randomly changes a single key's hue and saturation
// #  define ENABLE_RGB_MATRIX_HUE_BREATHING               // Hue shifts up a slight ammount at the same time then shifts back
// #  define ENABLE_RGB_MATRIX_HUE_PENDULUM                // Hue shifts up a slight ammount in a wave to the right then back to the left
// #  define ENABLE_RGB_MATRIX_HUE_WAVE                          // Hue shifts up a slight ammount and then back down in a wave to the right
// #    define RGB_MATRIX_FRAMEBUFFER_EFFECTS // Required for the following two effects
// #  define ENABLE_RGB_MATRIX_TYPING_HEATMAP               // How hot is your WPM!
// #  define ENABLE_RGB_MATRIX_DIGITAL_RAIN                 // That famous computer simulation
// #    define RGB_MATRIX_KEYPRESSES // reacts to keypresses, required for the remaining effects
// #    define RGB_MATRIX_KEYRELEASES // reacts to keyreleases (instead of keypresses)
// #  define ENABLE_RGB_MATRIX_SOLID_REACTIVE_SIMPLE       // Pulses keys hit to hue & value then fades value out
// #  define ENABLE_RGB_MATRIX_SOLID_REACTIVE              // Static single hue pulses keys hit to shifted hue then fades to current hue
// #  define ENABLE_RGB_MATRIX_SOLID_REACTIVE_WIDE         // Hue & value pulse near a single key hit then fades value out
// #  define ENABLE_RGB_MATRIX_SOLID_REACTIVE_MULTIWIDE    // Hue & value pulse near multiple key hits then fades value out
// #  define ENABLE_RGB_MATRIX_SOLID_REACTIVE_CROSS        // Hue & value pulse the same column and row of a single key hit then fades value out
// #  define ENABLE_RGB_MATRIX_SOLID_REACTIVE_MULTICROSS   // Hue & value pulse the same column and row of multiple key hits then fades value out
// #  define ENABLE_RGB_MATRIX_SOLID_REACTIVE_NEXUS        // Hue & value pulse away on the same column and row of a single key hit then fades value out
// #  define ENABLE_RGB_MATRIX_SOLID_REACTIVE_MULTINEXUS   // Hue & value pulse away on the same column and row of multiple key hits then fades value out
// #  define ENABLE_RGB_MATRIX_SPLASH                      // Full gradient & value pulse away from a single key hit then fades value out
// #  define ENABLE_RGB_MATRIX_MULTISPLASH                 // Full gradient & value pulse away from multiple key hits then fades value out
// #  define ENABLE_RGB_MATRIX_SOLID_SPLASH                // Hue & value pulse away from a single key hit then fades value out
// #  define ENABLE_RGB_MATRIX_SOLID_MULTISPLASH           // Hue & value pulse away from multiple key hits then fades value out

keymaps/yourusername/keymap.c - Your Keymap

Now we can finally declare what each key does, I recommend you again take a look at the QMK Docs to learn about the different keycodes and functionalities like layers, mod-tap, etc.

The QMK Configurator is a handy tool to quickly lookup keycodes as well.

Here is a slightly simplified version of my current keymap:

Chrome on Android freezes when rendering large syntax highlighted code blocks, so I removed my keymap from this page, you can view it on GitHub instead

Flash your firmware

Now we need to flash both halves with our firmware, let’s first set the default keyboard and keymap in QMK.

qmk config user.keyboard=handwired/dactyl_manuform/3x5_3 qmk config user.keymap=yourusername

We can now run qmk flash with one half of they keyboard connected by USB, this should compile the firmware, then attempt to flash it. When you see a message like Waiting for USB serial port - reset your controller now (Ctrl+C to cancel)......, go ahead and hit the reset button on they keyboard (twice if using a Pro Micro, once for Elite-C which usually has DFU firmware). Then repeat for the other half.

If you are using EE_HANDS, you’ll need to flash the EEPROM of each half as well.

  • Pro Micro (left): ./util/ handwired/dactyl_manuform/3x5_3:yourusername:avrdude-split-left
  • Pro Micro (right): ./util/ handwired/dactyl_manuform/3x5_3:yourusername:avrdude-split-right
  • Elite-C (left): ./util/ handwired/dactyl_manuform/3x5_3:yourusername:dfu-split-left
  • Elite-C (right): ./util/ handwired/dactyl_manuform/3x5_3:yourusername:dfu-split-right

Note: Either of the two halves will be unresponsive if not connected to the other half by the TRRS cable, I haven’t figured out why this happens but it has something to do with using I2C instead of serial.

With a little luck, you’ll have something like this when you’re finished.

Finished keyboard - front view Finished keyboard - top view


  • Most of the compiler error codes are pretty helpful, but feel free to reach out if you get stuck!
  • If some of the LEDs aren’t working, start by checking out the last one in the chain that is working, might need to re-solder or replace that one, if none work it’s probably the first LED.
  • If you’re having trouble flashing, try changing the bootloader in your config to qmk-dfu or caterina, whichever you aren’t using now. On Linux you may need to alter udev rules, there is instructions for this on QMK Docs .