When makers talk about putting a raspberry pi in keyboard projects, they are almost never talking about the credit-card-sized Linux single-board computers (SBCs) like the Pi 4 or Pi 5. A full SBC draws over 3 amps, requires a complex boot sequence, and runs an entire operating system just to poll a USB HID device. Instead, the industry standard for Pi-based keyboards is the Raspberry Pi Pico, powered by the RP2040 microcontroller. It draws roughly 50mA, boots instantly, and features programmable I/O (PIO) blocks that make matrix scanning incredibly efficient.
This guide walks through building a 4x12 ortholinear mechanical keyboard using the standard Raspberry Pi Pico (RP2040) running KMK firmware (a CircuitPython-based keyboard framework). We will cover the exact pinout, the Python code with hardware error handling, and how to debug the most common matrix failures.
Why the RP2040? Microcontroller Comparison
Before soldering, it helps to understand why the RP2040 has largely replaced the legacy ATmega32U4 in custom keyboard builds. The RP2040 offers dual cores, vastly more memory, and a lower price point, which allows for complex Python-based keymaps, OLED displays, and per-key RGB lighting without hitting RAM ceilings.
| Feature | Raspberry Pi Pico (RP2040) | Pro Micro (ATmega32U4) | STM32F401 (Blackpill) |
|---|---|---|---|
| Core / Architecture | Dual-core ARM Cortex-M0+ | Single-core AVR 8-bit | Single-core ARM Cortex-M4 |
| Clock Speed | 133 MHz (overclockable to 250+ MHz) | 16 MHz | 84 MHz |
| Flash / RAM | 2MB Flash / 264KB SRAM | 32KB Flash / 2.5KB SRAM | 512KB Flash / 96KB SRAM |
| Max Matrix (No Multiplexing) | ~26x26 (limited by GPIO count) | ~9x9 (limited by GPIO count) | ~15x15 |
| Firmware Ecosystem | KMK (Python), QMK (C), ZMK | QMK, VIA, VIAL | QMK, ZMK |
| Typical Board Price (2026) | $4.00 - $6.00 | $8.00 - $12.00 (clones) | $5.00 - $7.00 |
Note: The code in this guide specifically targets the standard Raspberry Pi Pico (RP2040) with the MicroPython/CircuitPython UF2 bootloader, not the Pico W (which dedicates specific GPIOs to the CYW43439 WiFi/Bluetooth chip).
Parts List and Pin Mapping
For this build, we are wiring a 48-key (4x12) ortholinear matrix. You will need to route rows and columns while avoiding the GPIOs reserved for the Pico's internal flash memory and USB bus.
Exact Parts List
- Microcontroller: Raspberry Pi Pico (Standard, RP2040, no headers pre-soldered)
- Switches: 48x Cherry MX or Gateron Pro 3.0 (3-pin or 5-pin)
- Diodes: 48x 1N4148 switching diodes (DO-35 glass package, not SMD unless using an SMD PCB)
- PCB: Generic 4x12 ortholinear hand-wiring plate or bare copper-clad board
- Wire: 22 AWG solid core copper for rows, 24 AWG stranded for Pico connections
- Keycaps: 48x 1U DSA or XDA profile
RP2040 Pin Mapping Table
The RP2040 has 26 usable GPIOs. We will use GP0-GP3 for rows (driven push-pull) and GP4-GP15 for columns (configured as inputs with internal pull-ups). This leaves GP16-GP28 free for I2C displays, rotary encoders, or split-keyboard TRRS communication.
| Matrix Role | GPIO Name | Physical Pin # | Electrical State (KMK) |
|---|---|---|---|
| Row 0 | GP0 | 1 | Output (Push-Pull) |
| Row 1 | GP1 | 2 | Output (Push-Pull) |
| Row 2 | GP2 | 4 | Output (Push-Pull) |
| Row 3 | GP3 | 5 | Output (Push-Pull) |
| Col 0 - 11 | GP4 to GP15 | 6 to 20 | Input (Pull-Up) |
Wiring the Matrix and Flashing Firmware
Hand-wiring a keyboard requires strict attention to diode polarity. If you reverse the diodes, the matrix will suffer from "ghosting" (registering phantom keypresses when multiple keys are held).
- Flash CircuitPython: Download the latest CircuitPython UF2 for the Pico from Adafruit's CircuitPython board list. Hold the BOOTSEL button on the Pico, plug it into USB, and drag the UF2 file onto the RPI-RP2 drive.
- Install KMK: Download the KMK firmware repository. Copy the
kmkfolder and theboot.pyfile directly to the root of theCIRCUITPYdrive that appears after flashing. - Solder Diodes: Solder the 1N4148 diodes to the switches. Critical: The black band on the diode must point away from the switch and toward the column wire (COL2ROW orientation).
- Wire Rows: Solder the row wires to the left pin of every switch in a horizontal line. Strip the insulation only at the exact solder points.
- Wire Columns: Solder the column wires to the right leg of the diodes in vertical lines.
- Connect to Pico: Solder 22 AWG flyout wires from your matrix rows/cols to the corresponding physical pins on the Raspberry Pi Pico. Use kapton tape to insulate the underside of the Pico before laying it on the metal plate.
In a COL2ROW matrix, the rows are driven low (0V) one at a time. The columns are read. If a key is pressed, the column is pulled low through the switch and diode. KMK handles the GPIO state transitions automatically, but understanding that the columns act as open-drain inputs with pull-ups helps when debugging stray voltage issues on long wire runs.
The Code: KMK Keymap with Error Handling
Create a file named main.py (or code.py) on the CIRCUITPY drive. This script initializes the board, defines the pins, and includes a try/except block to catch hardware initialization errors before the USB HID stack crashes.
import board
import sys
from kmk.kmk_keyboard import KMKKeyboard
from kmk.keys import KC
from kmk.scanners import DiodeOrientation
keyboard = KMKKeyboard()
try:
# Pin Definitions (Target: Raspberry Pi Pico RP2040 Standard)
keyboard.row_pins = (board.GP0, board.GP1, board.GP2, board.GP3)
keyboard.col_pins = (
board.GP4, board.GP5, board.GP6, board.GP7,
board.GP8, board.GP9, board.GP10, board.GP11,
board.GP12, board.GP13, board.GP14, board.GP15
)
# Diode direction: Black band points to columns
keyboard.diode_orientation = DiodeOrientation.COL2ROW
# 4x12 Ortholinear Keymap
keyboard.keymap = [
[
KC.ESC, KC.Q, KC.W, KC.E, KC.R, KC.T,
KC.Y, KC.U, KC.I, KC.O, KC.P, KC.BSPC,
KC.TAB, KC.A, KC.S, KC.D, KC.F, KC.G,
KC.H, KC.J, KC.K, KC.L, KC.SCLN, KC.ENT,
KC.LSFT, KC.Z, KC.X, KC.C, KC.V, KC.B,
KC.N, KC.M, KC.COMM, KC.DOT, KC.SLSH, KC.RSFT,
KC.LCTL, KC.LGUI, KC.LALT, KC.SPC, KC.SPC, KC.SPC,
KC.SPC, KC.SPC, KC.SPC, KC.RALT, KC.RGUI, KC.RCTL
]
]
except ValueError as e:
# Catches pin conflicts (e.g., trying to use a pin reserved for USB)
print(f"Hardware Init Error: {e}. Check for pin conflicts.")
sys.exit(1)
except Exception as e:
print(f"Unexpected KMK Error: {e}")
sys.exit(1)
if __name__ == '__main__':
keyboard.go()
Debugging: First Three Things to Check When It Fails
When a custom matrix fails, the issue is almost always physical wiring or a CircuitPython import path error. If your keyboard does not type, or the Pico disconnects immediately upon plugging in, check these three things in order.
1. The "Pin in Use" Conflict
Exact Error String: ValueError: Pin GP15 in use
Ranked Causes:
- Double Assignment: You accidentally listed the same GPIO twice in your
row_pinsorcol_pinstuples. - USB/I2C Conflict: You are using a Pico W and trying to use GP23, GP24, or GP25, which are internally routed to the WiFi chip and voltage regulator. Stick to GP0-GP22 for matrix scanning.
- Fix: Open the REPL (serial console) and verify your pin tuples. Ensure no overlaps exist.
2. The Missing Library Error
Exact Error String: OSError: [Errno 2] No such file/directory: 'kmk'
Ranked Causes:
- Incorrect Directory Structure: You placed
kmk.pyin the root instead of thekmkfolder. CircuitPython requires the entire directory tree. - Corrupted Drive: The
CIRCUITPYfilesystem became corrupted from unsafe ejection. Fix by re-flashing the CircuitPython UF2 (this wipes the drive) and re-copying the files.
3. Ghosting and Key Chatter (Physical Layer)
Symptom: Pressing 'Q' and 'A' simultaneously causes 'W' to register, or keys double-type.
Ranked Causes:
- Backward Diode: One or more 1N4148 diodes are soldered with the black band facing the wrong way. Use a multimeter in diode-test mode. Red probe on the switch side, black probe on the column wire should read ~0.6V. Reversed reads OL (Open Loop).
- Solder Bridge: A blob of solder is shorting two adjacent column wires on the PCB or switch legs. Wick it away with desoldering braid.
Extending and Simplifying the Build
Once your base 4x12 matrix is stable, the RP2040 has plenty of headroom for modifications.
How to Extend: Adding Rotary Encoders
The RP2040 handles rotary encoders beautifully using its PIO state machines, freeing up the main CPU cores. To add an EC11 rotary encoder:
- Solder the encoder's A and B pins to GP16 and GP17.
- Solder the C (common) pin to GND.
- In your
main.py, importfrom kmk.modules.encoder import EncoderHandler, initialize the handler, and map the pins:encoder_handler.pins = ((board.GP17, board.GP16, None, False),).
How to Simplify: The 3x3 Macropad
If a 48-key build is too complex for a first project, strip the code down to a 3x3 macropad.
- Reduce
row_pinsto(board.GP0, board.GP1, board.GP2). - Reduce
col_pinsto(board.GP3, board.GP4, board.GP5). - Use the remaining GPIOs to wire up a 128x64 SSD1306 OLED display via I2C (SDA to GP8, SCL to GP9) to display your Caps Lock status or CPU usage via a serial script.
Building a custom keyboard with a Raspberry Pi Pico bridges the gap between hardware soldering and Python software development. By respecting the diode polarity, keeping your pin mappings clean, and utilizing KMK's robust error handling, you can create a highly responsive, fully customizable input device that outperforms off-the-shelf alternatives.






