pixalynx/keyboard
This code defines the physical layout and key components of a USB-C dongle featuring an nRF52840 microcontroller with antenna, USB-A connector, status LED, pair button, firmware debug test points, and associated passive components and interface connectors.
- Version
- 1.0.0
- License
- unset
- Stars
- 0
index.circuit.tsx
PCB
Schematic
import { Fragment } from "react"
import { E73_2G4M08S1C } from "./imports/E73_2G4M08S1C"
import { KeyHotSocket } from "@tsci/seveibar.KeyHotSocket"
import { KeyShaftForHotSocket } from "@tsci/seveibar.KeyShaftForHotSocket"
import { WS2812C_2020_V1 } from "./imports/WS2812C_2020_V1"
import { A_1N4148WS } from "./imports/A_1N4148WS"
import { SN74AHCT1G125DBVR } from "./imports/SN74AHCT1G125DBVR"
import { TYPE_C_16PIN_2MD_073 } from "./imports/TYPE_C_16PIN_2MD_073"
import { TP4056_42_ESOP8 } from "./imports/TP4056_42_ESOP8"
import { S2B_PH_SM4_TB_LF__SN_ as JST_PH2_SMD } from "./imports/S2B_PH_SM4_TB_LF__SN_"
import { SS14L } from "./imports/SS14L"
import { MSK12C02 } from "./imports/MSK12C02"
import { SY6280AAC } from "./imports/SY6280AAC"
import { MT3608 } from "./imports/MT3608"
import { SWPA4030S220MT } from "./imports/SWPA4030S220MT"
import { TS_1187A_B_A_B } from "./imports/TS_1187A_B_A_B"
import { USBLC6_2SC6 } from "./imports/USBLC6_2SC6"
/*
* FALCHION-65: a 65% wireless RGB keyboard, edge-to-edge.
*
* - 68-key 65% layout (16u x 5u), hot-swap MX sockets
* - per-key RGB: a WS2812C-2020 on the top copper under every switch's LED
* lens, daisy-chained in a serpentine, fed from a switched 5 V boost
* rail through a 74AHCT1G125 level shifter
* - nRF52840 (Ebyte E73-2G4M08S1C). One radio, two link modes: BLE for
* everyday use, Nordic 2.4 GHz to the companion dongle for gaming. No
* extra RF hardware is needed for either -- it is a firmware choice.
* - Li-Po: TP4056 charger, power slide switch, Schottky OR into VSYS,
* which feeds the nRF52840's VDDH so its on-chip REG0 makes the 3.3 V
* rail (no external LDO, as on the nice!nano)
* - Windows / macOS slide switch on a GPIO, read by the firmware to swap
* the modifier layout
*
* BOARD SIZE: 306 x 102 mm against a 304.8 x 95.25 mm key block, i.e. 0.6 mm
* of side margin and no dedicated component strips at all. Everything lives
* in space the key grid already wastes:
*
* top band y 43.0 .. 51.0 -- above the row-0 socket bodies and out to
* the back edge. USB-C, charger, level shifter, both slide
* switches. Top side only.
* inter-row y 24.0 .. 30.8 and y -33.3 .. -25.8 -- full-width bands
* bands between one row's LEDs and the next row's sockets, free on
* both layers. Mounting holes go here.
* under the the space bar is 6.25u wide but its socket is only 16 mm,
* space bar leaving two large bottom-side pockets: region A at
* x -85..-30 and region B at x -14..39, both y -27..-51.
* Region A holds the radio module, reset and SWD; region B
* holds the LED boost and the battery connector.
*
* Everything is placed with absolute board coordinates on purpose: nested
* <group>s were being auto-packed by tscircuit's board layout, which moved
* the key cells away from their intended positions.
*/
const U = 19.05 // key pitch (mm)
const BOARD = { width: 306, height: 102 }
const TOP_Y = BOARD.height / 2 // 51
const KB_X0 = -152.4 // left edge of the key block
const KB_Y0 = 46 // top edge of the key block
const TOP_BAND_Y = 47.8 // centre line for top-band parts
// Offsets inside one key cell, relative to the hot-swap socket origin. The
// seveibar KeyHotSocket footprint puts the MX stem at (0.635, -3.7) from its
// own origin (derived from the socket's two 3 mm locating holes), so every
// socket is shifted by the opposite amount to land the stem on the key's
// true centre.
const STEM = { x: 0.635, y: -3.7 }
const SHAFT_OFF = { x: 0, y: -0.52 } // 4.2 mm stem hole, as in seveibar.Key
const LABEL_OFF = { x: 0, y: 5 }
// MX RGB switches carry their LED lens 5.08 mm "south" of the stem; the
// 2020 LED sits on the top copper under that lens.
const LED_OFF = { x: STEM.x, y: STEM.y - 5.08 }
// Bottom side, either side of the LED and clear of the socket pads: cap to
// the left, row diode to the right. Both stay inside the cell, so the cell
// pitch alone sets the board size.
const LED_CAP_OFF = { x: STEM.x - 6.2, y: LED_OFF.y }
const DIODE_OFF = { x: STEM.x + 6.0, y: STEM.y - 4.8 }
type KeyDef = { name: string; label: string; w?: number }
const k = (name: string, label: string, w = 1): KeyDef => ({ name, label, w })
// Falchion-style 65% (ANSI): 15 / 15 / 14 / 14 / 10 keys, every row 16u.
const ROWS: KeyDef[][] = [
[
k("ESC", "Esc"), k("N1", "1"), k("N2", "2"), k("N3", "3"), k("N4", "4"),
k("N5", "5"), k("N6", "6"), k("N7", "7"), k("N8", "8"), k("N9", "9"),
k("N0", "0"), k("MINUS", "-"), k("EQUAL", "="), k("BSPC", "Backspace", 2),
k("DEL", "Del"),
],
[
k("TAB", "Tab", 1.5), k("Q", "Q"), k("W", "W"), k("E", "E"), k("R", "R"),
k("T", "T"), k("Y", "Y"), k("U", "U"), k("I", "I"), k("O", "O"), k("P", "P"),
k("LBRC", "["), k("RBRC", "]"), k("BSLS", "\\", 1.5), k("PGUP", "PgUp"),
],
[
k("CAPS", "Caps", 1.75), k("A", "A"), k("S", "S"), k("D", "D"), k("F", "F"),
k("G", "G"), k("H", "H"), k("J", "J"), k("K", "K"), k("L", "L"),
k("SCLN", ";"), k("QUOT", "'"), k("ENT", "Enter", 2.25), k("PGDN", "PgDn"),
],
[
k("LSFT", "Shift", 2.25), k("Z", "Z"), k("X", "X"), k("C", "C"), k("V", "V"),
k("B", "B"), k("N", "N"), k("M", "M"), k("COMM", ","), k("DOT", "."),
k("SLSH", "/"), k("RSFT", "Shift", 1.75), k("UP", "Up"), k("END", "End"),
],
[
k("LCTL", "Ctrl", 1.25), k("LGUI", "Win", 1.25), k("LALT", "Alt", 1.25),
k("SPC", "Space", 6.25), k("RALT", "Alt"), k("FN", "Fn"), k("RCTL", "Ctrl"),
k("LEFT", "Left"), k("DOWN", "Down"), k("RGHT", "Right"),
],
]
type PlacedKey = KeyDef & { row: number; col: number; x: number; y: number; led: number }
// Resolve physical centres and matrix row/col. The LED chain snakes across
// the board (row 0 left->right, row 1 right->left, ...) so each DOUT->DIN
// hop is a single key pitch.
const KEYS: PlacedKey[] = []
{
let led = 0
ROWS.forEach((row, r) => {
let xu = 0
const placed: PlacedKey[] = row.map((key, c) => {
const w = key.w ?? 1
const p: PlacedKey = {
...key,
row: r,
col: c,
x: KB_X0 + (xu + w / 2) * U,
y: KB_Y0 - (r + 0.5) * U,
led: -1,
}
xu += w
return p
})
const chain = r % 2 === 0 ? placed : [...placed].reverse()
chain.forEach((p) => {
p.led = led++
})
KEYS.push(...placed)
})
}
const N_ROWS = ROWS.length
const N_COLS = Math.max(...ROWS.map((r) => r.length))
/* Cherry PCB-mount stabilisers. Geometry from the daprice/keyswitches.pretty
Stabilizer_MX_2u footprint (KiCad, y-down, converted here to y-up): per
stem a 3.9878 mm non-plated hole 8.255 mm south of the key centre and a
3.048 mm one 6.985 mm north of it. Stems sit +/-11.9 mm from the key
centre for 2u / 2.25u keys and +/-50 mm for the 6.25u space bar. Every
per-cell part stays inside +/-8.2 mm of its key, so the 2u/2.25u holes
fall in copper-free space; the space-bar stems are the constraint on the
two bottom-side pockets. */
const STAB_STEM_OFFSET: Record<string, number> = { "2": 11.9, "2.25": 11.9, "6.25": 50 }
const STAB_HOLES = KEYS.flatMap((key) => {
const off = STAB_STEM_OFFSET[String(key.w ?? 1)]
if (!off) return []
return [-1, 1].flatMap((side) => [
{ key: key.name, side, x: key.x + side * off, y: key.y - 8.255, d: 3.9878 },
{ key: key.name, side, x: key.x + side * off, y: key.y + 6.985, d: 3.048 },
])
})
const rowNet = (r: number) => `net.ROW${r}`
const colNet = (c: number) => `net.COL${c}`
/* nRF52840 pin plan (E73 module pin names). P0.02-P0.05 and P0.28-P0.31 are
the only ADC-capable pins, so P0.04 is reserved for battery sensing and the
rest are spread across the matrix. P0.00/P0.01 (32 kHz crystal) and
P0.09/P0.10 (NFC) are left unused so no special firmware configuration is
needed. Spare after this: P0.05, P1.06, P1.13, P0.00, P0.01, P0.09, P0.10. */
const COL_PINS = [
"P0_02", "P0_03", "P0_28", "P0_29", "P0_30", "P0_31", "P0_06", "P0_07",
"P0_08", "P0_12", "P0_13", "P0_15", "P0_17", "P1_09", "P1_11",
]
const ROW_PINS = ["P0_20", "P0_22", "P0_24", "P0_26", "P1_00"]
const LED_DATA_PIN = "P1_02"
const LED_EN_PIN = "P1_10" // enables the LED power path (load switch + boost)
const OS_SEL_PIN = "P1_04" // Windows / macOS slide switch, internal pull-up
const VBAT_SENSE_PIN = "P0_04" // AIN2, via the R8/R9 divider
const mcuMatrixPins: Record<string, string> = {}
COL_PINS.slice(0, N_COLS).forEach((pin, c) => (mcuMatrixPins[pin] = colNet(c)))
ROW_PINS.slice(0, N_ROWS).forEach((pin, r) => (mcuMatrixPins[pin] = rowNet(r)))
// Schematic layout (units). Sections are clustered by hand.
const SCH = {
Usb: { x: -22, y: 8 },
Charger: { x: -22, y: -2 },
Battery: { x: -22, y: -12 },
Mcu: { x: 0, y: 0 },
LedDriver: { x: 12, y: 8 },
LedPower: { x: 24, y: 8 },
Debug: { x: 24, y: -6 },
Matrix: { x: -22, y: -24, dx: 4.5, dy: 4.5 },
Leds: { x: -22, y: -50, dx: 4.2, dy: 4, perRow: 17 },
}
/* Mounting holes live in the two full-width bands between key rows, where
one row's LEDs have ended and the next row's sockets have not started. */
const BAND_HI = 27.3 // between rows 0 and 1
const BAND_LO = -29.5 // between rows 3 and 4
const MOUNT_HOLES: [number, number][] = [
[-149, BAND_HI], [-75, BAND_HI], [0, BAND_HI], [75, BAND_HI], [149, BAND_HI],
[-149, BAND_LO], [-110, BAND_LO], [60, BAND_LO], [110, BAND_LO], [149, BAND_LO],
]
// SWD / reset test points, a column on the bottom side beside the module.
const TESTPOINTS = [
{ name: "TP1", net: "net.SWDIO", y: -31 },
{ name: "TP2", net: "net.SWDCLK", y: -34 },
{ name: "TP3", net: "net.NRST", y: -37 },
{ name: "TP4", net: "net.V3V3", y: -40 },
{ name: "TP5", net: "net.GND", y: -43 },
]
export default () => (
<board width={`${BOARD.width}mm`} height={`${BOARD.height}mm`} thickness="1.6mm" routingDisabled>
<net name="GND" isGroundNet />
<net name="VBUS" isPowerNet />
<net name="VBAT" isPowerNet />
<net name="VSYS" isPowerNet />
<net name="V3V3" isPowerNet />
<net name="LED_5V" isPowerNet />
<schematicsection name="Usb" displayName="USB-C" />
<schematicsection name="Charger" displayName="TP4056 Li-Po Charger" />
<schematicsection name="Battery" displayName="Battery, Power Switch, Gauge" />
<schematicsection name="Mcu" displayName="nRF52840 (E73-2G4M08S1C)" />
<schematicsection name="LedDriver" displayName="RGB Data Level Shifter" />
<schematicsection name="LedPower" displayName="Switched 5 V LED Boost" />
<schematicsection name="Debug" displayName="Reset + SWD + OS Switch" />
<schematicsection name="Matrix" displayName="Key Matrix (68 keys)" />
<schematicsection name="Leds" displayName="Per-Key RGB (WS2812C-2020)" />
{MOUNT_HOLES.map(([x, y]) => (
<Fragment key={`mh-${x}-${y}`}>
<hole diameter="2.2mm" pcbX={x} pcbY={y} />
</Fragment>
))}
{/* Stabiliser cutouts: Backspace, Enter, left Shift, space bar */}
{STAB_HOLES.map((h) => (
<Fragment key={`stab-${h.key}-${h.side}-${h.d}`}>
<hole diameter={`${h.d}mm`} pcbX={h.x} pcbY={h.y} />
</Fragment>
))}
<silkscreentext text="FALCHION-65 RGB · nRF52840 / BLE + 2.4GHz" pcbX={60} pcbY={TOP_Y - 2} fontSize="1.8mm" />
{/* ============================ USB-C (back edge) ============================ */}
{/* Mating face sits ~0.7 mm inside the top edge; the contact pads land at
y ~44.8, above the row-0 socket bodies, on the top side only. */}
<TYPE_C_16PIN_2MD_073
name="J1"
pcbX={-40}
pcbY={TOP_Y - 4.1}
pcbRotation={180}
schSectionName="Usb"
schX={SCH.Usb.x}
schY={SCH.Usb.y}
/>
<resistor name="R1" resistance="5.1k" footprint="0402" pcbX={-49} pcbY={TOP_BAND_Y} pcbRotation={90} schSectionName="Usb" schX={SCH.Usb.x + 5} schY={SCH.Usb.y + 1.5} />
<resistor name="R2" resistance="5.1k" footprint="0402" pcbX={-32} pcbY={TOP_BAND_Y} pcbRotation={90} schSectionName="Usb" schX={SCH.Usb.x + 5} schY={SCH.Usb.y - 1.5} />
<trace from="J1.VBUS1" to="net.VBUS" thickness="0.5mm" />
<trace from="J1.VBUS2" to="net.VBUS" thickness="0.5mm" />
<trace from="J1.GND1" to="net.GND" />
<trace from="J1.GND2" to="net.GND" />
<trace from="J1.SHELL1" to="net.GND" />
<trace from="J1.SHELL2" to="net.GND" />
<trace from="J1.SHELL3" to="net.GND" />
<trace from="J1.SHELL4" to="net.GND" />
<trace from="J1.CC1" to="R1.pin1" />
<trace from="R1.pin2" to="net.GND" />
<trace from="J1.CC2" to="R2.pin1" />
<trace from="R2.pin2" to="net.GND" />
<trace from="J1.DP1" to="net.USB_DP" />
<trace from="J1.DP2" to="net.USB_DP" />
<trace from="J1.DM1" to="net.USB_DM" />
<trace from="J1.DM2" to="net.USB_DM" />
{/* ESD clamp on the USB data lines (IEC 61000-4-2 on a connector that
gets plugged daily). I/O1-I/O1' and I/O2-I/O2' are through-lines. */}
<USBLC6_2SC6 name="U9" pcbX={-26} pcbY={TOP_BAND_Y} schSectionName="Usb" schX={SCH.Usb.x + 9} schY={SCH.Usb.y} />
<trace from="U9.pin1" to="net.USB_DP" />
<trace from="U9.pin6" to="net.USB_DP" />
<trace from="U9.pin3" to="net.USB_DM" />
<trace from="U9.pin4" to="net.USB_DM" />
<trace from="U9.VBUS" to="net.VBUS" />
<trace from="U9.GND" to="net.GND" />
{/* ============================ Charger (top band) ============================ */}
<TP4056_42_ESOP8
name="U2"
pcbX={-80}
pcbY={TOP_BAND_Y - 0.6}
pcbRotation={90}
schSectionName="Charger"
schX={SCH.Charger.x}
schY={SCH.Charger.y}
/>
<capacitor name="C1" capacitance="10uF" footprint="0805" maxDecouplingTraceLength="30mm" pcbX={-88} pcbY={TOP_BAND_Y} pcbRotation={90} schSectionName="Charger" schX={SCH.Charger.x - 5} schY={SCH.Charger.y} schOrientation="vertical" />
<capacitor name="C2" capacitance="10uF" footprint="0805" maxDecouplingTraceLength="30mm" pcbX={-72} pcbY={TOP_BAND_Y} pcbRotation={90} schSectionName="Charger" schX={SCH.Charger.x + 5} schY={SCH.Charger.y} schOrientation="vertical" />
{/* PROG = 2k -> ~500 mA charge current, fine for a 1500-2000 mAh pack */}
<resistor name="R3" resistance="2k" footprint="0402" pcbX={-67} pcbY={TOP_BAND_Y} pcbRotation={90} schSectionName="Charger" schX={SCH.Charger.x} schY={SCH.Charger.y - 4} />
<led name="LED1" color="red" footprint="0603" pcbX={-62} pcbY={TOP_BAND_Y} pcbRotation={90} schSectionName="Charger" schX={SCH.Charger.x - 9} schY={SCH.Charger.y - 1} schRotation={90} />
<resistor name="R4" resistance="330" footprint="0402" pcbX={-58} pcbY={TOP_BAND_Y} pcbRotation={90} schSectionName="Charger" schX={SCH.Charger.x - 9} schY={SCH.Charger.y - 4} schOrientation="vertical" />
<trace from="net.VBUS" to="U2.VCC" thickness="0.5mm" />
<trace from="U2.GND" to="net.GND" />
<trace from="U2.EP" to="net.GND" />
<trace from="U2.CE" to="net.VBUS" />
<trace from="U2.TEMP" to="net.GND" />
<trace from="U2.BAT" to="net.VBAT" thickness="0.6mm" />
<trace from="U2.PROG" to="R3.pin1" />
<trace from="R3.pin2" to="net.GND" />
<trace from="C1.pin1" to="net.VBUS" />
<trace from="C1.pin2" to="net.GND" />
<trace from="C2.pin1" to="net.VBAT" />
<trace from="C2.pin2" to="net.GND" />
<trace from="U2.N_CHRG" to="LED1.cathode" />
<trace from="LED1.anode" to="R4.pin1" />
<trace from="R4.pin2" to="net.VBUS" />
{/* ============================ Power + OS switches (top band) ============================ */}
{/* SW1 = battery on/off (pin2 is the common). SW3 = Windows / macOS:
common to ground, so one position pulls OS_SEL low (Mac) and the
other leaves it on the MCU's internal pull-up (Windows). Both are on
the back edge where the case can expose them. */}
<MSK12C02
name="SW1"
pcbX={-128}
pcbY={TOP_BAND_Y}
schSectionName="Battery"
schX={SCH.Battery.x + 4}
schY={SCH.Battery.y}
/>
<silkscreentext text="PWR" pcbX={-128} pcbY={TOP_Y - 1.5} fontSize="1.2mm" />
<MSK12C02
name="SW3"
pcbX={-116}
pcbY={TOP_BAND_Y}
schSectionName="Debug"
schX={SCH.Debug.x}
schY={SCH.Debug.y + 4}
/>
<silkscreentext text="WIN / MAC" pcbX={-116} pcbY={TOP_Y - 1.5} fontSize="1.2mm" />
<trace from="SW1.pin2" to="net.VBAT" thickness="0.6mm" />
<trace from="SW1.pin1" to="net.VBAT_SW" thickness="0.6mm" />
<trace from="SW1.pin4" to="net.GND" />
<trace from="SW3.pin2" to="net.GND" />
<trace from="SW3.pin1" to="net.OS_SEL" />
<trace from="SW3.pin4" to="net.GND" />
{/* Battery -> VSYS and USB -> VSYS Schottkys, so the board runs from
either source and from USB with no cell fitted. */}
<SS14L name="D1" pcbX={-106} pcbY={TOP_BAND_Y} schSectionName="Battery" schX={SCH.Battery.x + 9} schY={SCH.Battery.y} />
<SS14L name="D3" pcbX={-100} pcbY={TOP_BAND_Y} schSectionName="Battery" schX={SCH.Battery.x + 9} schY={SCH.Battery.y + 3} />
<trace from="D1.anode" to="net.VBAT_SW" thickness="0.6mm" />
<trace from="D1.cathode" to="net.VSYS" thickness="0.6mm" />
<trace from="D3.anode" to="net.VBUS" thickness="0.6mm" />
<trace from="D3.cathode" to="net.VSYS" thickness="0.6mm" />
{/* ============================ LED driver (top band, above Esc) ============================ */}
{/* 74AHCT1G125 (TTL input thresholds) lifts the 3.3 V data line to the
5 V LED rail, placed right above the Esc key where the chain starts. */}
<SN74AHCT1G125DBVR
name="U6"
pcbX={-147}
pcbY={TOP_BAND_Y}
schSectionName="LedDriver"
schX={SCH.LedDriver.x}
schY={SCH.LedDriver.y}
/>
<capacitor name="C3" capacitance="100nF" footprint="0402" pcbX={-143} pcbY={TOP_BAND_Y} pcbRotation={90} schSectionName="LedDriver" schX={SCH.LedDriver.x + 4} schY={SCH.LedDriver.y + 1} schOrientation="vertical" />
<capacitor name="C4" capacitance="22uF" footprint="0805" pcbX={-138.5} pcbY={TOP_BAND_Y} pcbRotation={90} schSectionName="LedDriver" schX={SCH.LedDriver.x + 7} schY={SCH.LedDriver.y + 1} schOrientation="vertical" />
<trace from="U6.A" to="net.LED_DATA_3V3" />
<trace from="U6.Y" to="net.LED_DIN0" />
<trace from="U6.N_OE" to="net.GND" />
<trace from="U6.VCC" to="net.LED_5V" />
<trace from="U6.GND" to="net.GND" />
<trace from="C3.pin1" to="net.LED_5V" />
<trace from="C3.pin2" to="net.GND" />
<trace from="C4.pin1" to="net.LED_5V" thickness="0.6mm" />
<trace from="C4.pin2" to="net.GND" />
{/* ============================ MCU (region A, bottom side) ============================ */}
{/* The module sits in the pocket under the left half of the space bar
with its antenna pointing at the board's front edge, 0.4 mm away, and
clear of the left space-bar stabiliser at x ~ -71.4. The case bottom
under the antenna must be plastic, not metal.
VSYS (3.7-5 V) goes into VDDH and the chip's REG0 makes 3.3 V on VDD;
DCCH is left open (REG0 in LDO mode, no external inductor). */}
<E73_2G4M08S1C
name="U1"
layer="bottom"
pcbX={-50}
pcbY={-41}
schSectionName="Mcu"
schX={SCH.Mcu.x}
schY={SCH.Mcu.y}
connections={{
...mcuMatrixPins,
[LED_DATA_PIN]: "net.LED_DATA_3V3",
[LED_EN_PIN]: "net.LED_EN",
[OS_SEL_PIN]: "net.OS_SEL",
[VBAT_SENSE_PIN]: "net.VBAT_SENSE",
VDDH: "net.VSYS",
VDD: "net.V3V3",
VBUS: "net.VBUS",
D_POS: "net.USB_DP",
D_NEG: "net.USB_DM",
RESET: "net.NRST",
SWDIO: "net.SWDIO",
SWDCLK: "net.SWDCLK",
GND1: "net.GND",
GND2: "net.GND",
GND3: "net.GND",
}}
/>
<silkscreentext text="RF KEEPOUT - NO COPPER / NO METAL CASE" layer="bottom" pcbX={-50} pcbY={-48} fontSize="1.2mm" />
<capacitor name="C5" capacitance="4.7uF" footprint="0603" layer="bottom" pcbX={-53} pcbY={-28.8} pcbRotation={90} schSectionName="Mcu" schX={SCH.Mcu.x + 6} schY={SCH.Mcu.y + 4} schOrientation="vertical" />
<capacitor name="C6" capacitance="10uF" footprint="0805" layer="bottom" pcbX={-48} pcbY={-28.8} pcbRotation={90} schSectionName="Mcu" schX={SCH.Mcu.x + 9} schY={SCH.Mcu.y + 4} schOrientation="vertical" />
<trace from="C5.pin1" to="net.V3V3" />
<trace from="C5.pin2" to="net.GND" />
<trace from="C6.pin1" to="net.VSYS" />
<trace from="C6.pin2" to="net.GND" />
{/* Battery gauge: 1M/1M divider off the switched cell rail into AIN2.
~1.9 uA of standing drain, which is negligible next to the radio. */}
<resistor name="R8" resistance="1M" footprint="0402" layer="bottom" pcbX={-41} pcbY={-30.5} pcbRotation={90} schSectionName="Battery" schX={SCH.Battery.x - 4} schY={SCH.Battery.y + 4} schOrientation="vertical" />
<resistor name="R9" resistance="1M" footprint="0402" layer="bottom" pcbX={-38.5} pcbY={-30.5} pcbRotation={90} schSectionName="Battery" schX={SCH.Battery.x - 4} schY={SCH.Battery.y} schOrientation="vertical" />
<capacitor name="C7" capacitance="100nF" footprint="0402" layer="bottom" pcbX={-36} pcbY={-30.5} pcbRotation={90} schSectionName="Battery" schX={SCH.Battery.x - 1} schY={SCH.Battery.y + 2} schOrientation="vertical" />
<trace from="R8.pin1" to="net.VBAT_SW" />
<trace from="R8.pin2" to="net.VBAT_SENSE" />
<trace from="R9.pin1" to="net.VBAT_SENSE" />
<trace from="R9.pin2" to="net.GND" />
<trace from="C7.pin1" to="net.VBAT_SENSE" />
<trace from="C7.pin2" to="net.GND" />
{/* ============================ Reset + SWD (region A) ============================ */}
<TS_1187A_B_A_B
name="SW2"
layer="bottom"
pcbX={-36}
pcbY={-38}
schSectionName="Debug"
schX={SCH.Debug.x}
schY={SCH.Debug.y}
/>
<trace from="SW2.pin1" to="net.NRST" />
<trace from="SW2.pin2" to="net.GND" />
{TESTPOINTS.map((tp, i) => (
<Fragment key={tp.name}>
<testpoint
name={tp.name}
footprintVariant="pad"
padShape="circle"
padDiameter="1.5mm"
layer="bottom"
pcbX={-61}
pcbY={tp.y}
schSectionName="Debug"
schX={SCH.Debug.x + 4 + i * 1.5}
schY={SCH.Debug.y}
/>
<trace from={`${tp.name}.pin1`} to={tp.net} />
</Fragment>
))}
{/* ============================ LED power (region B) ============================ */}
{/* VSYS -> SY6280 load switch (EN from the MCU, so lighting can be cut
completely on battery) -> MT3608 boost -> 5 V LED rail. R7 sets the
switch's current limit (~1.4 A); R5/R6 set the boost to 5.1 V.
Deliberately in the opposite pocket from the radio module, so the
switcher's noise is ~40 mm away from the antenna. */}
<SY6280AAC name="U7" layer="bottom" pcbX={-10} pcbY={-31} schSectionName="LedPower" schX={SCH.LedPower.x} schY={SCH.LedPower.y} />
<resistor name="R7" resistance="4.7k" footprint="0402" layer="bottom" pcbX={-10} pcbY={-35.5} pcbRotation={90} schSectionName="LedPower" schX={SCH.LedPower.x} schY={SCH.LedPower.y - 3} schOrientation="vertical" />
<capacitor name="C8" capacitance="22uF" footprint="0805" layer="bottom" pcbX={-5} pcbY={-31} pcbRotation={90} schSectionName="LedPower" schX={SCH.LedPower.x + 3} schY={SCH.LedPower.y - 2} schOrientation="vertical" />
<MT3608 name="U8" layer="bottom" pcbX={-1} pcbY={-31} schSectionName="LedPower" schX={SCH.LedPower.x + 6} schY={SCH.LedPower.y} />
<resistor name="R5" resistance="75k" footprint="0402" layer="bottom" pcbX={-1} pcbY={-35.5} pcbRotation={90} schSectionName="LedPower" schX={SCH.LedPower.x + 10} schY={SCH.LedPower.y} schOrientation="vertical" />
<resistor name="R6" resistance="10k" footprint="0402" layer="bottom" pcbX={2.5} pcbY={-35.5} pcbRotation={90} schSectionName="LedPower" schX={SCH.LedPower.x + 10} schY={SCH.LedPower.y - 3} schOrientation="vertical" />
<SWPA4030S220MT name="L1" layer="bottom" pcbX={4} pcbY={-31} schSectionName="LedPower" schX={SCH.LedPower.x + 6} schY={SCH.LedPower.y + 3} />
<SS14L name="D2" layer="bottom" pcbX={10} pcbY={-31} schSectionName="LedPower" schX={SCH.LedPower.x + 10} schY={SCH.LedPower.y + 3} />
<capacitor name="C9" capacitance="22uF" footprint="0805" layer="bottom" pcbX={14} pcbY={-31} pcbRotation={90} schSectionName="LedPower" schX={SCH.LedPower.x + 13} schY={SCH.LedPower.y - 2} schOrientation="vertical" />
<trace from="U7.IN" to="net.VSYS" thickness="0.6mm" />
<trace from="U7.OUT" to="net.LED_VIN" thickness="0.6mm" />
<trace from="U7.EN" to="net.LED_EN" />
<trace from="U7.GND" to="net.GND" />
<trace from="U7.ISET" to="R7.pin1" />
<trace from="R7.pin2" to="net.GND" />
<trace from="C8.pin1" to="net.LED_VIN" />
<trace from="C8.pin2" to="net.GND" />
<trace from="U8.IN" to="net.LED_VIN" thickness="0.6mm" />
<trace from="U8.EN" to="net.LED_VIN" />
<trace from="U8.GND" to="net.GND" />
<trace from="L1.pin1" to="net.LED_VIN" thickness="0.6mm" />
<trace from="L1.pin2" to="net.BOOST_SW" thickness="0.6mm" />
<trace from="U8.SW" to="net.BOOST_SW" thickness="0.6mm" />
<trace from="D2.anode" to="net.BOOST_SW" thickness="0.6mm" />
<trace from="D2.cathode" to="net.LED_5V" thickness="0.6mm" />
<trace from="R5.pin1" to="net.LED_5V" />
<trace from="R5.pin2" to="net.BOOST_FB" />
<trace from="U8.FB" to="net.BOOST_FB" />
<trace from="R6.pin1" to="net.BOOST_FB" />
<trace from="R6.pin2" to="net.GND" />
<trace from="C9.pin1" to="net.LED_5V" thickness="0.6mm" />
<trace from="C9.pin2" to="net.GND" />
{/* ============================ Battery connector (region B) ============================ */}
{/* Side-entry SMD JST-PH on the bottom side: the cell sits under the PCB
inside the case, so the cable runs flat and nothing protrudes on top.
Per the JST PH drawing the reinforcement pads sit at the wire-entry
side of the housing; the footprint puts them at -y, so the 180 deg
rotation turns the opening toward the board centre (the battery). */}
<JST_PH2_SMD
name="J2"
layer="bottom"
pcbX={0}
pcbY={-44.5}
pcbRotation={180}
schSectionName="Battery"
schX={SCH.Battery.x}
schY={SCH.Battery.y}
/>
<silkscreentext text="BAT + -" layer="bottom" pcbX={0} pcbY={-35.5} fontSize="1.2mm" />
<trace from="J2.pin1" to="net.VBAT" thickness="0.6mm" />
<trace from="J2.pin2" to="net.GND" />
{/* ============================ Key matrix + RGB ============================ */}
{/* Each key cell: hot-swap MX socket + stem hole (socket on the bottom),
1N4148WS row diode (bottom), WS2812C-2020 RGB LED on the top copper
under the switch lens, and a 100 nF cap for it on the bottom.
Columns drive, rows sense: COL -> switch -> diode -> ROW. */}
{KEYS.map((key) => {
const gx = key.x - STEM.x
const gy = key.y - STEM.y
const sx = SCH.Matrix.x + key.col * SCH.Matrix.dx
const sy = SCH.Matrix.y - key.row * SCH.Matrix.dy
const lx = SCH.Leds.x + (key.led % SCH.Leds.perRow) * SCH.Leds.dx
const ly = SCH.Leds.y - Math.floor(key.led / SCH.Leds.perRow) * SCH.Leds.dy
const din = `net.LED_DIN${key.led}`
const dout = key.led === KEYS.length - 1 ? "net.LED_DOUT_END" : `net.LED_DIN${key.led + 1}`
return (
<Fragment key={key.name}>
<KeyHotSocket
name={`SW_${key.name}`}
pcbX={gx}
pcbY={gy}
schX={sx}
schY={sy}
connections={{ pin2: colNet(key.col) }}
{...({ schSectionName: "Matrix" } as object)}
/>
<KeyShaftForHotSocket
name={`SW_${key.name}_STEM`}
pcbX={gx + SHAFT_OFF.x}
pcbY={gy + SHAFT_OFF.y}
{...({ doNotPlace: true } as object)}
/>
<silkscreentext text={key.label} pcbX={gx + LABEL_OFF.x} pcbY={gy + LABEL_OFF.y} fontSize="1.2mm" />
<A_1N4148WS
name={`D_${key.name}`}
layer="bottom"
pcbX={gx + DIODE_OFF.x}
pcbY={gy + DIODE_OFF.y}
schX={sx}
schY={sy - 1.6}
schRotation={90}
schSectionName="Matrix"
connections={{ anode: `.SW_${key.name} .pin1`, cathode: rowNet(key.row) }}
/>
<WS2812C_2020_V1
name={`LED_${key.name}`}
pcbX={gx + LED_OFF.x}
pcbY={gy + LED_OFF.y}
schX={lx}
schY={ly}
schSectionName="Leds"
connections={{ VDD: "net.LED_5V", GND: "net.GND", DI: din, DO: dout }}
/>
<capacitor
name={`C_${key.name}`}
capacitance="100nF"
footprint="0402"
layer="bottom"
pcbX={gx + LED_CAP_OFF.x}
pcbY={gy + LED_CAP_OFF.y}
schX={lx + 2}
schY={ly}
schOrientation="vertical"
schSectionName="Leds"
connections={{ pin1: "net.LED_5V", pin2: "net.GND" }}
/>
</Fragment>
)
})}
</board>
)