Building the MonkiiBoard39
This guide walks you through every step of building the MonkiiBoard39 — from printing the plate to flashing firmware. No PCB needed. You'll handwire a 39-key matrix directly to a Pro Micro ATmega32U4, solder everything by hand, and end up with a fully custom keyboard built entirely by you.
System Status
Nominal
Difficulty
Intermediate
Build Rev.
5–8 hours
Before You Build
Gather everything below before you start — it makes the whole build go smoothly.
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 Plate (STL provided) | 1 | PLA at 0.2mm layer height, 40% infill |
| Keycaps | 39 | Any MX-compatible set with enough 1u caps |
| M2 screws (6mm) | 6 | For case assembly |
| Soldering iron + solder | — | 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
01
Step 1 // MONKIIBOARD-39
Print the Plate
Download the plate STL from our GitHub and 3D print it in PLA. Use 0.2mm layer height and 40% infill — the plate needs to be rigid. No supports required if printed flat. The plate holds all 39 switches and forms the structural backbone of the keyboard.
Pro Tip
A textured PEI build plate gives the bottom a nice grip texture.
02
Step 2 // MONKIIBOARD-39
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 — bent pins cause hard-to-diagnose matrix issues later.
03
Step 3 // MONKIIBOARD-39
Wire the Rows
The MonkiiBoard39 is a diodeless matrix — 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: 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 easy to follow.
04
Step 4 // MONKIIBOARD-39
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 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.
05
Step 5 // MONKIIBOARD-39
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 — you'll 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.
06
Step 6 // MONKIIBOARD-39
Flash Arduino Firmware & Test
Open the MonkiiBoard39 firmware sketch 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 — all should register. Any silent key usually means a cold solder joint or a wire on the wrong pin.
Pro Tip
In the firmware, rows are OUTPUTs and columns are INPUT_PULLUP — if a whole row or column is dead, check that pin first.
Firmware
The full Arduino sketch for this build. Open it in the Arduino IDE, match the pins to your wiring, and upload — or grab the whole repo from GitHub.
#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);
}
}
Join the Discussion
Stuck on a step, got a tip, or finished your build? Leave us a comment through the form — we read every one and it helps us improve the guide.
Leave a Comment →