pixalynx/usbc-led-switch
A single-sided USB-C–powered LED light board with a latching pushbutton, 100 Ω current-limiting resistor, white 5730 SMD LED, and dual 5.1 kΩ USB-C CC pull-down resistors.
- Version
- 0.0.1
- License
- unset
- Stars
- 0
lib/footprints.tsx
import { Fragment, type ReactElement } from "react"
/** Land patterns for a home-milled, single-sided board. Millimetres, top view, +y up.
*
* Isolation milling has no solder mask and no plating, so every land here is drawn bigger than a fab
* footprint would be, gaps are kept at 0.30 mm or more, and through-holes are round (drill bits, not slots)
* with their copper ring on the top (component) side only.
*/
const keyed = (els: ReactElement[]) => els.map((e, i) => <Fragment key={e.key ?? `n${i}`}>{e}</Fragment>)
const fp = (els: ReactElement[]) => <footprint>{keyed(els)}</footprint>
export const pad = (pin: string | string[], x: number, y: number, w: number, h: number) => (
<smtpad portHints={typeof pin === "string" ? [pin] : pin} pcbX={x} pcbY={y} width={w} height={h} shape="rect" />
)
/** Round hole with a top-side ring. tscircuit calls it a plated hole; on the milled board it is a drilled hole
* whose lead is soldered to the ring from the top. */
export const thPad = (pin: string, x: number, y: number, drill: number, ring: number) => (
<platedhole portHints={[pin]} pcbX={x} pcbY={y} holeDiameter={drill} outerDiameter={ring} shape="circle" />
)
export const npth = (x: number, y: number, d: number) => <hole pcbX={x} pcbY={y} diameter={d} />
export const silk = (pts: [number, number][], stroke = 0.15) => (
<silkscreenpath route={pts.map(([x, y]) => ({ x, y }))} strokeWidth={stroke} />
)
export const box = (w: number, h: number, cx = 0, cy = 0, stroke = 0.15) =>
silk([[cx - w / 2, cy - h / 2], [cx + w / 2, cy - h / 2], [cx + w / 2, cy + h / 2], [cx - w / 2, cy + h / 2], [cx - w / 2, cy - h / 2]], stroke)
export const courtyard = (w: number, h: number, cx = 0, cy = 0) => (
<courtyardoutline outline={[
{ x: cx - w / 2, y: cy + h / 2 }, { x: cx + w / 2, y: cy + h / 2 }, { x: cx + w / 2, y: cy - h / 2 },
{ x: cx - w / 2, y: cy - h / 2 }, { x: cx - w / 2, y: cy + h / 2 },
]} />
)
const text = (t: string, x: number, y: number, size = 0.8) => (
<silkscreentext text={t} pcbX={x} pcbY={y} fontSize={size} anchorAlignment="center" />
)
// -------------------------------------------------------------------------------------------------
// 1206 resistor, hand-solder / milling land: 1.4 x 1.9 at +/-1.6 (IPC nominal is ~1.15 x 1.8 at +/-1.45).
// The 1.8 mm between the lands leaves room to pass a trace underneath if a layout ever needs a jumper.
// -------------------------------------------------------------------------------------------------
export const R1206 = () => fp([pad("pin1", -1.6, 0, 1.4, 1.9), pad("pin2", 1.6, 0, 1.4, 1.9), courtyard(5.0, 2.3)])
/** The same land turned upright (pin1 at -y), so the part can stay at rotation 0. */
export const R1206V = () => fp([pad("pin1", 0, -1.6, 1.9, 1.4), pad("pin2", 0, 1.6, 1.9, 1.4), courtyard(2.3, 5.0)])
// -------------------------------------------------------------------------------------------------
// USB-C 16-pin top-mount receptacle (the generic "TYPE-C 16P" phone-repair part, same land pattern as
// JLCPCB C2765186 / HRO TYPE-C-31-M-12). Origin = connector centre line, contact row at y = +2.125,
// mouth (front of the shell) at y = -5.225.
//
// Power-only, milling-friendly variant:
// * Only GND, VBUS, CC1, CC2, VBUS, GND get lands. The six D+/D-/SBU contacts have NO land: they are not
// needed to draw power, and leaving them out turns the 0.20 mm gaps of the stock pattern into >= 0.30 mm.
// Their legs simply rest on bare board.
// * Lands are stretched 0.6 mm rearwards (soldering-iron room). GND lands are 0.5 wide; VBUS lands are 0.4 wide
// and sit 0.05 mm outboard of the contact centre, so VBUS-GND is 0.30 mm and CC2-VBUS2 is 0.35 mm.
// * Shell legs: round drilled holes instead of plated slots (rear legs 1.3 mm, front legs 1.1 mm) with a
// top-side ring to solder the leg to. The rear rings merge into the GND lands, so the shell is grounded.
// * Locating pegs: 0.8 mm holes (the pegs are 0.65-0.7 mm).
// -------------------------------------------------------------------------------------------------
export const USBC_PINS = {
pin1: ["GND1"], pin2: ["VBUS1"], pin3: ["CC1"], pin4: ["CC2"], pin5: ["VBUS2"], pin6: ["GND2"],
pin7: ["SHELL1"], pin8: ["SHELL2"], pin9: ["SHELL3"], pin10: ["SHELL4"],
} as const
const LAND_Y = 2.425 // 1.575 .. 3.275: stock land front edge kept, back edge moved 0.6 mm rearwards
const LAND_L = 1.7
export const UsbC16pMill = () => fp([
pad("pin1", -3.2, LAND_Y, 0.5, LAND_L),
pad("pin2", -2.45, LAND_Y, 0.4, LAND_L),
pad("pin3", -1.25, LAND_Y, 0.3, LAND_L),
pad("pin4", 1.75, LAND_Y, 0.3, LAND_L),
pad("pin5", 2.45, LAND_Y, 0.4, LAND_L),
pad("pin6", 3.2, LAND_Y, 0.5, LAND_L),
thPad("pin7", -4.325, 1.575, 1.3, 2.3),
thPad("pin8", 4.325, 1.575, 1.3, 2.3),
thPad("pin9", -4.325, -2.625, 1.1, 2.1),
thPad("pin10", 4.325, -2.625, 1.1, 2.1),
npth(-2.89, 1.055, 0.8),
npth(2.89, 1.055, 0.8),
// Shell outline (8.94 wide, mouth at -5.225) and a marker for the unused, land-free contacts
silk([[-4.47, -5.225], [4.47, -5.225]]),
silk([[-4.47, -1.4], [-4.47, 0.35]]),
silk([[4.47, -1.4], [4.47, 0.35]]),
silk([[-4.47, -5.225], [-4.47, -3.85]]),
silk([[4.47, -5.225], [4.47, -3.85]]),
courtyard(11.1, 8.9, 0, -1.05),
])
// -------------------------------------------------------------------------------------------------
// White 5730 SMD LED (5.7 x 3.0 mm, the Amazon "Coliao" 5730 white). Land pattern from the Yuji
// YJ-BC-5730L-G02 and Sunrom 5730 datasheets: two 1.1 x 1.7 electrodes 6.8 mm overall plus a 2.5 x 1.7 heat
// pad offset towards the anode (centres anode 2.85, heat pad 0.35, cathode -2.85). Drawn rotated 180 deg from
// the datasheet so the anode is at +x, electrodes stretched 0.3 mm outwards and all pads 2.0 mm tall for
// hand soldering. The heat pad is an isolated land with no net: both datasheets show it tied to the anode
// inside the LED, but unbranded parts vary and at 20 mA it carries no heat worth sinking, so it is
// soldered for grip only. The chamfered corner on top of the LED marks the CATHODE (-x here).
// -------------------------------------------------------------------------------------------------
export const LED5730 = () => fp([
pad(["pin1", "anode"], 3.0, 0, 1.4, 2.0),
<smtpad pcbX={0.35} pcbY={0} width={2.5} height={2.0} shape="rect" />,
pad(["pin2", "cathode"], -3.0, 0, 1.4, 2.0),
silk([[-2.85, 1.5], [2.85, 1.5]]),
silk([[-2.85, -1.5], [2.85, -1.5]]),
silk([[-4.2, 1.2], [-4.2, -1.2]], 0.25), // cathode bar
text("+", 4.4, 1.8, 1.0),
courtyard(8.0, 3.6),
])
// -------------------------------------------------------------------------------------------------
// Push-on / push-off switch: Adafruit 3870 = Yueqing Legong LG-15F (Core Electronics "Mini On/Off Push-Button
// Switch"). SPST latching, 5-8 N actuation, 1.0 mm travel. Drawing: base 14.0 x 8.1 mm, two flat tabs 4.3 mm
// wide, 23.0 mm overall, tab-hole centres 18.5 mm apart, tabs 0.2 mm below the body base, so the part lies flat
// on the copper side like an SMD part. Each tab gets a 4.6 x 5.0 land; the 1.0 mm hole at the tab-hole centre
// is optional - thread a resistor-lead offcut through tab and board, bend it under, solder it on top for extra
// grip on milled copper.
// -------------------------------------------------------------------------------------------------
export const SW_PINS = { pin1: ["A"], pin2: ["B"] } as const
const tab = (pin: string, x: number) => (
<platedhole portHints={[pin]} pcbX={x} pcbY={0} shape="circular_hole_with_rect_pad"
holeDiameter={1.0} rectPadWidth={4.6} rectPadHeight={5.0} />
)
export const LG15F = () => fp([
tab("pin1", -9.25),
tab("pin2", 9.25),
box(14.0, 8.1),
<silkscreencircle pcbX={0} pcbY={0} radius={3.45} strokeWidth={0.15} />,
courtyard(23.6, 8.6),
])