Deep-Dive Series // 6 Steps

Building the MonkiiBoard39

A complete handwiring guide to building your own 39-key keyboard from scratch

Difficulty

Intermediate

Time

5–8 hours

Steps

6 stages

Every file for this build is up on GitHub, the plates, the case and the firmware

Our GitHub

Watch It Get Built

Prefer to see it happen? Here is the full walkthrough of this build and how the finished board actually works. Follow along with the written steps below.

Before You Build

Gather everything below before you start and the whole build goes a lot smoother. The printable parts are on GitHub, so you only need to buy the hardware.

No Case // The Sandwich

There is no case in this build. Our boards are a sandwich, a 3D printed top plate and a 3D printed bottom plate with the whole handwired matrix pressed in between, held together by screws at the corners. That is deliberate. Handwiring goes wrong in ways a beginner or a STEM maker cannot always predict, and a sealed case turns every loose joint into an afternoon of surgery. Undo the screws, lift the top plate, and the entire matrix is in front of you, ready to fix and bolt back together.

Component Manifest

Component Qty Specification
MX-compatible switches 39 Any MX switch works, 3-pin or 5-pin
Pro Micro ATmega32U4 1 Programmed via Arduino IDE
Hookup wire (24 AWG) ~5m Use multiple colors for rows vs columns
3D Printed Plates (STLs provided) 2 Top + bottom, PLA at 0.2mm layer height, 40% infill
Keycaps 39 Any MX-compatible set with enough 1u caps
M2 screws (6mm) 6 Clamp the two plates together
Soldering iron + solder 1 set Temperature-controlled iron recommended

Tools Required

01

Soldering iron (temp-controlled preferred)

02

Solder (63/37 or 60/40 rosin core)

03

Wire strippers

04

Flush cutters

05

Multimeter (continuity mode)

06

Tweezers

07

Computer with Arduino IDE installed

Build Steps

Work through these in order. Anything you need to download along the way, the plates, the case and the firmware, is already waiting for you on GitHub.

01

Step 1 of 6

Print the Plates

Both plate STLs are already on GitHub, so download them there and 3D print the pair in PLA. Use 0.2mm layer height and 40% infill, the plates need to be rigid. No supports required if printed flat. The top plate holds all 39 switches and forms the structural backbone of the keyboard; the bottom plate closes the sandwich once the matrix is wired.

Pro Tip

A textured PEI build plate gives the bottom a nice grip texture.

Print the Plates
Step 1, Print the Plates

02

Step 2 of 6

Mount Switches in the Plate

Snap all 39 switches into the plate. They should click in firmly with no wobble. Start with the four corners to lock the plate flat, then fill in the rest row by row. This is your last chance to finalize the layout before wiring begins.

Pro Tip

Check that no switch pins are bent before pressing down, because bent pins cause hard to diagnose matrix issues later.

Mount Switches in the Plate
Step 2, Mount Switches in the Plate

03

Step 3 of 6

Wire the Rows

The MonkiiBoard39 is a diodeless matrix, so there are no diodes to solder. Run a wire horizontally across each of the 4 rows, soldering it directly to one pin of every switch in that row. One continuous wire per row is fine, so strip a small window at each switch, solder, and continue. Keep the wires tidy and flat against the plate.

Pro Tip

Use one wire color for all rows and a different color for all columns, it keeps the matrix much easier to follow.

Wire the Rows
Step 3, Wire the Rows

04

Step 4 of 6

Wire the Columns

Run a wire vertically down each of the 10 columns, soldering to the other pin of each switch. Every switch now bridges one row wire and one column wire. Row wires and column wires must never touch each other directly, only ever through a switch.

Pro Tip

With no diodes, pressing 3+ keys that form a rectangle in the matrix can ghost. Normal typing is unaffected, just keep it in mind for heavy chording.

Wire the Columns
Step 4, Wire the Columns

05

Step 5 of 6

Connect the Matrix to the Pro Micro

Wire each row and column to a GPIO pin on the Pro Micro. The reference firmware uses rows on pins 2, 3, 4, 5 and columns on 6, 7, 8, 9, A1, A0, 15, 14, 16, 10. Write down every connection, because you will need this exact mapping in the Arduino sketch, or edit rowPins/colPins to match your own wiring.

Pro Tip

A simple sketch or spreadsheet of your pin mapping saves a lot of debugging time.

Connect the Matrix to the Pro Micro
Step 5, Connect the Matrix to the Pro Micro

06

Step 6 of 6

Flash Arduino Firmware & Test

The MonkiiBoard39 sketch is on GitHub, or printed in full further down this page. Open it in the Arduino IDE, make sure rowPins and colPins match your wiring, then upload to the Pro Micro (ATmega32U4). Open a keyboard tester and press every key, they should all register. Any silent key usually means a cold solder joint or a wire on the wrong pin. If you would rather lay out your own keys, the Keymap Editor will generate the sketch for you.

Pro Tip

In the firmware, rows are OUTPUTs and columns are INPUT_PULLUP, so if a whole row or column is dead, check that pin first.

Flash Arduino Firmware & Test
Step 6, Flash Arduino Firmware & Test

Firmware

The full Arduino sketch for this build. Open it in the Arduino IDE, match the pins to your wiring, and upload. There is no need to copy it off this page, the same sketch is sitting on GitHub with the rest of the repo.

MonkiiBoard39.ino
#include <Keyboard.h>

const int colPins[10] = {6, 7, 8, 9, A1, A0, 15, 14, 16, 10};
const int rowPins[4]  = {2, 3, 4, 5};

// ===== Key state + debounce =====
bool keyState[4][10] = {false};
unsigned long lastChangeTime[4][10] = {0};
const int debounceDelay = 30;

// ===== Sticky modifiers =====
bool ctrlActive = false;
bool shiftActive = false;
bool altActive = false;
bool guiActive = false;

// ===== Double-tap GUI =====
unsigned long lastGuiPressTime = 0;
const int doubleTapDelay = 300;

// ===== KEYMAP =====
uint8_t keymap[4][10] = {

  {'q','w','e','r','t','y','u','i','o','p'},
  {'a','s','d','f','g','h','j','k','l', KEY_BACKSPACE},
  {'z','x','c','v','b','n','m',',','.', KEY_RETURN},

  // Bottom row handled manually
  {0,0,0,0,0,' ',' ',0,0,0}
};

void setup() {

  for (int c = 0; c < 10; c++) {
    pinMode(colPins[c], INPUT_PULLUP);
  }

  for (int r = 0; r < 4; r++) {
    pinMode(rowPins[r], OUTPUT);
    digitalWrite(rowPins[r], HIGH);
  }

  Keyboard.begin();
}

void applyModifiers() {
  if (ctrlActive)  Keyboard.press(KEY_LEFT_CTRL);
  if (shiftActive) Keyboard.press(KEY_LEFT_SHIFT);
  if (altActive)   Keyboard.press(KEY_LEFT_ALT);
  if (guiActive)   Keyboard.press(KEY_LEFT_GUI);
}

void releaseModifiers() {
  if (ctrlActive)  Keyboard.release(KEY_LEFT_CTRL);
  if (shiftActive) Keyboard.release(KEY_LEFT_SHIFT);
  if (altActive)   Keyboard.release(KEY_LEFT_ALT);
  if (guiActive)   Keyboard.release(KEY_LEFT_GUI);
}

void clearModifiers() {
  ctrlActive = false;
  shiftActive = false;
  altActive = false;
  guiActive = false;
}

void loop() {

  for (int r = 0; r < 4; r++) {

    digitalWrite(rowPins[r], LOW);
    delayMicroseconds(30);

    for (int c = 0; c < 10; c++) {

      bool reading = (digitalRead(colPins[c]) == LOW);

      // ===== DEBOUNCE + STATE CHANGE =====
      if (reading != keyState[r][c] &&
          millis() - lastChangeTime[r][c] > debounceDelay) {

        lastChangeTime[r][c] = millis();
        keyState[r][c] = reading;

        // ===== KEY PRESSED =====
        if (reading) {

          // ----- Bottom row -----
          if (r == 3) {

            if (c == 0) ctrlActive = true;
            else if (c == 1) shiftActive = true;
            else if (c == 2) altActive = true;

            // ===== WINDOWS KEY WITH DOUBLE TAP =====
            else if (c == 3) {

              unsigned long now = millis();

              if (now - lastGuiPressTime < doubleTapDelay) {
                // double tap → open start menu
                Keyboard.press(KEY_LEFT_GUI);
                delay(50);
                Keyboard.release(KEY_LEFT_GUI);
                guiActive = false;
              } else {
                guiActive = true;
              }

              lastGuiPressTime = now;
            }

            // SPACE (shared)
            else if (c == 5 || c == 6) {
              Keyboard.press(' ');
            }

            continue;
          }

          // ----- Normal keys -----
          uint8_t key = keymap[r][c];

          if (key != 0) {
            applyModifiers();
            Keyboard.press(key);
          }
        }

        // ===== KEY RELEASED =====
        else {

          if (r == 3) {

            if (c == 5 || c == 6) {
              Keyboard.release(' ');
            }

            continue;
          }

          uint8_t key = keymap[r][c];

          if (key != 0) {
            Keyboard.release(key);
            releaseModifiers();
            clearModifiers();
          }
        }
      }
    }

    digitalWrite(rowPins[r], HIGH);
  }
}

Want a different keymap?

Lay out your own keys, layers and macros in the browser, then download a sketch already wired for this board. No editing C by hand.

Open Keymap Editor →

Everything is on GitHub

Every 3D printing file, every firmware sketch and every build guide we have written lives in one repository, and all of it is free to download and free to change however you like. The plate and case files are there, so is the Arduino code for each board, the wiring diagrams and the PCB designs.

View on GitHub

Join the Discussion

Stuck on a step, got a tip, or just finished your build? Drop it in our form. We read every single one and it helps us make the guide better.

Leave a Comment →