Files
William RickertandClaude Opus 5 49ad7e97cf Verify the L103/F103 pin overlay: all 48 identical
Parsed the pin-definition tables out of both datasheets - Table 2-1-1 (CH32L103,
C8T6 column) and Table 2 (STM32F103, LQFP48 column) - and diffed them pin by
pin. Zero mismatches, including OSC_IN/OSC_OUT, NRST, PA11/PA12 for USB,
PA13/PA14 for SWD, and BOOT0. Power pins differ only in naming.

This was the one unverified assumption the whole project rested on: three
boards laid out to a footprint that has to accept either MCU. It holds.

Full table recorded in Documents/pinout-L103-vs-F103.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 10:42:07 -05:00

362 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# tertiary
A three-piece keyboard: two column-staggered 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.
![All three boards to scale](Documents/three-board-overview.svg)
<table>
<tr>
<td><img src="Documents/half-left-staggered.svg" alt="Left half, 34 keys"></td>
<td><img src="Documents/half-right-staggered.svg" alt="Right half, 41 keys"></td>
</tr>
</table>
![Numpad layout](Documents/numpad-layout.svg)
Derived from a full 104 rather than a compact ergo core — see
[`Documents/104-key-split.svg`](Documents/104-key-split.svg) for how the original layout
maps across the three boards.
## Licence
**Hardware and documentation: [CC BY-NC-SA 4.0](LICENSES/CC-BY-NC-SA-4.0.txt).**
Build one, modify it, share your modifications under the same terms — but no
commercial products.
**Firmware: [GPL-2.0-or-later](LICENSES/GPL-2.0.txt)**, forced by QMK, which
permits commercial use. See [`LICENSE.md`](LICENSE.md) for why the two can't be
the same, and for the caveats on what "NonCommercial" actually means.
The vendor PDFs inside `Documents/` (WCH, ST) are third-party and aren't covered by
either.
---
## The three boards
| Board | Qty | Keys | Carries |
|---|---|---|---|
| `Schematics/Left` | 1 | **34** | MCU, matrix, USB-C, SBU split link |
| `Schematics/Right` | 1 | **41** | MCU, matrix, USB-C, SBU split link |
| `Schematics/Numpad` | 1 | **26** (14 numpad + 10 nav + 2 thumb) | MCU, matrix, **CH334R hub**, 3 downstream ports |
**Numpad layout is settled** — see `Documents/numpad-layout.svg`, and
`Documents/three-board-overview.svg` for all three at the same scale. Six
columns by five rows, ~114 × 105 mm, **26 keys and no encoder**.
- **Nav block across the top**: a solid 3×2 — Home/End/PgUp over Del/PrtSc/PgDn
— with the arrows in a standard **inverted-T** isolated below by a symmetric
pair of empty cells, the same breathing room a 104 gives them. Del sits two
rows above `←` with a gap between, so the destructive key is never adjacent to
the one you hit blind.
- **PrtSc has a real key.** It's the screenshot key on every Linux desktop, so
it earns one. Insert, ScrLk and Pause do not, and stay on a layer.
- **Numpad on the right**: digits in a 3×3 with `0` as a 2u, and `/ * ` in a
right-hand column beside 9/6/3, where a full-size numpad puts its operators.
- **`Enter` (2u) and `+` on a stepped thumb cluster**, spanning exactly the
three nav columns. That's what lets the digits drop from four columns to
three and keeps the board at six wide.
- **No Num key** — the firmware resolves keypad vs number-row at press time, so
the pad never needed one.
- **No encoder.** Dropped along with the scroll wheel; volume lives on a layer
or on the halves' function row. This makes the numpad a completely plain QMK
keyboard — no encoder, no pointing device, just a matrix. It is the simplest
of the three boards by a distance, and ~$4.16 in parts.
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.
> ✅ **Verified 2026-09-01: all 48 pins identical, zero mismatches.** Both
> datasheets' pin-definition tables were parsed and diffed pin by pin — including
> `OSC_IN`/`OSC_OUT`, `NRST`, `PA11`/`PA12` (USB), `PA13`/`PA14` (SWD) and
> `BOOT0`. Power pins differ only in naming (`VSS` vs `VSS_1..3`). Full table:
> [`Documents/pinout-L103-vs-F103.md`](Documents/pinout-L103-vs-F103.md).
> This was the one assumption the whole project rested on. It holds.
**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.
### The halves are two different boards
Splitting a full 104 gives **34 keys left, 41 right** (after moving `6`/`F6` right) —
the right side inherits Backspace, Enter, both brackets, the backslash and an
extra bottom-row key. They are therefore *not* mirrors, and the earlier plan of
one layout flipped for both halves does not survive.
Two separate designs: two schematics, two layouts, two fab setups, different
diode and socket counts. A reversible PCB was never on the table anyway — the
hotswap socket footprint isn't mirror-symmetric, so it would have needed two
socket footprints per key with one populated.
See `Documents/half-left.svg` and `Documents/half-right.svg`.
### Column stagger, not row stagger
A 104's row offsets are a typewriter artifact — mechanical linkages needed the
room, hands didn't. Since these are new PCBs, the columns get offset vertically
to match finger length instead: middle column highest, pinky and inner-reach
columns down ~0.4u.
`Documents/half-left-staggered.svg` is the left half rebuilt this way. **Same 36
keys**, and it resolves into a clean 7-column grid:
| Column | Holds |
|---|---|
| 0 (outer) | Esc, `, Tab, Caps, Shift |
| 15 | the finger columns, 5 keys each |
| 6 (inner reach) | F6, 6 — only two keys |
That last column is honest rather than a defect: on a 104, `6` and `F6` really
are an index-finger stretch, and the stagger makes it visible.
Side effect worth having: it comes out **~7u wide instead of 8.5u** (~134 mm vs
~162 mm), because the function row no longer carries the 104's arbitrary gaps.
Narrower *and* better.
### Thumb clusters
Both spacebars break into stepped thumb keys. On the left that's Ctrl / Win /
Alt / Space stepping down and inward. This is where the pinky relief actually
comes from — a stock 104 puts Tab, Caps, Shift *and* Ctrl on the weakest finger.
⚠️ Only about **two or three** of the four are genuinely comfortable; the
outermost is at the edge of thumb travel. Consider dropping Ctrl to a home-row
mod and reclaiming that slot for a layer key.
The right is worse: **five** thumb keys (Space / Alt / Win / Menu / Ctrl). One
thumb will not cover five. Menu is the obvious deletion — nothing else on the
board is used less.
`Documents/half-right-staggered.svg`.
### The outer edge — resolved
Splitting a row-staggered 104 leaves keys with no column to belong to: the
number row and the alpha rows are cut half a unit apart, so `6` sits between `T`
and `Y` with no alpha beneath it.
**Resolved by shifting the right half's top two rows over one column**, so `6`
leads the number row and `F6` leads the function row, both sitting above `Y`.
- **Left: 34 keys, six columns, every one five deep.** No orphans at all.
- **Right: 41 keys.** Six full columns, then `F12`/`=`/`]`/`Enter`, then `Bksp`
and `\` sharing the outer pinky column.
`\` no longer sits alone two columns out — it pairs with Backspace, and both
are genuine outer-pinky reaches. `Documents/half-left-staggered.svg`,
`Documents/half-right-staggered.svg`.
Still open: the right thumb is carrying **five** keys (Space / Alt / Win / Menu
/ Ctrl) and one thumb will not cover five. Menu is the obvious deletion.
### Tenting
Tenting hardware is a CAD/case job and comes after the PCB — but the **mounting
holes are PCB**, and where they go depends on the case scheme. Decide sandwich
vs tray *before routing*, or you'll find the case wants a standoff exactly where
a switch or a matrix trace already is. Holes can't be added later without a
respin.
### 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.
**No encoder anywhere.** The scroll wheel went first, then the volume knob. That
strips a surprising amount out of the numpad's firmware:
- No `ENCODER_ENABLE`, no `POINTING_DEVICE_ENABLE`, no `MOUSEKEY_ENABLE`, no
hi-res scroll multiplier.
- The numpad **enumerates as a plain keyboard and nothing else** — no mouse
interface, no consumer-control interface unless media keys go on a layer.
- One fewer USB endpoint, which matters because the L103's USBFS has eight and
QMK's composite device was tight against that.
- The detentless-EC11 sourcing risk is gone, because there is no EC11.
Volume has to live on a layer, or on the halves' function row. Decide that
rather than discover it.
---
## 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 `Documents/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.
- [x] Scanned: no LAN topology, credentials or personal data in files or history.
- [x] Licence chosen: CC BY-NC-SA 4.0 (hardware) + GPL-2.0 (firmware).
- [ ] Vendor datasheets in `Documents/` are redistributed third-party PDFs.
Consider linking instead of vendoring if that matters to you.