Loading 3D model… large boards can take a moment.
A 3x3 mechanical macropad built around the Raspberry Pi RP2350A. It enumerates as a standard USB HID keyboard plus a USB CDC serial port, so each of the nine keys sends a configurable HID keycode and modifier combination. The key map is programmed over the serial port from a small web app, with no need to recompile or reflash the firmware to change it.
The board also drives a 1.14 inch ST7789 LCD that shows live temperature and humidity from an AHT20 sensor, basic system info (CPU clock, static RAM, uptime) and the current 3x3 key map. Three SK6805 RGB LEDs report status: power, caps lock, and an ambient temperature gradient.
.
├── Kicad/ KiCad 10 schematic, PCB and project files
├── assets/ Renders, board photos, exported schematic PDF
├── datasheets/ Datasheets for the main parts on the board
├── ibom/ Interactive HTML BOM
├── production/ Gerbers and fabrication outputs
└── code/
├── firmware/ RP2350 firmware (C, Pico SDK 2.2)
├── web-programmer/ Next.js web app for editing the key map
└── host-actions/ Optional Python daemon for host-side macros
The schematic and PCB are in Kicad/. Rendered images and a PDF schematic are
in assets/, and an interactive BOM is in ibom/.
code/firmware)Built with the Raspberry Pi Pico SDK 2.2. The recommended setup is the official
"Raspberry Pi Pico" VS Code extension, which downloads the SDK, toolchain and
CMake into ~/.pico-sdk/ and provides the build, debug and flash actions used
by .vscode/launch.json and .vscode/tasks.json.
cd code/firmware
export PICO_SDK_PATH="$HOME/.pico-sdk/sdk/2.2.0"
export PICO_TOOLCHAIN_PATH="$HOME/.pico-sdk/toolchain/14_2_Rel1"
cmake -S . -B build -G Ninja
cmake --build build
The output is build/rp2350-macropad.uf2. To flash, hold BOOTSEL while plugging
the board in and copy the UF2 onto the mounted RPI-RP2 drive, or run
picotool load build/rp2350-macropad.uf2 -fx. With a Debug Probe on the SWD
connector, the VS Code extension can flash and debug over SWD directly.
main.c: main loop (USB tasks, matrix scan, sensor read, LCD draw, LED animation, the CDC command protocol, and key map persistence in the last flash sector).usb_descriptors.c: composite USB descriptor (HID keyboard plus CDC).st7789.c, st7789.h: minimal SPI driver and bitmap text routines for the ST7789 panel.font5x7.h: 5x7 bitmap font used by the LCD text routines.icons.h, tools/gen_icons.py: color macro icons, and the script that generates them.ws2812.pio: PIO program driving the SK6805 LED chain.tusb_config.h: TinyUSB configuration (HID and CDC enabled).CMakeLists.txt, pico_sdk_import.cmake: build configuration.code/web-programmer)A Next.js app that talks to the firmware over the USB CDC serial port using the Web Serial API. It reads the current key map from the device, lets you assign a keycode and modifier combination (or a host macro) to each of the nine keys, and writes the new map back to the last sector of flash so it survives a reboot. A live tester highlights each key on an on-screen copy of the LCD as it is pressed.
Web Serial runs in Chromium browsers (Chrome, Edge and other Chromium based browsers) and in Firefox 151 and newer. Safari does not support it.
cd code/web-programmer
npm install
npm run dev
Open http://localhost:3000, click Connect via USB, choose the "Macropad Programmer" port from the browser picker, and edit the key map.
code/host-actions)An optional Python daemon for macros that a single keystroke cannot express,
such as running a script or toggling a system setting. A cell marked as a macro
in the web programmer types nothing on its own; instead the firmware emits a
serial event that the daemon turns into an action on the host. See
code/host-actions/README.md for details.
The firmware exposes a line-oriented ASCII protocol on the CDC interface
(115200 baud, although the rate is irrelevant on USB CDC). Each command is a
single line ending in \n; responses are also single lines.
| Command | Response |
|---|---|
INFO | INFO:{"temp":..,"hum":..,"cpu_hz":..,"heap_used":..,"heap_free":..,"static_used":..,"uptime_ms":..,"flash_used":..,"flash_total":..} |
GET | CONFIG:k0,k1,k2,k3,k4,k5,k6,k7,k8,m0,m1,m2,m3,m4,m5,m6,m7,m8, nine HID keycodes followed by nine modifier bytes in row-major order |
SET:k0,k1,...,k8,m0,m1,...,m8 | OK on success, ERR on a malformed payload. The new key map is written to the last 4 KB sector of flash and takes effect immediately |
NAME | NAME:<current device name>, empty if the device has never been renamed |
SETNAME:<name> | OK on success, ERR if <name> is longer than 31 bytes or contains a non-printable-ASCII byte. The name is persisted alongside the key map |
In addition to the request/response commands above, the firmware emits two asynchronous push notifications while a host is connected. These are not replies to a request; they arrive whenever a key is touched:
| Event | When |
|---|---|
PRESS:<idx> | A physical key has just gone down (idx 0-8, row-major) |
RELEASE:<idx> | A physical key has just gone up |
Hosts that do not care about these events can ignore any line whose first token
is PRESS or RELEASE. The web programmer uses them to drive the live tester,
and the host actions daemon uses them to trigger macros.
Modifier bits follow the standard USB HID convention: bit 0 = Ctrl, bit 1 = Shift, bit 2 = Alt, bit 3 = GUI/Meta, with the right-side modifiers in the upper nibble.
The key map is stored in the last flash sector with the following layout:
| Offset | Bytes | Field |
|---|---|---|
| 0..3 | 4 | magic = 0xCA1A1002 (little-endian) |
| 4..12 | 9 | keycodes (row-major) |
| 13..21 | 9 | modifiers (row-major) |
| 22 | 1 | name length (0..31; 0xFF is treated as "unset") |
| 23..54 | 32 | name bytes (printable ASCII, padded with 0xFF) |
If the magic is missing or wrong (a fresh chip, or a firmware update that erased
the sector) the firmware falls back to digits 1-9 with no modifiers and an empty
name. Configs written by older firmware that did not yet have the name field
still load: byte 22 reads as 0xFF and the name is treated as unset.
Released under the MIT License. See LICENSE.
Updated
No comments yet. Be the first to ask about this board.