William RickertandClaude Opus 5 2a6dc40f2e Add datasheets for the parts actually in use
CH32L103 datasheet and reference manual, plus the CH334/335 hub datasheet.
Documents/README.md notes which section of each was load-bearing, and flags
the missing STM32F103C8T6 datasheet needed for the pin-number overlay.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 14:17:47 -05:00

tertiary

A three-piece keyboard: two mirrored halves plus a numpad/nav/media board that also carries the USB hub. One cable reaches the computer.

Status: design. No boards ordered, no firmware written.


The three boards

Board Qty Keys Carries
hardware/half 2 (mirrored) TBD — layout not designed MCU, matrix, USB-C, SBU split link
hardware/numpad 1 26 (17 numpad + 9 nav) MCU, matrix, 2 encoders, CH334R hub, 3 downstream ports

Cabling:

PC ──USB-C── numpad ──USB-C── left half
                     └─USB-C── right half

Both halves plug into the numpad. The numpad's hub gives each half a USB port, and a passive trace across the numpad shorts SBU between the two half ports, which is how QMK's split protocol gets from one half to the other. See below.


Decisions, and why

MCU: CH32L103C8T6, with an STM32F103C8T6 escape hatch

C22398291, LQFP-48, 6264 K flash / 20 K SRAM, QingKe RISC-V4C.

QMK does not support CH32. Verified: zero issues or PRs mentioning CH32 in qmk/qmk_firmware ever, and no os/hal/ports/CH32 in ChibiOS-Contrib. Adding it means a ChibiOS HAL port — PAL, ST, and critically a usb_lld for WCH's own USBFS peripheral. Estimated 36 weeks of evenings; the USB driver is ~75% of it.

So the board is laid out to the STM32F103C8T6 (C8734) footprint, which the L103 is pin-compatible with (same LQFP-48, PC13/PC14/PC15, PD0/PD1 as OSC_IN/OSC_OUT, PA11/PA12 as USB DM/DP, PA13/PA14 as SWDIO/SWCLK). Populate the F103 and it works with stock QMK today; populate the L103 when the port lands. Same board either way.

⚠️ Unverified assumption the whole board rests on: pin names and functions were confirmed identical from both datasheets, but the pin numbering was not — the PDFs interleave four packages and extract badly. Overlay the two pinout drawings before routing.

Only one component differs between the two MCUs: R5, a 1.5 kΩ pull-up on D+. The F103 has no internal one. The L103 does (~1.5 kΩ, enabled via RB_UC_DEV_PU_EN in R8_USB_CTRL) — so populate R5 for F103, DNF for L103.

Buy three loose F103s and keep them in a drawer. When the first board doesn't enumerate, hot-air one over and flash stock QMK: that tells you instantly whether the fault is the PCB or your USB driver. Debugging both at once is where this project would die.

Why not RP2040

No internal flash, so it needs an external QSPI chip, a crystal, and fussy decoupling — ~24 line items versus ~16. Modules (RP2040-Zero, $3.99) were the first plan and are still viable, but bare silicon gives USB-C placement freedom and no single-supplier risk.

Why not crystal-less

Nothing in WCH's catalogue does spec-compliant crystal-less USB. Checked V203, X035 and L103: all use factory-calibrated RC with manual HSITRIM and no SOF-based clock recovery anywhere in the family. Accuracies run 1.0/+1.6 % (V203), 1.7/+1.6 % (X035), ±1.6 % rising to ±2.2 % over full range (L103). USB full-speed wants ±0.25 %.

WCH's own datasheet admits it: crystal-free "may deviate from the USB specification, and are only applicable to non-precision occasions", and it's a factory ordering option not enabled by default on stock parts.

The only genuinely crystal-less option found was STM32F0x2 (HSI48 + CRS, actively trimmed against host SOF packets) — rejected on stock: 773 units.

An 8 MHz crystal plus two caps is $0.12 and satisfies both MCUs' USB PLL.

Hub in the numpad, not the right half

Keeps both halves the same design. The numpad is a one-off anyway and it's the roomiest board. Cost: the numpad must be plugged in for the halves to reach the computer — but each half has a complete USB stack, so any half can be plugged directly into a machine when travelling.

CH334R (C4154405, QSOP-16, $0.50), 4 ports mapping exactly:

Port Destination
Upstream PC
1 numpad's own MCU — internal trace, no connector
2 left half
3 right half
4 spare external

CH334R has no port power switching (dropped in the 16-pin package), so each downstream port gets a CH213K ideal diode for overcurrent — WCH's own recommendation in the CH334 reference design.

Downstream ports are host-side: 56 kΩ Rp pull-ups on CC, not 5.1 kΩ Rd pull-downs. Getting this backwards means nothing enumerates.

The 16-pin USB-C connector breaks out SBU1 (A8) and SBU2 (B8) and nothing uses them. Each half ties SBU1+SBU2 together — SBU is crossed inside the cable, so tying them makes the link orientation-proof and doubles the conductor — and runs it through 100 Ω + ESD to a USART pin. The numpad shorts the two half-ports' SBU nets with a passive trace.

Result: QMK split_common runs between the halves with no extra cable and no TRRS.

⚠️ Requires a full-featured USB-C cable. Cheap USB 2.0 charge cables omit SBU entirely. Failure mode is nasty: power and USB work, only the split dies. Put "USE A FULL-FEATURED CABLE" on the silkscreen.

A 3-pin JST footprint is carried on both halves as DNF insurance if SBU proves unreliable.

Hotswap sockets, everything bottom-side

Kailh MX CPG151101S11 (C41430893, $0.037, 93 k in stock — they are in JLC's library). Sockets mount on the back, so put all SMD on the bottom: MCU, passives, diodes, sockets. Single-sided assembly, one JLC setup instead of two.

A plate is mandatory. Without one, switch insertion flexes the PCB and tears socket pads off. FR4 plate is just another JLC board.

Use perigoso/keyswitch-kicad-library for footprints — don't draw them.

Mirrored board files, not a reversible PCB

The socket footprint isn't mirror-symmetric, so a reversible board needs two socket footprints per key with one populated. Two mirrored layouts sharing one schematic and one BOM is simpler and keeps everything that mattered.

RGB: numpad underglow only

Per-key RGB is arithmetically impossible on a bus-powered hub. Each half sits on a downstream port capped at 100 mA; 42 SK6812MINI-E at full white is 1.5 A, and even 10 % brightness overruns the port. Real per-key RGB needs a self-powered hub with an external supply — and probably a bigger CH334 variant, since the small packages don't expose PSELF.

Numpad has ~355 mA to itself: about 9 LEDs at full white or ~30 at 30 %.

If any SK6812 is fitted: 3.3 V data into a 5 V part is out of spec (Vih ≈ 3.5 V). SK6812MINI-E is rated 3.75.5 V, so run the LED rail at ~4.3 V through a series diode — Vih drops to ~3.0 V and you stay in spec.


Firmware notes

Nothing written yet. Things already known:

Do not use SPLIT_USB_DETECT. Both halves are plugged into the hub, so both would see VBUS and both would decide they're master. Set MASTER_LEFT (or MASTER_RIGHT) at compile time. The peripheral still enumerates as an idle HID device — expected, not a fault.

Split pin: PA9 (USART1_TX) exists on both MCUs. QMK's half-duplex serial_usart uses TX only, open-drain with pull-up.

Numpad Num Lock handling. Num Lock is global host state on Windows and Linux; with it off, KP_7 sends Home and the pad silently becomes a duplicate of the nav cluster three inches away. macOS has no functional Num Lock (Apple keyboards use Clear) and the pad always types digits there.

Plan is belt-and-braces:

  1. One attempt per power-up to turn Num Lock on, fired from housekeeping_task_user (never from inside led_update_user — an unbounded loop there has been reported to brick a Moonlander), after a 500 ms settle so a transient enumeration report doesn't trigger a false correction.
  2. Regardless of whether that took, resolve each keypad key at press time against host_keyboard_led_state().num_lock and emit the number-row keycode when Num Lock is off. Latch what was registered so the release matches if state flips mid-hold.

Only eleven keys need this (19, 0, .). KP_SLASH/ASTERISK/MINUS/PLUS/ ENTER mean the same thing either way.

Encoders: volume wants a detented EC11; scroll wants a detentless one, because hi-res scrolling (POINTING_DEVICE_HIRES_SCROLL_ENABLE, resolution multiplier, 1/120 tick) fights physical detents. Confirm the detentless SKU from the datasheet — vendor descriptions are unreliable.


Open questions

  • The layout. Undesigned, and it's the only input the PCB can't defer. If a thumb layer on one half reaches keys on the other, the halves must be a QMK split. If each half is self-sufficient, they can be three independent keyboards. The hardware supports both — see docs/104-key-split.svg for how a 104-key layout maps onto three boards, and note a straight chop gives 36 vs 39 keys, so a symmetric core plus layers is needed for mirrored boards.
  • Pin-number overlay of L103 vs F103 (above).
  • CH334 internal crystal load-cap value vs the 20 pF-CL crystal — fit DNF cap pads.
  • Whether AT32F415 is a better fallback than F103 (128 K/32 K vs 64 K/20 K, same price).

TODO before this repo is made public

Per house convention. This one is unusually clean:

  • Nothing here touches LAN topology, credentials, or personal data — no scrubbing needed, unlike most repos here.
  • Confirm no JLC/LCSC account identifiers end up in fab exports.
  • Decide on a licence before publishing hardware files (CERN-OHL-S or TAPR OHL for hardware; GPL2 for anything derived from QMK).
S
Description
No description provided
Readme
5.2 MiB
Languages
SVG 100%