commit 4467aa9433491e01849bb66fb08bd0f678379d86 Author: William Rickert Date: Thu Aug 27 14:07:50 2026 -0500 Initial design record for the three-piece keyboard Two mirrored halves plus a numpad that carries the USB hub. Captures the architecture decisions and the reasoning behind them: CH32L103 with an STM32F103 pin-compatible escape hatch, split protocol tunnelled over USB-C SBU through the numpad, hotswap sockets bottom-side, and why crystal-less USB was ruled out across the whole WCH catalogue. No boards ordered, no firmware written. Layout still undesigned. Co-Authored-By: Claude Opus 5 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..314b58f --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# agent config — can contain live API tokens +.claude/ +.codex/ + +# KiCad +*-backups/ +*.kicad_prl +*.kicad_pcb-bak +*.kicad_sch-bak +*.net +fp-info-cache +\#auto_saved_files# +*.bak +*~ + +# fab output +gerbers/ +*.zip + +# firmware build +build/ +.build/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..5d933b9 --- /dev/null +++ b/README.md @@ -0,0 +1,221 @@ +# 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, 62–64 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 3–6 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. + +### Split link over USB-C SBU + +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](https://github.com/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.7–5.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 (1–9, 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). diff --git a/docs/104-key-split.svg b/docs/104-key-split.svg new file mode 100644 index 0000000..b94bce3 --- /dev/null +++ b/docs/104-key-split.svg @@ -0,0 +1,119 @@ + +Full 104-key keyboard split across three pieces +An ANSI 104-key layout colour-coded by which of three boards each key lands on: left half 36 keys, right half 39 keys, numpad piece 26 keys, and four keys not on any piece. +A 104-key layout, divided across the three boards +Esc +F1 +F2 +F3 +F4 +F5 +F6 +F7 +F8 +F9 +F10 +F11 +F12 +PrS +ScL +Pau +` +1 +2 +3 +4 +5 +6 +7 +8 +9 +0 +- += +Bksp +Ins +Hom +PgU +Num +/ +* +- +Tab +Q +W +E +R +T +Y +U +I +O +P +[ +] +\ +Del +End +PgD +7 +8 +9 ++ +Caps +A +S +D +F +G +H +J +K +L +; +' +Enter +4 +5 +6 +Shift +Z +X +C +V +B +N +M +, +. +/ +Shift + +1 +2 +3 +Ent +Ctrl +Win +Alt +Spc +Spc +Alt +Win +Mnu +Ctrl + + + +0 +. + +Left half · 36 keys + +Right half · 39 keys + +Numpad piece · 26 keys + +No board · layer only +A straight chop gives 36 vs 39 keys — but mirrored boards need matching counts.A real split trims to a symmetric core and reaches the rest by layer. + \ No newline at end of file diff --git a/docs/bom.csv b/docs/bom.csv new file mode 100644 index 0000000..a27fcfd --- /dev/null +++ b/docs/bom.csv @@ -0,0 +1,42 @@ +Board,Section,Ref,Qty,Value,MPN,LCSC,Package,JLC,Unit USD,Notes +ALL,MCU,U1,1,CH32L103C8T6,CH32L103C8T6,C22398291,LQFP-48(7x7),Extended,0.80,"MCU. Swap-compatible: STM32F103C8T6 = C8734 / Preferred / $1.44" +ALL,MCU,U2,1,3.3V LDO 500mA,RT9013-33GB-MS,C7434207,SOT-23-5,Extended,0.06,"Alt basic: XC6206P332MR-G C5446 but only 200mA" +ALL,MCU,U3,1,USB ESD,USBLC6-2SC6,C2687116,SOT-23-6,Extended,0.048,"on this board's own USB-C" +ALL,MCU,J1,1,USB-C 16P,TYPE-C16PIN,C393939,SMD,Extended,0.069,"upstream / device port" +ALL,MCU,J2,1,SWD header,-,-,1.27mm 4-pin,Consigned,0.00,"SWDIO=PA13 SWCLK=PA14 GND 3V3" +ALL,MCU,Y1,1,8MHz crystal,TAXM8M4RDBCCT2T,C400090,SMD3225-4P,Extended,0.146,"10pF load 250R ESR +/-10ppm. Satisfies both MCUs' USB PLL" +ALL,MCU,SW1,1,Tactile RESET,TS2306A 200gf,C5363482,SMD 3x2mm,Extended,0.062,"to NRST" +ALL,MCU,SW2,1,Tactile BOOT,TS2306A 200gf,C5363482,SMD 3x2mm,Extended,0.062,"BOOT0 to 3V3. Works for both boot paths" +ALL,MCU,C1-C5,5,100nF 0402 X7R 16V,CL05B104KO5NNNC,C1525,0402,Basic,0.006,"VDD_1 VDD_2 VDD_3 VDDA NRST" +ALL,MCU,C6,1,4.7uF 0603 X5R,CL10A475KO8NNNC,C19666,0603,Basic,0.024,"bulk at MCU" +ALL,MCU,C7,1,1uF 0402 X5R 25V,CL05A105KA5NQNC,C52923,0402,Basic,0.014,"VDDA" +ALL,MCU,C8-C9,2,10uF 0805 X5R,CL21A106KAYNNNE,C15850,0805,Basic,0.077,"LDO input and output" +ALL,MCU,C10-C11,2,12pF 0402 C0G,JLC basic library,-,0402,Basic,0.005,"MCU crystal load. 12pF assumes 10pF CL + ~4pF stray - verify vs stackup" +ALL,MCU,R1-R2,2,22R 0402,FRC0402F22R0TS,C2929994,0402,Extended,0.0007,"USB D+/D- series. 0R (C17168 Basic) also acceptable" +ALL,MCU,R3-R4,2,5.1k 0402,JLC basic library,-,0402,Basic,0.002,"USB-C CC1/CC2 pull-DOWN (Rd - device side)" +ALL,MCU,R5,1,1.5k 0402,JLC basic library,-,0402,Basic,0.002,"*** SWAP-DEPENDENT *** D+ pull-up. POPULATE for STM32F103. DNF for CH32L103 (internal 1.5k via RB_UC_DEV_PU_EN)" +ALL,MCU,R6-R7,2,10k 0402,FRC0402F1002TS,C25744,0402,Basic,0.003,"BOOT0 pulldown, BOOT1/PB2 pulldown" +HALF,Matrix,D1-Dn,1,1N4148W,1N4148W,C81598,SOD-123,Basic,0.011,"One per key. Qty per your layout" +HALF,Split,R14,1,100R 0402,JLC basic library,-,0402,Basic,0.002,"Series on the SBU net between connector and MCU USART pin" +HALF,Split,D-ESD,1,ESD 5V bidirectional,PESD5V0S1BA,C2827694,SOD-323,Extended,0.036,"On the SBU net - user-exposed signal going to a GPIO" +HALF,Split,J3,1,3-pin header DNF,JST SH,-,-,Consigned,0.02,"*** DNF INSURANCE *** VCC/GND/DATA fallback if SBU proves flaky (bad cables). 3 pads cost nothing" +HALF,Split,-,-,SBU1+SBU2 TIE,-,-,-,-,0.00,"Tie A8 and B8 together on the board. SBU is CROSSED in the cable, so this makes it orientation-proof and doubles the conductor" +NUMPAD,DNF,J1/U3/R3/R4/R5,0,DO NOT POPULATE,-,-,-,-,0.00,"*** From the ALL block: numpad MCU has NO connector of its own. Its D+/D- route internally to hub downstream port 1. No connector = no ESD part and no CC resistors needed. Saves ~$0.19" +NUMPAD,Matrix,D1-D26,26,1N4148W,1N4148W,C81598,SOD-123,Basic,0.011,"26 keys: 17 numpad + 9 nav" +NUMPAD,Encoder,ENC1,1,EC11 detented,EC11L1525G01,C2991196,THT,Extended,1.27,"VOLUME - wants detents. THT: hand-solder or JLC THT surcharge" +NUMPAD,Encoder,ENC2,1,EC11 detentless,verify variant,-,THT,Extended,1.27,"SCROLL - wants NO detents for hi-res scroll. Confirm detentless SKU" +NUMPAD,Hub,U4,1,USB2.0 4-port hub,CH334R,C4154405,QSOP-16-150mil,Extended,0.50,"Ports: 1=own MCU (internal FS) 2=left half 3=right half 4=spare" +NUMPAD,Hub,Y2,1,12MHz crystal,X322512MSB4SI,C9002,SMD3225-4P,Basic,0.101,"CH334 has built-in load caps - verify internal value vs this 20pF CL crystal; fit DNF cap pads" +NUMPAD,Hub,C12-C14,3,100nF 0402,CL05B104KO5NNNC,C1525,0402,Basic,0.006,"hub decoupling" +NUMPAD,Hub,C15,1,10uF 0805,CL21A106KAYNNNE,C15850,0805,Basic,0.077,"hub bulk" +NUMPAD,Hub,J4,1,USB-C upstream,TYPE-C16PIN,C393939,SMD,Extended,0.069,"to PC. This replaces J1 as the board's real upstream" +NUMPAD,Hub,J5-J7,3,USB-C downstream,TYPE-C16PIN,C393939,SMD,Extended,0.069,"left half / right half / spare" +NUMPAD,Hub,U5-U7,3,USB ESD,USBLC6-2SC6,C2687116,SOT-23-6,Extended,0.048,"one per downstream port" +NUMPAD,Hub,U8-U10,3,Ideal diode / OCP,CH213K,C5456874,SOT-23-3,Extended,0.073,"VBUS protection per downstream port. CH334R has no port power switching" +NUMPAD,Hub,R8-R13,6,56k 0402,FRC0402F5602TS,C2906875,0402,Extended,0.0018,"Rp pull-UPS on CC1/CC2 of each downstream port - host side. NOT 5.1k" +NUMPAD,SBU,-,-,SBU PASS-THROUGH,-,-,-,-,0.00,"*** Passive trace *** Tie SBU1+SBU2 on J5 (left half) and on J6 (right half), then connect the two nets together. Carries QMK split_common UART between the halves. Numpad MCU does NOT touch this net" +NUMPAD,SBU,D-ESD2,2,ESD 5V bidirectional,PESD5V0S1BA,C2827694,SOD-323,Extended,0.036,"One per half-port SBU net" +HALF,Switches,SW-,1,MX hotswap socket,CPG151101S11,C41430893,SMD 14.5x5.9mm,Extended,0.037,"One per key. BOTTOM SIDE. Alt: C49352235 (CPG151101S11-2)" +NUMPAD,Switches,SW-,26,MX hotswap socket,CPG151101S11,C41430893,SMD 14.5x5.9mm,Extended,0.037,"One per key. BOTTOM SIDE" +NOTE,Assembly,-,-,SINGLE-SIDED,-,-,-,-,-,"Put ALL SMD on the BOTTOM: sockets, MCU, passives, diodes, and any reverse-mount SK6812MINI-E. Top face bare except switch holes. Keeps JLC to one assembly pass" +NOTE,Mechanical,-,-,PLATE REQUIRED,-,-,-,-,-,"Hotswap needs a switch plate or socket pads tear off during switch insertion. FR4 plate = just another JLC board"