A 62-key split columnar ergonomic keyboard.
This repo holds the hardware design files. The design pipeline is: Keyboard Layout Editor -> Ergogen -> KiCad -> Fabrication -> Onshape -> OrcaSlicer -> QMK firmware (see Developing).
| Version | MCU | Changes from previous | Firmware | Photo |
|---|---|---|---|---|
| v4 | splitkb Liatris (RP2040) | Added USB VBUS detection and TRRS data-line protection | splinter | ![]() |
| v3 | Adafruit KB2040 (RP2040) | Switched from AVR to RP2040 | splinter-v3.0 | ![]() |
| v2 | SparkFun Pro Micro (ATmega32U4) | Symmetrical enclosures; added a key (62 keys) | splinter-v2.0 | ![]() |
| v1 | SparkFun Pro Micro (ATmega32U4) | Initial version: 61 keys, columnar layout, asymmetrical enclosures | splinter-v1.0 | ![]() |
Install the following tools:
- KiCad 10
- Node.js
- OrcaSlicer
- Python 3
- Freerouting (Optional, for
npm run route. See Autorouting.) - KiKit (Optional, for
npm run panelize; needs the git-master build. See Panelization.)
# Include submodules when cloning
git clone --recursive git@github.com:andornaut/splinter-keyboard.git
cd splinter-keyboard
# Install the Node version from .nvmrc, then the deps (including Ergogen)
nvm install
npm install
# Install KiCad 10 (provides kicad-cli, used for fab file generation)
# Or use the Ansible task: https://github.com/andornaut/ansible-ctrl/blob/main/roles/hobbies/tasks/electronics.yml
sudo add-apt-repository ppa:kicad/kicad-10.0-releases
sudo apt install kicad
# Fallback only if you cloned without --recursive: fetch the submodules
git submodule update --init --recursiveAlternatively, you can install OrcaSlicer, KiCad, and Freerouting using these Ansible tasks (tags orcaslicer, kicad, freerouting).
npm run ergogen uses the vendored footprint submodules (ceoloide, infused-kim) at their pinned revision and does not advance them, so builds stay reproducible. To pull the latest upstream footprints and re-pin them:
git submodule update --remote ergogen/footprints/ceoloide ergogen/footprints/infused-kim
git add ergogen/footprints/ceoloide ergogen/footprints/infused-kim
git commit -m "Bump footprint submodules"Set the active version in package.json under config.VERSION to one of v1, v2, v3, or v4, either by editing the file or with npm pkg set config.VERSION=v3.
Step 2. Keyboard Layout Editor
- Prototype a layout in Keyboard Layout Editor.
- Export it to
keyboard-layout-editor/keyboard-layout-editor.jsonso you can re-import and iterate later. - Use it as the basis for the production Ergogen design.
Step 3. Ergogen
- Run
docker compose upto start the Ergogen GUI (it builds automatically), then open http://ergogen.internal (needs docker_etc_hosts for the/etc/hostsentry). - Paste in, edit, then download
ergogen/config.yaml. - Run
npm run ergogento generate outlines and PCBs intodist/v4/ergogen/, thennpm run copy:dist-to-unrouted. Or usenpm run watch/npm run watch:sync-unrouted.
Notes:
- The GUI prototypes key placement, layout, and outlines but does not render PCBs. It runs client-side only (no filesystem access, no sync), so edit there and copy back to
config.yaml, the source of truth. Usenpm run ergogenfor full builds and PCB generation. - Custom footprints are baked into the GUI image via the
Dockerfilesince the browser can't load them from disk. It registers this repo's custom footprints on top of the upstreamceoloideandinfused-kimlibraries; any unregisteredwhat:shows up as unknown. After adding one, rebuild withdocker compose build --no-cache.
Step 4. KiCad
-
Copy Ergogen's generated boards into
kicad/unrouted/withnpm run copy:dist-to-unrouted(existing boards are first backed up to gitignoredkicad/backups/<name>-<timestamp>.kicad_pcb), then open one in KiCad, e.g.left.kicad_pcb.- Board keepout/DRC features that constrain routing are version-specific; v4 carries copper keepout zones (added by
npm run ergogen) that flag stray copper near the board edge or screw bosses, see Copper keepout zones.
- Board keepout/DRC features that constrain routing are version-specific; v4 carries copper keepout zones (added by
-
Route the boards in
kicad/unrouted/. Once routing is done, clean each board up before saving:- Cleanup Tracks & Vias (Tools > Cleanup Tracks & Vias): enable all options to merge collinear segments, delete redundant/dangling tracks and vias, and remove tracks inside pads.
- Add Teardrops (Edit > Edit Teardrops, with nothing selected to apply board-wide): smooths track-to-pad/via junctions for stronger joints and better DFM. Re-run after any reroute.
- Run DRC (Inspect > Design Rules Checker) with "Refill all zones before performing DRC" checked: resolve every violation and confirm there are no unrouted nets.
npm run fabre-runs this same check headlessly and refuses to emit gerbers if it fails (see Step 5), but fixing violations interactively in KiCad is far easier than reading the JSON report. - Check copper/silk visually: confirm the GND zone has no isolated islands or stranded pads, and that silkscreen text and reference designators clear pads and the board edge.
Then
npm run copy:unrouted-to-routedto save them tokicad/routed/.- After regenerating boards with Ergogen,
npm run copy:traces-to-unroutedcopies the traces and teardrops fromkicad/routed/back into the same-named boards inkicad/unrouted/(then File > Revert in KiCad).
KiCad has no built-in autorouter. npm run route routes the kicad/unrouted/ boards in place via Freerouting, leaving kicad/routed/ untouched. Expect to hand-clean the result (the matrix routes nicer by hand), then File > Revert to load it. The defaults below aim for a fully-connected, DRC-clean board; raising via cost trades vias for unrouted nets, so it can't beat hand-routing on via count.
| Env var | Default |
|---|---|
FREEROUTING_PASSES |
100 |
FREEROUTING_STRATEGY |
greedy |
FREEROUTING_SELECTION |
prioritized |
FREEROUTING_VIA_COST |
50 |
FREEROUTING_UNDESIRED_DIR_COST |
unset |
FREEROUTING_LOG_LEVEL |
WARN |
npm run pipeline re-syncs already-routed boards after a config change. It requires existing routed masters in kicad/routed/: the copy:traces-to-unrouted step copies their traces back onto the freshly generated boards, and it aborts if a master has no human routing. It does not route for you, so use it only for updates where the existing routing still applies; for a first route (or when geometry changes enough that the old traces no longer fit), route by hand in KiCad (Step 4) instead.
It runs the whole hardware path in order: ergogen, copy:dist-to-unrouted, copy:traces-to-unrouted, copy:unrouted-to-routed, validate:provenance, fab, validate:fab, then panelize. It prints a per-step banner and a closing summary of the artifacts produced. Each step is a hard gate except the final panelize, which is skipped with a note when KiKit is not installed (the per-half fab from fab is complete on its own).
The cp steps and manual routing let routed/ silently drift from config.yaml, so you could fab a stale board. To guard against this, npm run ergogen stamps each board with a hash of config.yaml, and npm run fab checks that stamp (scoped to routed/) before it fabs, refusing a drifted or unstamped master. Run npm run validate:provenance any time to check without fabbing.
Clear a mismatch by re-running the pipeline (ergogen, copy:dist-to-unrouted, re-route, copy:unrouted-to-routed); when the existing routing still applies, npm run pipeline does that whole chain in one step. Caveat: only config.yaml is hashed, so a footprint .js or Ergogen-version change can move geometry without tripping the check, while a comment-only config edit trips a false "stale".
With the boards saved to routed/ (Step 4), npm run fab exports from kicad/routed/ into dist/v4/kicad/jlcpcb/<name>/:
- Gerbers + drill (
<name>-gerber.zip): the bare PCB. - BOM + CPL (
<name>-BOM.csv,<name>-CPL.csv): JLCPCB PCBA assembly files, generated only whenkicad/jlcpcb-parts.jsonis present. - DRC report (
<name>-drc.json): a headless DRC gate runs first and aborts the whole fab (no gerbers written) on any error-level violation or unrouted net.
After npm run fab, run npm run validate:fab to audit the outputs against design intent the DRC and provenance gates miss: that the routed master and the exported gerbers both carry a board-spanning GND ground plane, that the gerber zip has every expected layer plus drill, and that every assembled part made it into the BOM/CPL. It is step 7 of npm run pipeline. (This guards the failure where a board with no ground plane passed every other gate and still shipped.)
LCSC part numbers live in kicad/jlcpcb-parts.json, kept out of the .kicad_pcb so they survive Ergogen regen. Which parts JLCPCB places vs. which you hand-solder is version-specific; see the version README.
Ordering from JLCPCB: upload each <name>-gerber.zip for bare boards, plus the matching <name>-BOM.csv / <name>-CPL.csv for assembly. Check placement in JLCPCB's DFM viewer; fix a mis-oriented part via its rotation in the JSON and re-run.
npm run panelize combines left + right into one panel so JLCPCB's per-order assembly setup and stencil fees are paid once instead of twice. Worthwhile only for PCBA orders; skip it for bare boards. It exports gerbers + BOM/CPL into dist/v4/kicad/jlcpcb/panel/, with the per-half fab staying the strict DRC gate. Requires KiKit (git-master build for KiCad 10 support); point panelize.sh at its interpreter with KIKIT_PYTHON.
Step 6. Onshape
- In Onshape, create a document and start a sketch.
- Select "Insert a DXF or DWG file" > "Import ..." (bottom of the dialog) >
dist/v4/ergogen/outlines/full.dxf. - Design the case, then export
*.stepfiles toonshape/.
Step 7. OrcaSlicer
- Open or create an OrcaSlicer project.
- Import the
*.stepfiles fromonshape/. - Slice and print the case.
- Install an M2.5 heat-set insert (CNC Kitchen) into each mounting boss with a soldering iron, then clamp the PCB with the M2.5 screws (this is the recommended approach; the machined-aluminium alternative below taps the holes directly instead).
Instead of 3D printing, the case can be CNC-machined in aluminium via JLCCNC. Upload each half's *.step and set:
- Material: 6061 aluminium (JLCCNC's standard alloy; 6063 is the equivalent keyboard-typical option, marginally more even anodize color).
- Surface finish: Bead blasting + Anodizing (matte) is the recommended premium-keyboard finish: smooth, uniform, fingerprint resistant. For a glossier sheen, choose Anodizing without bead blasting. Pick a color (black is the safe default).
- Tolerance: the default (ISO 2768 medium) is fine for a case.
- Quantity: left and right are two separate mirrored parts, so set quantity per file.
- Threaded holes: tap the M2.5 mounting holes directly (the heat-set inserts in the BOM are only for the printed case). A STEP can't carry threads, so model each hole at the ~2.05mm tap-drill diameter and also upload a 2D drawing (PDF) with an
M2.5x0.45thread callout (depth, through/blind) for JLCCNC to tap to.
To make the thread drawing in Onshape:
- From the case Part Studio, create a Drawing and place a top (or section) view of the half.
- Add a Hole/thread callout (right-click the hole edge > Callout) on a mounting hole; it reads the hole and emits
M2.5x0.45. Add the thread depth and through/blind. - Export the drawing to PDF and upload it with the
.step.
Since every mounting hole shares one spec, a full dimensioned drawing is optional: a single PDF or screenshot noting "All mounting holes: M2.5x0.45 tapped, N mm deep" is accepted, and JLCCNC's order interface can also tag threaded holes in its 3D viewer. The STEP still needs the holes at ~2.05mm tap-drill either way.
Step 8. QMK Firmware
- Install the custom QMK firmware







