

SMK macro pad
4x4 wireless macro pad — RP2040 + Raspberry Pi RM2. KiCad project generated from Python; includes a 42%-smaller 'mini' variant with back-mounted electronics.
- Size
- 120 × 89.3 mm
- Copper layers
- 4
- Thickness
- 1.6 mm
- Parts
- 89
- Nets
- 60
- Pads
- 343
- Tracks, vias
- 792, 68
- Pours
- 4
- Routed
- Yes
- KiCad
- KiCad 10.0 (file 20260206)
- Created
- 16 Aug 2026
- Updated
- 23 Aug 2026
Overview
SMK macro pad is an open-source printed circuit board by mburger89, from the mburger89/SMK_macro_pad repository on GitHub, published under the MIT licence. It is a 4-layer board of 120 × 89.3 mm with 89 parts on 60 nets, routed with 792 tracks and 68 vias.
It is built around the RP2040, RMC20452T and W25Q16JVUXIQ, with the AP2112K-3.3, MCP73831 and USBLC6-2SC6 among its other parts. Its parts include 23 capacitors, 19 diodes, 19 switches, 12 resistors and 6 ICs. It belongs with the keyboards and input boards and the RP2040 boards of this library.
Main components
11RP2040
LQFN-56(7x7)12bit 133MHz 30 LQFN-56(7x7) Microcontrollers (MCU/MPU/SOC) ROHSRaspberry PiMicrocontrollers (MCU/MPU/SOC)RMC20452T
SMD,16.5x14.5mm-30℃~+70℃ 2.4GHz 271mA 3V~4.8V 43mA On-board PCB Antenna SPI SMD,16.5x14.5mm RF Modules ROHSRaspberry PiRF ModulesW25Q16JVUXIQ
USON-8-EP(2x3)-40℃~+85℃ 100,000 cycles 10uA 133MHz 16Mbit 2.7V~3.6V 20 Years 3ms SPI USON-8-EP(2x3) NOR FLASH ROHSWinbond ElecNOR FLASHAP2112K-3.3
SOT253.3V 600mA CMOS Low Dropout Linear Regulator with EnableDiodes IncorporatedUncategorisedMCP73831
UncategorisedMicrochip TechnologyUSBLC6-2SC6
SOT-23-60.35pF、0.8pF 100nA 150W@8/20us 15V 2 5V 6A@8/20us 6V ESD SOT-23-6 ESD and Surge Protection (TVS/ESD) ROHSUMWESD And Surge Protection (TVS/ESD)1N4148W
SOD-123150mA 1A 1V@50mA 1uA@75V 400mW 4ns 75V Independent SOD-123 Switching Diodes ROHSSTMicroelectronicsSwitching DiodesKS-2P02B01-02
SMDSMD New Arrivals ROHSGATERONUncategorisedB5819W
SOD-123-55℃~+125℃ 1.5A 1mA@40V 25A 40V 600mV@1A Standalone SOD-123 Schottky Diodes ROHSMDDSchottky DiodesDMG3415U
SOT-23MOSFETsTECH PUBLICTYPE-C-31-M-12
SMD-30℃~+80℃ 1 10,000 cycles 16P 20V 5A 7.35mm Female Surface Mount, Right Angle Type-C SMD USB Connectors ROHSHRO ElectronicsUSB Connectors
README
README.mdSMK Macro Pad — 4×4, RP2040 + Raspberry Pi RM2 wireless
A 16-key (4×4) wireless macro pad. KiCad hardware project, generated from a
Python script rather than hand-drawn in the GUI — the .kicad_pcb/
.kicad_sch files are build artifacts of generate_macropad.py, not the
source of truth. This is a sibling project to ~/esp/SMK_Keyboard
(59-key wireless keyboard) and deliberately reuses that project's
RP2040/RM2/power reference circuit and footprints.
- MCU: RP2040 chip-down (QFN-56) + external W25Q16JV QSPI flash + 12MHz crystal (RP2040 has no internal flash — this is required, not optional).
- Wireless: Raspberry Pi RM2 (Radio Module 2, RMC20452T) — the same
CYW43439 die used in Pico W, packaged on a 1.5mm-pitch castellated module
with an integrated, pre-tuned antenna. Wired to gSPI on GPIO23/24/25/29 —
the exact default pins the Pico SDK's
cyw43-driver+ BTstack expect, so firmware can use the same software stack as Pico W/Pico 2 W unmodified. - Matrix: 4×4, COL2ROW, one 1N4148W diode per key, Gateron KS-33 hot-swap sockets (same footprint as the keyboard project).
- Power: USB-C (HRO TYPE-C-31-M-12) → USBLC6-2SC6 ESD → MCP73831 Li-ion charger (500mA via 2.0kΩ PROG) → power mux (small SOD-123 Schottky from VBUS, DMG3415U P-FET from battery) → VSYS → AP2112K-3.3 LDO → +3V3, gated by a slide switch (true hardware off). Battery-voltage sense via a 1MΩ/1MΩ divider into an ADC-capable GPIO.
- Extras: SWD debug header, RESET + BOOTSEL buttons, one firmware-controlled status LED (GPIO8).
Open smk_macropad/smk_macropad.kicad_pro in KiCad 9/10 (files are written
in KiCad 8 format, which later versions read natively).
Why this looks like the keyboard project
Rather than re-deriving a wireless RP2040 design from scratch, this reuses
the sister project's already-datasheet-verified work: the RP2040 QFN-56
pinout table, the RM2 module's pinout/footprint/gSPI wiring, and the
USB/charger/LDO/power-mux reference circuit are transcribed unchanged from
SMK_Keyboard/generate_kbd_rp2040.py (see that file's own header for the
specific datasheet sections each of those was checked against). What's new
here is a much smaller 4×4 matrix, no per-key RGB, no ESP32 variant, and a
fresh board layout sized for 16 keys instead of 59.
GPIO map
| Function | GPIO | Net |
|---|---|---|
| Matrix rows | GPIO0–3 | ROW0–3 |
| Matrix columns | GPIO4–7 | COL0–3 |
| Status LED | GPIO8 | STATUS_LED |
| (spare) | GPIO9–17, 27, 28 | — |
| RM2 WL_ON + BT_ON | GPIO23 | RM2_ON |
| RM2 gSPI data/nIRQ | GPIO24 | RM2_DATA (→ RM2_DOUT via 470Ω, RM2_NIRQ via 10kΩ) |
| RM2 gSPI CS | GPIO25 | RM2_CS |
| RM2 gSPI CLK | GPIO29 | RM2_SCLK |
| Battery sense | GPIO26/ADC0 | VBAT_SENSE (see firmware note below) |
| SWD | GPIO24(SWCLK)/GPIO25(SWDIO)* | SWCLK/SWDIO |
Firmware note — VBAT_SENSE settling. The divider (R6/R7, 1M/1M) is filtered by C5 (100nF), giving an RC time constant of ~50 ms (500 kΩ Thevenin × 100 nF). After power-on the node needs ~250 ms (5τ) to reach its final value — verified in ngspice: 1.17 V at 50 ms, 1.838 V at 250 ms, 1.849 V at 400 ms against a 1.850 V final. Wait ~300–400 ms after boot before trusting the first battery reading. Steady-state sampling is exact (C5 swamps the ADC's ~5 pF sample cap; simulated error < 0.001 LSB), so no read-averaging or throwaway reads are needed once settled.
* RP2040's dedicated SWCLK/SWDIO pins (24/25 on the QFN-56 pinout) are
separate from the GPIO24/25 numbers used above for RM2 — same pin-number
coincidence as the sister project, not a conflict (see RP2040_PINOUT in
the generator for the authoritative pin table).
Build pipeline
generate_macropad.py— emits the full KiCad project (schematic + PCB, footprints, symbols) from scratch intosmk_macropad/. Stdlib only (uuid,os,json) — no venv needed. Re-run any time to regenerate after editing the script; it overwrites the project directory contents. Includes a basic automated bounding-box overlap scan (prints warnings for any two footprint courtyards that collide) as a sanity check — seecheck_overlaps().- Not yet done: routing. This script only places footprints and
declares nets — the board ships as ratsnest-only (no
(segment ...)tracks). Route it either interactively in KiCad's PCB editor, or by adapting the sister project'sroute_kbd_rp2040.pyA* router to this net list (same net names/topology, much smaller board). - Not yet done: fab export. Once routed, fill zones and export
Gerbers using KiCad's own bundled Python (needs
pcbnew), the same way the keyboard project'sexport_fab.pydoes.
Floorplan — functional blocks
Board: 120 × 100 mm, 76% covered by functional blocks. (It was 190 × 110 at 52% coverage — the repack cut 8,900 mm², a 43% smaller board, without removing a single component.)
The board is organised into seven labelled blocks so it's obvious at a glance what belongs to what:
| Block | Contains |
|---|---|
| WIRELESS | U6 (RM2) + gSPI isolation resistors R10/R11 |
| 4×4 KEY MATRIX | SW1–16 and each key's own diode D1–16 |
| USB-C + ESD | J1 receptacle (north edge), U4 ESD array, CC resistors |
| RP2040 CORE | U1, QSPI flash U5, crystal Y1 + load caps, the decoupling farm, RUN pull-up |
| POWER | D17/Q1 mux, U3 LDO, bulk caps, slide switch SW17, VBAT-sense divider |
| CHARGER + INDICATORS + BATTERY | U2 charger, PROG/bulk, both LEDs, JST battery connector |
| DEBUG | SWD header J3, RESET SW18, BOOTSEL SW19 |
Block rectangles and captions are drawn on Cmts.User — a documentation
layer. They're visible in the editor but are not copper, not silkscreen and
not plotted into fab output, so the annotation adds zero new DRC
violations and doesn't change the manufactured board. (On F.SilkS it would
have meant real ink and a fresh crop of silk-overlap errors.)
Placement follows signal flow rather than convenience: USB-C on the north
edge so the cable exits away from the user; the power path running
left-to-right across its block (VBUS → D17 → VSYS ← Q1 ← BAT+, then
VSYS → U3 → +3V3); support parts on whichever side of U1 their pins
actually exit (QFN-56 pin sides are 1–14 west, 15–28 south, 29–42 east,
43–56 north, so matrix nets leave west toward the keys, QSPI/USB leave north,
crystal/SWD/RUN leave south); and the decoupling on a strict 4 mm grid east
of U1 so it reads as one block instead of a scatter.
check_blocks() asserts the floorplan holds — blocks must not overlap, and
every footprint must fall inside exactly one of them (mounting holes
excepted, they're deliberately in the leftover slivers). Move a part
somewhere it doesn't belong and the build fails rather than quietly undoing
the grouping.
What actually sets the board size
Worth knowing before trying to shrink it further — the remaining space is mostly not free:
- The matrix is immovable. 19.05 mm key pitch is mechanical, so the matrix is a fixed 75.5 × 73.5 mm rectangle — 46% of the whole board on its own. Everything else is fitted around it.
- The antenna keepout is mandated empty. 18.5 × 14.5 mm of board that must carry no pour or vias, and it has to point off a real edge. Combined with the module it's a 35 mm strip, which is why wireless sits on the west edge in the band below the matrix — the only west-edge run tall enough.
- Three parts need board edges: USB-C (north), the power slide switch (east, so the actuator clears a case), and the antenna (west).
- The 6.5 mm channel between the matrix and the east column is working space, not waste — it carries the matrix→MCU fanout and hosts two mounting holes.
Verified against the generated file: closest pad copper to a board edge is 1.2 mm (JLCPCB minimum is 0.3 mm), nothing sits outside the outline, and no component intrudes into the antenna keepout.
Further shrinking would mean a smaller key pitch (changes the keycaps), choosing a wireless part without a keepout, or moving parts to the back side — not just tidier packing.
One trade-off worth knowing: RP2040's gSPI pins (35/36/37/41) are on its east side while the RM2 module is on the west edge, so those four radio nets take a long trip across the board. Keeping U1's west-side matrix pins facing the keys is the better trade — eight matrix nets versus four radio nets — and an antenna wants a board edge pointing away from everything else. The sister keyboard makes the same call for the same reason.
Schematic
The sheet uses the same seven groupings as the PCB, each in a dashed frame with a title, so the two documents read the same way.
It was previously one flat sea of net labels — 212 of them, 41% just power rails. Now:
| Before | After | |
|---|---|---|
| Global labels | 212 | 126 |
| Power ports | 0 | 86 |
| Wires | 32 | 118 |
| Block frames | 0 | 7 |
Power rails (GND, +3V3, VSYS, VBUS, BAT+) are drawn as proper
power port symbols on short stub wires instead of text. KiCad resolves a
power port by the name of its hidden power pin, so the symbol name, Value
and pin name are deliberately kept identical — change one without the others
and the net silently splits.
Global labels are kept for signals that genuinely leave their block
(ROW*/COL*, QSPI_*, RM2_*, USB_D*, SWCLK/SWDIO, …). Drawing
those as wires across the sheet would be far less readable — a good
schematic mixes both, and the split here is by scope, not convenience.
J2 also got a real 2-pin connector symbol; it had been borrowing the
resistor symbol, which read as "there is a resistor here".
Connectivity is checked, not assumed
check_schematic() reads the generated .kicad_sch and .kicad_sym back,
recomputes every pin's absolute position from the library (so placement code
and library can't drift), and asserts:
- every pin has a wire end, label, power port or no-connect exactly on it, and
- every wire end terminates on a pin, label, no-connect or another wire.
Check 2 exists because check 1 alone is fooled by a dangling wire: delete a power port and the pin still looks connected while the net is broken. All four failure modes are verified to trip the assertion (nudged label, deleted power port, deleted wire, moved symbol) — 263 pins currently pass.
The one deliberate difference from the PCB: the 16 switch→diode nets are
unnamed local nets in the schematic (they're just a visible wire between
the two parts), while the PCB names them N_R{r}C{c}. Connectivity is
identical and both files come from the same generator, so they can't drift —
only the auto-generated name differs, and re-running "Update PCB from
Schematic" would simply rename those 16 nets.
A bug this surfaced
Drawing the status LED properly exposed that LED2 was wired backwards —
anode on GND, cathode to GPIO8 through R12. It could never light, since
that needs the GPIO below 0 V. Now anode on the GPIO side, cathode to GND,
fixed in both the schematic and the footprint's pad assignment. (LED1, the
charge indicator, was already correct: cathode on STAT, which the MCP73831
pulls low while charging.)
Fab limits — JLCPCB, 4-layer / 1oz outer copper
Board Setup → Design Rules → Pre-defined Sizes is populated, so track widths and via sizes can be picked from the PCB editor's dropdowns while routing instead of typed each time. Everything below is sized against JLCPCB's multilayer minimums (0.09mm track/space, 0.25mm via pad, 0.15mm annular ring) — not the looser 2-layer numbers, since this is a 4-layer board.
Track widths (current = IPC-2221 external, 1oz, 10 °C rise — all far above what this board draws; chosen for routability and fab margin, not current limits):
| Width | Use | ≈ Current |
|---|---|---|
| 0.15 mm | fine escape — RP2040 QFN-56 0.4mm-pitch fanout | 0.6 A |
| 0.25 mm | default signal (matches the Default netclass) | 0.85 A |
| 0.40 mm | wide signal / short power hop | 1.2 A |
| 0.60 mm | power rails: +3V3, VSYS | 1.6 A |
| 1.00 mm | VBUS / BAT+ / charge path | 2.4 A |
Via sizes (annular ring = (pad − drill)/2; all ≥ JLCPCB's 0.15mm multilayer minimum, and every drill is ≥0.20mm — JLCPCB's preferred minimum — so none lands in the 0.15–0.20mm tighter-tolerance band):
| Pad / drill | Ring | Use |
|---|---|---|
| 0.50 / 0.20 mm | 0.15 mm | fine escape via near the QFN fanout |
| 0.60 / 0.30 mm | 0.15 mm | default (matches the Default netclass) |
| 0.80 / 0.40 mm | 0.20 mm | power via |
| 1.00 / 0.50 mm | 0.25 mm | heavy power / GND stitching |
No blind or buried vias anywhere — JLCPCB's standard service is through-hole only. (That constraint is exactly why the sister project dropped its chip-down CYW43439 for the RM2 module this board also uses.)
Two DRC rules are set explicitly because KiCad's own defaults are looser
than JLCPCB: min_via_annular_width (KiCad 0.10mm → 0.15mm) and the
silkscreen minimums (KiCad 0.8mm text / 0.08mm stroke → 1.0mm / 0.15mm).
Left at KiCad's defaults, DRC would pass geometry the fab then rejects. The
silkscreen rules are additionally enforced in smk_macropad.kicad_dru,
which survives a Board Setup reset where the project-file values wouldn't.
generate_macropad.py asserts every preset clears the JLCPCB floor
(check_fab_limits()), so an ill-advised edit fails the build loudly rather
than silently shipping an unmanufacturable number.
The differential-pair preset is geometry only
There's one diff-pair preset (0.20mm width / 0.15mm gap) for USB D+/D−, and it is deliberately not claimed to be 90 Ω. Real differential impedance depends on the finished stackup's dielectric heights and Er, which JLCPCB sets at build time — hitting 90 Ω ±10% needs their controlled-impedance service (available on 4+ layer orders) and/or a field solver. USB 2.0 Full Speed over a few cm on a board this size is very tolerant, so the pragmatic approach is a tight, length-matched pair over a solid reference plane, and only pay for controlled impedance if a board comes back failing enumeration.
Source: JLCPCB PCB capabilities, via the sister project's vendored copy of that table (fetched 2026-07-25). Re-check the live page before ordering — JLCPCB revises these periodically.
Verification status (read before fab)
-
RP2040 pinout/QFN-56 pad geometry, RM2 pinout/gSPI wiring/footprint, MCP73831/AP2112K-3.3/USBLC6-2SC6/DMG3415U reference circuit, USB-C pad map, Gateron KS-33 hot-swap footprint: high confidence — unchanged from the sister project's own datasheet-checked work.
-
Matrix diode topology/wiring: high confidence — same COL2ROW pattern and footprints as the verified keyboard, just a smaller grid.
-
Board layout/component placement: new for this board, and now run through KiCad 10's real DRC (2026-07-27, report below). Result: 52 violations + 143 unconnected pads, and every one is either expected or cosmetic:
Count Type Assessment 143 unconnected_itemsexpected — the board is unrouted 16 courtyards_overlapexpected — verified to be exactly each switch with its own diode, the sister project's intentional per-key offset 15 silk_over_coppercosmetic — silkscreen over pads 12 silk_overlapcosmetic — ref-des text colliding (C16, SW18/19, C20, J3…) 5 starved_thermalminor — GND zone spoke count, resolves on routing/refill 3 lib_footprint_mismatchbenign, see below 1 silk_edge_clearancecosmetic — silk clipped by board edge Zero copper violations — no clearance, track-width, annular-ring, via or hole errors. Worth being precise about what that does and doesn't prove: the board currently has 0 track segments and 0 vias, so DRC could not yet exercise the track/via rules. What it does confirm is that the rules and presets are correctly loaded and that the placement itself is clean. Re-run DRC after routing for the real test.
The 3
lib_footprint_mismatchwarnings are a generator artifact, not a design problem:export_libs()writes each library copy in its default orientation, while the board placesSW18/SW19on the back side (side="B") andJ1at 180°. KiCad flags the instance as a local override; the board is authoritative and the geometry is self-consistent. -
The silkscreen violations are worth a cleanup pass before fab (mostly auto-placed ref-des text landing on pads), but none affect copper.
-
RM2 antenna keepout zone (west edge) placement: VERIFY — not physically prototyped, same caveat as the keyboard project.
-
Status LED (GPIO8) and battery-sense divider are this project's own additions (not present on the keyboard board) — straightforward, but unverified against a real assembled board.
-
Pre-defined track/via sizes: values are checked against JLCPCB's published table (see above), asserted in the generator, and verified live in KiCad 10 — Board Setup → Pre-defined Sizes lists exactly the 5 track widths and 4 via sizes with no stray zero row, and the toolbar dropdowns show "use netclass width"/"use netclass sizes" for the reserved index-0 slot. The Constraints page likewise reads back 0.09 clearance, 0.09 track, 0.15 annular, 0.25 via, 1.0/0.15 silkscreen.
Mini variant
generate_macropad_mini.py builds smk_macropad_mini/: the same circuit on
a 76.5 × 90.65mm board (down from 120 × 100mm, 42% less area) by shrinking
the outline to the switch field and moving the electronics to the back,
into the clear bands between the key rows. Design notes:
docs/superpowers/specs/2026-08-15-macropad-mini-variant-design.md.
It imports generate_macropad.py rather than forking it, so a fix to a
footprint builder reaches both boards.
Its own DRC run (2026-08-15, --refill-zones so the GND pours are actually
filled before clearance is judged — a fresh board declares its zones without
filled-polygon data, and skipping this flag judges clearance against empty
pours) found 134 violations, 143 unconnected pads, and 60 schematic-parity
notes. As with the main board, none of that is a real defect:
| Count | Type | Assessment |
|---|---|---|
| 143 | unconnected_items | expected — the board is unrouted |
| 39 | lib_footprint_mismatch | benign, same generator artifact as the main board — back-side/rotated instances vs. each library copy's default orientation |
| 36 | courtyards_overlap | expected — every one pairs a switch with a back-side part deliberately placed inside its courtyard; that's the whole reason this board is smaller. check_back_clearance() proves real clearance against the socket's actual pads, holes and plastic housing — not courtyards — and passes |
| 30 | silk_over_copper / 22 silk_overlap | real remaining work, not noise — see below |
| 6 | starved_thermal | minor — GND zone spoke count, resolves on routing/refill |
| 1 | silk_edge_clearance | SW17's reference field overhangs the west edge (the slide switch hugs that edge) — accepted as-is |
npth_inside_courtyard is now zero, matching the main board. An earlier
run reported 4 and dismissed them as "expected — mounting holes sit inside
the switches/parts around them"; that was wrong twice over. The main board
reports zero, so there was nothing to match; and the four holes really were
buried in switch bodies, which made all four unusable — no screw head,
standoff or boss could be fitted at any of them. They have been moved (see
MOUNT_XY in the generator) and the warning went away with them.
Zero copper violations — no clearance, copper_edge_clearance,
hole_clearance, or hole_near_hole entries. The 60 schematic-parity notes
are the same categories the main board already reports (net conflicts on
diode pads, missing MPN fields, mounting-hole "extra footprints") — compared
directly against a same-day parity run on smk_macropad/, which reports the
identical categories at different counts. Nothing here is new to this
variant.
This board's silkscreen needs a cleanup pass before fab, plainly — 22 silk
overlaps and 30 silk-over-copper hits on a much smaller board is real
remaining work, not an acceptable steady state, even though none of it
touches copper. One of the fixes along the way traded a clipped board title
for two more of these: the title used to run off the west edge
(silk_edge_clearance), and moving it fully onto the board so it's readable
cost one more silk_over_copper and one more silk_overlap where it now
sits among band B3's parts — 155 violations before that fix, 156 after.
Worth it for a title that's actually visible, but the count went up, not
down. (The 156 later came down to 134 when the bands were re-cut around the
socket housing and the mounting holes were moved out of the switch bodies —
that repacking spread band B3's parts out and took 13 silk hits with it.)
Renders of both sides (kicad-cli pcb render) confirm the placement by eye:
the switch field fills the board with only a thin margin, U6 (the RM2) sits
top right with its blank antenna edge against the north edge, USB-C (J1) is
on the north edge at the left, and the back shows three horizontal bands of
parts threaded between the socket holes with the RP2040 (U1) in the top
band.
Mounting holes: four, but not at the corners. A fastener is a
through-board feature — head on the front among the 14 × 14 mm switch
bodies, nut or standoff on the back among the socket housings and the band
electronics. Intersecting those two constraints leaves exactly nine
5.05 × 4.13 mm windows in the key field, at the crossings of the column and
row gaps. H1–H4 take the four corners of that 3 × 3 grid: a 38.1 × 38.1 mm
pattern on a 76.5 × 90.65 mm board. The outer ~19 mm of board on every side
has no mount and cannot have one — north of row 0 the strip is full of
USB-C, RM2 and the slide switch, and south of row 3 only 1.5 mm of board is
left. Each hole gets Ø4.0 mm of clear surface on both faces, which takes an
M2 socket-head cap screw (Ø3.8 head) or a Ø4 round standoff but not an M2
washer (Ø5.0) or a hex standoff (Ø4.6 across corners). Ø4.13 is the hard
ceiling anywhere on this board. That is a real cost of the shrink, enforced
by check_hole_clearance().
- RM2 antenna placement: VERIFY — the antenna now shares the north edge with USB-C (J1), 51.6mm away on the same edge (U6 at x=68.00, J1 at x=16.60, centre to centre). The design spec calls this "the least favourable neighbour" the antenna could have, chosen deliberately to save 8mm of board height rather than for any RF benefit. Not physically prototyped; treat as pre-production until a real board is measured, same caveat as the main board's antenna-keepout entry above, but sharper here since the trade was made knowingly against RF rather than incidentally.
Do not run "Update Footprints from Library" on the mini board. Most of its parts are placed on B.Cu by mirroring a front-side library footprint, so KiCad would silently un-flip every one of them. This is the same hazard the main board already carries, for the same reason.
Repo contents
generate_macropad.py— the actual source of truth (see above).smk_macropad/— generated KiCad project (.kicad_pro/.kicad_sch/.kicad_pcb,kbd.kicad_sym,kbd.pretty/, library tables).smk_macropad/3dmodels/— hand-authored/vendor 3D models copied from the sister project (Gateron switch/socket, USB-C, JST-SH, slide switch).smk_macropad_mini/3dmodels— a symlink to the above, created bygenerate_macropad_mini.py. Five of the mini's footprints reference${KIPRJMOD}/3dmodels/...and the 3D viewer is the only independent mechanical check a board like this has. Symlinked rather than copied because 705 kB of the 708 kB is one binary STEP file, and a second copy in git could silently drift from the original.- Datasheets for the RP2040/RM2/etc. are not duplicated here — see
~/esp/SMK_Keyboard/datasheets/for the same parts' reference PDFs.
License
MIT — see LICENSE. That covers this project's own work: the generator scripts, the tests, the docs, and the KiCad projects they emit.
Two things it does not cover, because they aren't mine to license:
smk_macropad/3dmodels/— vendor and third-party 3D models (Gateron switch and socket, USB-C receptacle, JST-SH, slide switch), vendored here so the 3D viewer works. Each carries whatever terms its author released it under.- Footprints and symbols derived from KiCad's stock libraries. Several
kbd.pretty/footprints are transcriptions or mirrors of upstream KiCad library parts, which ship under the KiCad Library License — CC-BY-SA 4.0 with an explicit exception permitting use in any design without imposing a licence on that design.
Note that MIT is a software licence, and most of what is here genuinely is software: a Python program that happens to emit board files. If you want to reuse the board rather than the generator, a hardware licence such as CERN-OHL-P is the conventional fit — open an issue and I will consider dual-licensing.
And the obvious one, restated because this is hardware: the MIT warranty disclaimer is not boilerplate here. This design has never been fabricated. See "Verification status" above before you spend money on it.
Parts
89| Qty | Value | Footprint | References |
|---|---|---|---|
| 16 | 1N4148W | kbd:D_SOD-123_Back | D1, D2, D3, D4, D5, D6, D7, D8, D9, D10, D11, D12, D13, D14, D15, D16 |
| 16 | KS-2P02B01-02 | kbd:SW_Gateron_KS33_HS | SW1, SW2, SW3, SW4, SW5, SW6, SW7, SW8, SW9, SW10, SW11, SW12, SW13, SW14, SW15, SW16 |
| 13 | 100n | kbd:RC_0603 | C5, C6, C9, C12, C13, C14, C15, C16, C17, C18, C19, C22, C23 |
| 4 | M2 | kbd:MountingHole_M2 | H1, H2, H3, H4 |
| 3 | 1u | kbd:RC_0603 | C3, C10, C11 |
| 3 | 4.7u | kbd:RC_0603 | C1, C2, C24 |
| 2 | 10u | kbd:RC_0603 | C4, C7 |
| 2 | 18p | kbd:RC_0603 | C20, C21 |
| 2 | 1M | kbd:RC_0603 | R6, R7 |
| 2 | 1k | kbd:RC_0603 | R4, R8 |
| 2 | 330 | kbd:RC_0603 | R9, R12 |
| 2 | 5.1k | kbd:RC_0603 | R1, R2 |
| 1 | 100k | kbd:RC_0603 | R5 |
| 1 | 10k | kbd:RC_0603 | R11 |
| 1 | 12MHz | kbd:XTAL_3225 | Y1 |
| 1 | 470 | kbd:RC_0603 | R10 |
| 1 | 6.8k | kbd:RC_0603 | R3 |
| 1 | AP2112K-3.3 | kbd:SOT-23-5 | U3 |
| 1 | B5819W | kbd:D_SOD-123_Back | D17 |
| 1 | BOOTSEL | kbd:SW_PUSH_6mm_THT | SW19 |
| 1 | Battery JST-SH | kbd:JST_SH_SM02B_2pin_Back | J2 |
| 1 | CHG | kbd:LED_0603 | LED1 |
| 1 | DMG3415U | kbd:SOT-23 | Q1 |
| 1 | MCP73831 | kbd:SOT-23-5 | U2 |
| 1 | PWR MSK-12C02 | kbd:SW_Slide_MSK12C02 | SW17 |
| 1 | RESET | kbd:SW_PUSH_6mm_THT | SW18 |
| 1 | RMC20452T | kbd:RM2_RADIO_MODULE_2 | U6 |
| 1 | RP2040 | kbd:RP2040_QFN56 | U1 |
| 1 | STATUS | kbd:LED_0603 | LED2 |
| 1 | SWD_DEBUG | kbd:SWD_HEADER_1x03 | J3 |
| 1 | TYPE-C-31-M-12 | kbd:USB_C_Receptacle_HRO_TYPE-C-31-M-12 | J1 |
| 1 | USBLC6-2SC6 | kbd:SOT-23-6 | U4 |
| 1 | W25Q16JVUXIQ | kbd:FLASH_USON8 | U5 |
| 1 | ferrite | kbd:RC_0603 | FB1 |
Similar boards
- 4 layers95.6 × 73.6 mm

- 2 layers228.6 × 114.3 mm

- 2 layers234.5 × 120.8 mm

Smk kbd devkit
mburger89/SMK_Keyboard_DevKit
59-key hot-swap mechanical keyboard PCB carrier for a socketed ESP32-C6-DevKitC-1 dev board — KiCad project generated/routed by Python scripts
148 parts - 4 layers104 × 69.4 mm

- 2 layers285.1 × 94.6 mm

YM60JIS PCB
ymkn/YM60JIS
A JIS layout 60% hot swappable custom mechanical keyboard PCB powered by RP2040.
173 parts10MIT - 2 layers285.1 × 94.6 mm

YuTofu PCB
ymkn/YuTofu
YuTofu is an unofficial compatible PCB for the well-known custom keyboard Tofu60 2.0.
169 parts5MIT - 2 layers252.9 × 110.6 mm

- 2 layers285.1 × 94.6 mm

Questions
How big is the SMK macro pad PCB?
It measures 120 × 89.3 mm and has 4 copper layers, 1.6 mm thick.
What parts does the SMK macro pad use?
89 parts, built around the RP2040, RMC20452T, W25Q16JVUXIQ and AP2112K-3.3. The full list of its parts is on this page, with links to each part's symbol and footprint where PCBKit's library has them.
Where can I download the SMK macro pad KiCad files?
From the mburger89/SMK_macro_pad repository on GitHub, at the commit this page was made from: the board is smk_macropad/smk_macropad.kicad_pcb and its schematic smk_macropad/smk_macropad.kicad_sch. PCBKit opens the board in the browser, with nothing to install.
Can I use the SMK macro pad design in my own project?
Its repository publishes it under the MIT licence: you may reuse it on that licence's terms (keep the author's notice, and for a copyleft licence share your changes alike).