silkscreen, rotate panels, fpc connector, breakout nets

This commit is contained in:
zyphlar
2026-07-04 03:28:12 -07:00
parent f770479fc4
commit 55f1d612c1
4 changed files with 828 additions and 718 deletions
+399 -361
View File
@@ -1,34 +1,40 @@
#!/usr/bin/env python3
"""
Generate KiCad 7 combined panel PCB for Baby Mobile project.
Baby Mobile — Combined Panel v4
PANEL LAYOUT (v-score breakaway):
┌─────────────────────────────────────────────────────
CARRIER BOARD (42mm × 24mm)
┌──────────────────────────────────────────────┐
┌─────────────┐ [IS25LP128F] [JST SH 10] │
XIAO ■USB │ ●1 J1
nRF52840 [C3] [R1][C5]
│ D0 .... D10│ [TPS61220] [R2]
│ D1 D9 │ [L1][C1][C2]
│ ... ...
│ │ D6 3V3│ (M2) (M2)
└─────────────┘
└──────────────────────────────────────────────┘ │
- - SNAP - - - - - - - - - - - - - - - - - - - - -
──────────┐ ┌────────────┐ ┌────────────┐
│ │ SOP-8 │ │ SOP-16N │ │ SOP-16W │ │
│ castell. │ │ castellated│ │ castellated│
│ │ [JST 8p] │ │ [JST 10p] │ │ [JST 10p] │ │
└──────────┘ └────────────┘ └────────────┘
└─────────────────────────────────────────────────────┘
PANEL LAYOUT:
┌───────────────────────────────────────────────┐
CARRIER BOARD (44×26mm)
┌───────USB-C──┐
│ █████████████ │
│ │ │ [U2 IS25LP128F] [C3][R2]
│ │ XIAO BLE │ ●1
│ │ nRF52840 │ [R1][C5]
│ │ │ │
│ │ D0 D10│ [U1 TPS61220] │
│ │ D1 D9│ ●1 [L1][C1][C2] │
│ │ D2 D8│ │
│ D3 D7│ ┌────────────────┐
│ │ D4 3V3│ │ J1 FPC-20 0.5mm│
│ D5 GND│ └────────────────┘
│ D6 5V│
───────────────┘ (M2) (M2)
├ ─ ─ ─ ─ ─ SNAP ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┤
┌──────┐ ┃ ┌──────────┐ ┃ ┌──────────┐
│ │SOP-8SOP-16 N SOP-16 W
C│ │C┃C│ │C┃C│ │C
│ A│[FPC] │A┃A│ [FPC] │A┃A│ [FPC] │A │
│ S│ │S┃S│ │S┃S│ │S │
│ T│ │T┃T│ │T┃T│ │T │
│ └──────┘ ┃ └──────────┘ ┃ └──────────┘ │
└───────────┸──────────────┸─────────────────────┘
C=castellated edges ┃=v-score snap lines
Each SOP adapter:
- Board edges cut through SOP pad centers → castellated half-holes
- Top surface: JST SH connector + pin 1 dot + pin number silk labels
- Bottom surface: nothing (sits flush on toy PCB)
- Long edges (top/bottom): castellated half-holes = SOP pads
- Short edges: v-score snap lines
- Center top: FPC 0.5mm connector (20-pin for SOP-16, 10-pin for SOP-8)
- All SOP pins netted to corresponding FPC pins
"""
import uuid, os, json
@@ -37,469 +43,501 @@ def uid():
return str(uuid.uuid4())
# ============================================================
# Geometry Constants
# KiCad drawing primitives
# ============================================================
CARRIER_W = 42.0
CARRIER_H = 24.0
ADAPTER_AREA_H = 10.0 # height for adapter strip below carrier
PANEL_W = 48.0
PANEL_H = CARRIER_H + ADAPTER_AREA_H + 2.0 # +2 for v-score margin
def gr_line(x1, y1, x2, y2, layer="Edge.Cuts", w=0.1):
return f' (gr_line (start {x1:.3f} {y1:.3f}) (end {x2:.3f} {y2:.3f}) (layer "{layer}") (width {w}) (uuid "{uid()}"))'
SOP_PITCH = 1.27
SOP8_PINS_PER_SIDE = 4
SOP16_PINS_PER_SIDE = 8
def gr_rect(x1, y1, x2, y2, layer="F.SilkS", w=0.15):
return f' (gr_rect (start {x1:.3f} {y1:.3f}) (end {x2:.3f} {y2:.3f}) (layer "{layer}") (width {w}) (uuid "{uid()}"))'
# SOP pad dimensions
PAD_W = 0.6
PAD_H = 1.5
def gr_text(s, x, y, sz=0.8, layer="F.SilkS", mirror=False):
th = max(0.08, sz * 0.13)
mir = " (justify mirror)" if mirror else ""
return f' (gr_text "{s}" (at {x:.3f} {y:.3f}) (layer "{layer}") (uuid "{uid()}") (effects (font (size {sz} {sz}) (thickness {th:.3f})){mir}))'
# Castellated hole
CAST_DRILL = 0.6
CAST_PAD = 1.0
def gr_circle(x, y, r=0.3, layer="F.SilkS", fill="solid"):
return f' (gr_circle (center {x:.3f} {y:.3f}) (end {x+r:.3f} {y:.3f}) (layer "{layer}") (width 0.1) (fill {fill}) (uuid "{uid()}"))'
# JST SH 1.0mm pitch
JST_PITCH = 1.0
# ============================================================
# Helpers
# ============================================================
def smd(name, x, y, w, h, net, nname, layer="F.Cu", mask=True):
ml = f'"{layer}" "{layer[0]}.Paste" "{layer[0]}.Mask"' if mask else f'"{layer}"'
return f' (pad "{name}" smd rect (at {x:.3f} {y:.3f}) (size {w} {h}) (layers {ml}) (net {net} "{nname}") (uuid "{uid()}"))'
def thru(name, x, y, pd, dd, net, nname):
return f' (pad "{name}" thru_hole circle (at {x:.3f} {y:.3f}) (size {pd} {pd}) (drill {dd}) (layers "*.Cu" "*.Mask") (net {net} "{nname}") (uuid "{uid()}"))'
def cast(name, x, y, net, nname):
"""Castellated half-hole: thru-hole pad on board edge."""
return f' (pad "{name}" thru_hole circle (at {x:.3f} {y:.3f}) (size {CAST_PAD} {CAST_PAD}) (drill {CAST_DRILL}) (layers "*.Cu" "*.Mask") (net {net} "{nname}") (uuid "{uid()}"))'
def line(x1, y1, x2, y2, layer="Edge.Cuts", w=0.1):
return f' (gr_line (start {x1} {y1}) (end {x2} {y2}) (layer "{layer}") (width {w}) (uuid "{uid()}"))'
def rect(x1, y1, x2, y2, layer="F.SilkS", w=0.15):
return f' (gr_rect (start {x1} {y1}) (end {x2} {y2}) (layer "{layer}") (width {w}) (uuid "{uid()}"))'
def txt(s, x, y, sz=0.8, layer="F.SilkS", bold=False):
th = sz * 0.15
b = " bold" if bold else ""
return f' (gr_text "{s}" (at {x} {y}) (layer "{layer}") (uuid "{uid()}") (effects (font (size {sz} {sz}) (thickness {th}){b})))'
def circle_mark(x, y, r=0.3, layer="F.SilkS"):
"""Pin 1 dot marker."""
return f' (gr_circle (center {x} {y}) (end {x+r} {y}) (layer "{layer}") (width 0.15) (fill solid) (uuid "{uid()}"))'
def fp_start(name, x, y, layer="F.Cu"):
def fp_hdr(name, x, y, layer="F.Cu"):
return f"""
(footprint "panel:{name}" (layer "{layer}")
(at {x} {y})
(at {x:.3f} {y:.3f})
(uuid "{uid()}")
(fp_text reference "{name}" (at 0 0) (layer "{layer[0]}.Fab") (uuid "{uid()}")
(effects (font (size 0.5 0.5) (thickness 0.08)) hide))
(fp_text value "" (at 0 0) (layer "{layer[0]}.Fab") (uuid "{uid()}")
(effects (font (size 0.5 0.5) (thickness 0.08)) hide))"""
def pad_smd(name, x, y, w, h, net, nname, layer="F.Cu"):
layers = f'"{layer}" "{layer[0]}.Paste" "{layer[0]}.Mask"'
return f' (pad "{name}" smd rect (at {x:.3f} {y:.3f}) (size {w:.3f} {h:.3f}) (layers {layers}) (net {net} "{nname}") (uuid "{uid()}"))'
def pad_thru(name, x, y, pd, dd, net, nname):
return f' (pad "{name}" thru_hole circle (at {x:.3f} {y:.3f}) (size {pd} {pd}) (drill {dd}) (layers "*.Cu" "*.Mask") (net {net} "{nname}") (uuid "{uid()}"))'
def pad_cast(name, x, y, net, nname):
"""Castellated half-hole on board edge."""
return f' (pad "{name}" thru_hole circle (at {x:.3f} {y:.3f}) (size 1.0 1.0) (drill 0.6) (layers "*.Cu" "*.Mask") (net {net} "{nname}") (uuid "{uid()}"))'
# ============================================================
# Generate Panel
# XIAO nRF52840 Dimensions (from Seeed docs)
# ============================================================
# PCB: 21.0mm x 17.5mm
# USB-C: centered on one 17.5mm short edge, ~9mm wide, protrudes ~2.5mm
# Pin headers: 2x7, 2.54mm pitch, rows 17.78mm apart (center-center)
# Pin 1 (D0) at top-left when USB faces left
# Reset button on USB side, LED on opposite side
XIAO_W = 21.0
XIAO_H = 17.5
XIAO_ROW_SPAN = 17.78 # center to center of pin rows
XIAO_USB_W = 8.9
XIAO_USB_H = 3.2
XIAO_USB_PROTRUDE = 1.5 # how far USB sticks past PCB edge
# ============================================================
# Main generator
# ============================================================
def generate_panel():
ox, oy = 100, 100 # KiCad origin offset
def generate():
# Panel dimensions
CARRIER_W = 44.0
CARRIER_H = 26.0
ADAPTER_STRIP_H = 14.0
PANEL_W = 48.0
PANEL_H = CARRIER_H + ADAPTER_STRIP_H + 1.0 # 1mm v-score gap
ox, oy = 100.0, 100.0 # KiCad origin
pcb = []
# --- Header ---
pcb.append(f"""(kicad_pcb (version 20221018) (generator "panel_gen_v3")
# ---- File header ----
pcb.append(f"""(kicad_pcb (version 20221018) (generator "panel_v4")
(general (thickness 1.6))
(paper "A4")
(title_block
(title "Baby Mobile Combined Panel")
(title "Baby Mobile Combined Panel v4")
(date "2026-03-11")
(rev "3.0")
(comment 1 "Carrier: XIAO nRF52840 + IS25LP128F + TPS61220")
(comment 2 "Breakaway: SOP-8, SOP-16N, SOP-16W castellated adapters")
(comment 3 "Order as one panel from JLCPCB with castellated holes + v-score")
(rev "4.0")
(comment 1 "Carrier: XIAO nRF52840 + IS25LP128F + TPS61220 + FPC-20")
(comment 2 "Adapters: SOP-8, SOP-16N, SOP-16W castellated, FPC connected")
(comment 3 "Order: JLCPCB, 2-layer, castellated holes=yes, v-score=yes")
)
(layers
(0 "F.Cu" signal) (31 "B.Cu" signal)
(34 "B.Paste" user) (35 "F.Paste" user)
(36 "B.SilkS" user "B.Silkscreen") (37 "F.SilkS" user "F.Silkscreen")
(38 "B.Mask" user "B.Mask") (39 "F.Mask" user "F.Mask")
(44 "Edge.Cuts" user) (49 "F.Fab" user "F.Fab") (48 "B.Fab" user "B.Fab")
(44 "Edge.Cuts" user)
(48 "B.Fab" user "B.Fab") (49 "F.Fab" user "F.Fab")
)
(setup (pad_to_mask_clearance 0.05))
(setup
(pad_to_mask_clearance 0.05)
(pcbplotparams (layerselection 0x00010fc_ffffffff) (outputdirectory "gerbers/"))
)
(net 0 "")
(net 1 "GND") (net 2 "VBAT") (net 3 "3V3")
(net 4 "SPI_MOSI") (net 5 "SPI_MISO") (net 6 "SPI_SCK") (net 7 "FLASH_CS")
(net 8 "BOOST_SW")
(net 9 "SIG_1") (net 10 "SIG_2") (net 11 "SIG_3") (net 12 "SIG_4")
(net 13 "SIG_5") (net 14 "SIG_6") (net 15 "SIG_7") (net 16 "SIG_8")
(net 8 "BOOST_SW") (net 9 "AUDIO_FILT")
""")
# ============================
# PANEL OUTLINE (Edge.Cuts)
# ============================
# 16 signal nets for SOP pins / FPC
for i in range(1, 17):
pcb.append(f' (net {i+9} "S{i}")')
# Total nets: 0-25
# Main panel rectangle
pcb.append(line(ox, oy, ox+PANEL_W, oy))
pcb.append(line(ox+PANEL_W, oy, ox+PANEL_W, oy+PANEL_H))
pcb.append(line(ox+PANEL_W, oy+PANEL_H, ox, oy+PANEL_H))
pcb.append(line(ox, oy+PANEL_H, ox, oy))
# ================================================================
# PANEL OUTLINE
# ================================================================
# V-score line between carrier and adapter strip
vscore_y = oy + CARRIER_H + 1.0
pcb.append(line(ox, vscore_y, ox+PANEL_W, vscore_y))
# Mark v-score on silk
pcb.append(txt("— SNAP HERE —", ox + PANEL_W/2, vscore_y, 0.6))
# V-score lines between adapters in the strip
adapt_y = vscore_y + 1.0
# Adapter positions in the strip
a8_x = ox + 1.0
a8_w = 10.0
a16n_x = a8_x + a8_w + 1.0
a16n_w = 14.0
a16w_x = a16n_x + a16n_w + 1.0
a16w_w = 14.0
# Outer rectangle
pcb.append(gr_line(ox, oy, ox+PANEL_W, oy))
pcb.append(gr_line(ox+PANEL_W, oy, ox+PANEL_W, oy+PANEL_H))
pcb.append(gr_line(ox+PANEL_W, oy+PANEL_H, ox, oy+PANEL_H))
pcb.append(gr_line(ox, oy+PANEL_H, ox, oy))
pcb.append(line(a16n_x - 0.5, adapt_y, a16n_x - 0.5, oy + PANEL_H))
pcb.append(line(a16w_x - 0.5, adapt_y, a16w_x - 0.5, oy + PANEL_H))
# ============================
# CARRIER BOARD COMPONENTS
# ============================
# V-score: carrier / adapter strip
vs_y = oy + CARRIER_H + 0.5
pcb.append(gr_line(ox, vs_y, ox+PANEL_W, vs_y))
pcb.append(gr_text("▼ SNAP ▼", ox + PANEL_W/2, vs_y - 0.3, 0.5))
cx = ox + 3 # carrier area left edge (with margin)
cy = oy + 1 # carrier area top edge
# V-score: between adapters
adapt_base = vs_y + 0.5
# SOP-8: 10mm wide, SOP-16N: 16mm wide, SOP-16W: 16mm wide
a8_w = 12.0
a16n_w = 16.0
a16w_w = 16.0
gap = 0.0 # v-score is zero-width
# ---- XIAO SOCKET ----
xiao_cx = cx + 13
xiao_cy = cy + CARRIER_H/2
xiao_w, xiao_h = 21, 17.5
row_half = 17.78 / 2
vs_x1 = ox + a8_w + 1.0
vs_x2 = vs_x1 + a16n_w
pcb.append(gr_line(vs_x1, adapt_base, vs_x1, oy + PANEL_H))
pcb.append(gr_line(vs_x2, adapt_base, vs_x2, oy + PANEL_H))
# XIAO outline with USB-C rectangle
pcb.append(rect(xiao_cx - xiao_w/2, xiao_cy - xiao_h/2,
xiao_cx + xiao_w/2, xiao_cy + xiao_h/2))
# USB-C bump
usb_w, usb_h = 9, 3.5
pcb.append(rect(xiao_cx - xiao_w/2 - 1, xiao_cy - usb_h/2,
xiao_cx - xiao_w/2, xiao_cy + usb_h/2))
pcb.append(txt("USB-C", xiao_cx - xiao_w/2 - 3, xiao_cy, 0.5))
pcb.append(txt("XIAO nRF52840", xiao_cx, xiao_cy, 0.7, bold=True))
# ================================================================
# CARRIER: XIAO SOCKET with accurate outline
# ================================================================
# Pin labels along XIAO outline
left_labels = ["D0", "D1", "D2", "D3", "D4", "D5", "D6"]
right_labels = ["D10", "D9", "D8", "D7", "3V3", "GND", "5V"]
# Place XIAO with USB-C facing left edge of carrier
xiao_cx = ox + 3 + XIAO_W/2 # center of XIAO
xiao_cy = oy + CARRIER_H/2
# Left header (D0-D6 → signals from toy)
left_nets = [9, 10, 11, 12, 13, 14, 15] # SIG_1 through SIG_7
pcb.append(fp_start("XIAO_L", xiao_cx - row_half, xiao_cy))
# Board outline rectangle
xb_l = xiao_cx - XIAO_W/2 # left edge of XIAO PCB
xb_r = xiao_cx + XIAO_W/2 # right edge
xb_t = xiao_cy - XIAO_H/2 # top
xb_b = xiao_cy + XIAO_H/2 # bottom
pcb.append(gr_rect(xb_l, xb_t, xb_r, xb_b, "F.SilkS", 0.2))
# USB-C rectangle protruding from left edge
usb_l = xb_l - XIAO_USB_PROTRUDE
usb_t = xiao_cy - XIAO_USB_W/2
usb_b = xiao_cy + XIAO_USB_W/2
pcb.append(gr_rect(usb_l, usb_t, xb_l, usb_b, "F.SilkS", 0.2))
# Fill USB area
pcb.append(gr_rect(usb_l + 0.3, usb_t + 0.3, xb_l - 0.1, usb_b - 0.3, "F.SilkS", 0.15))
pcb.append(gr_text("USB-C", usb_l + 2, xiao_cy, 0.5))
pcb.append(gr_text("XIAO nRF52840", xiao_cx + 1, xiao_cy, 0.8))
# Reset button indicator (top-left corner of XIAO near USB)
pcb.append(gr_text("RST", xb_l + 2, xb_t + 1.5, 0.4))
pcb.append(gr_circle(xb_l + 1, xb_t + 1.5, 0.5, "F.SilkS", "none"))
# Pin labels
left_labels = ["D0","D1","D2","D3","D4","D5","D6"]
right_labels = ["D10","D9","D8","D7","3V3","GND","5V"]
# Left row nets: SIG_1..7 → S1..S7 (from toy via FPC)
left_nets = [(10,"S1"),(11,"S2"),(12,"S3"),(13,"S4"),(14,"S5"),(15,"S6"),(16,"S7")]
# Right row: D10→S8(audio), D9→SCK, D8→MOSI, D7→MISO, 3V3, GND, 5V(NC)
right_nets = [(17,"S8"),(6,"SPI_SCK"),(4,"SPI_MOSI"),(5,"SPI_MISO"),(3,"3V3"),(1,"GND"),(0,"")]
# Left header
pcb.append(fp_hdr("J2_XIAO_L", xiao_cx - XIAO_ROW_SPAN/2, xiao_cy))
for i in range(7):
y = -7.62 + i * 2.54
pcb.append(thru(left_labels[i], 0, y, 1.7, 1.0, left_nets[i], f"SIG_{i+1}"))
net_id, net_name = left_nets[i]
pcb.append(pad_thru(left_labels[i], 0, y, 1.7, 1.0, net_id, net_name))
pcb.append(" )")
for i, lbl in enumerate(left_labels):
pcb.append(txt(lbl, xiao_cx - row_half - 3, xiao_cy - 7.62 + i*2.54, 0.5))
pcb.append(gr_text(lbl, xiao_cx - XIAO_ROW_SPAN/2 - 2.5, xiao_cy - 7.62 + i*2.54, 0.45))
# Right header (D10,D9,D8,D7 → audio/SPI/motor; 3V3,GND,5V → power)
right_nets = [16, 6, 4, 5, 3, 1, 0]
right_nnames = ["SIG_8", "SPI_SCK", "SPI_MOSI", "SPI_MISO", "3V3", "GND", ""]
pcb.append(fp_start("XIAO_R", xiao_cx + row_half, xiao_cy))
# Right header
pcb.append(fp_hdr("J3_XIAO_R", xiao_cx + XIAO_ROW_SPAN/2, xiao_cy))
for i in range(7):
y = -7.62 + i * 2.54
n = right_nets[i]
nn = right_nnames[i]
if n > 0:
pcb.append(thru(right_labels[i], 0, y, 1.7, 1.0, n, nn))
net_id, net_name = right_nets[i]
if net_id > 0:
pcb.append(pad_thru(right_labels[i], 0, y, 1.7, 1.0, net_id, net_name))
else:
pcb.append(f' (pad "{right_labels[i]}" thru_hole circle (at 0 {y:.2f}) (size 1.7 1.7) (drill 1.0) (layers "*.Cu" "*.Mask") (uuid "{uid()}"))')
pcb.append(" )")
for i, lbl in enumerate(right_labels):
pcb.append(txt(lbl, xiao_cx + row_half + 3, xiao_cy - 7.62 + i*2.54, 0.5))
# ---- IS25LP128F (SOIC-8) ----
fl_cx = cx + 31
fl_cy = cy + 5.5
pcb.append(gr_text(lbl, xiao_cx + XIAO_ROW_SPAN/2 + 2.8, xiao_cy - 7.62 + i*2.54, 0.45))
# ================================================================
# CARRIER: IS25LP128F (SOIC-8)
# ================================================================
fl_cx = ox + 32
fl_cy = oy + 5.5
soic_span = 5.4
# SOIC-8 body outline on silk
pcb.append(gr_rect(fl_cx - 2.5, fl_cy - soic_span/2 - 0.5,
fl_cx + 2.5, fl_cy + soic_span/2 + 0.5))
# Pin 1 dot
pcb.append(circle_mark(fl_cx - 2.5, fl_cy + soic_span/2 + 0.8))
# Outline
pcb.append(rect(fl_cx - 2.5, fl_cy - 2, fl_cx + 2.5, fl_cy + 2))
pcb.append(txt("U2 IS25LP128F", fl_cx, fl_cy - 3.5, 0.5))
pcb.append(txt("●1", fl_cx - 2.5, fl_cy + soic_span/2 + 1.5, 0.4))
pcb.append(gr_circle(fl_cx - 1.5, fl_cy + soic_span/2 - 0.5, 0.25))
# Notch on pin 1 end
pcb.append(gr_circle(fl_cx, fl_cy + soic_span/2 + 0.5, 0.4, "F.SilkS", "none"))
pcb.append(gr_text("U2", fl_cx, fl_cy - soic_span/2 - 1.5, 0.5))
pcb.append(gr_text("IS25LP128F", fl_cx, fl_cy + soic_span/2 + 2, 0.4))
# Pin numbers on silk
for i, num in enumerate([1,2,3,4]):
pcb.append(gr_text(str(num), fl_cx - 1.905 + i*1.27, fl_cy + soic_span/2 + 1.2, 0.3))
for i, num in enumerate([8,7,6,5]):
pcb.append(gr_text(str(num), fl_cx - 1.905 + i*1.27, fl_cy - soic_span/2 - 1.2, 0.3))
flash_pins = [
(1, -1.905, soic_span/2, 7, "FLASH_CS"),
(2, -0.635, soic_span/2, 5, "SPI_MISO"),
(3, 0.635, soic_span/2, 3, "3V3"),
(3, 0.635, soic_span/2, 3, "3V3"), # ~WP tie high
(4, 1.905, soic_span/2, 1, "GND"),
(5, 1.905, -soic_span/2, 4, "SPI_MOSI"),
(6, 0.635, -soic_span/2, 6, "SPI_SCK"),
(7, -0.635, -soic_span/2, 3, "3V3"),
(8, -1.905, -soic_span/2, 3, "3V3"),
(7, -0.635, -soic_span/2, 3, "3V3"), # ~HOLD tie high
(8, -1.905, -soic_span/2, 3, "3V3"), # VCC
]
pcb.append(fp_start("IS25LP128F", fl_cx, fl_cy))
pcb.append(fp_hdr("U2_Flash", fl_cx, fl_cy))
for pin, x, y, net, nn in flash_pins:
pcb.append(smd(str(pin), x, y, 0.6, 1.5, net, nn))
pcb.append(pad_smd(str(pin), x, y, 0.6, 1.5, net, nn))
pcb.append(" )")
# Flash decoupling C3 (0805)
pcb.append(fp_start("C3", fl_cx + 5, fl_cy))
pcb.append(smd("1", -1.0, 0, 1.0, 1.2, 3, "3V3"))
pcb.append(smd("2", 1.0, 0, 1.0, 1.2, 1, "GND"))
# Flash decoupling C3
pcb.append(fp_hdr("C3", fl_cx + 5.5, fl_cy - 1))
pcb.append(pad_smd("1", -1.0, 0, 1.0, 1.2, 3, "3V3"))
pcb.append(pad_smd("2", 1.0, 0, 1.0, 1.2, 1, "GND"))
pcb.append(" )")
pcb.append(txt("C3 100n", fl_cx + 5, fl_cy - 1.2, 0.4))
# Polarity: + mark on pin 1 side
pcb.append(txt("+", fl_cx + 3.8, fl_cy, 0.4))
# ---- Flash CS pullup R2 ----
pcb.append(fp_start("R2", fl_cx + 5, fl_cy + 3))
pcb.append(smd("1", -1.0, 0, 1.0, 1.2, 7, "FLASH_CS"))
pcb.append(smd("2", 1.0, 0, 1.0, 1.2, 3, "3V3"))
pcb.append(" )")
pcb.append(txt("R2 10K", fl_cx + 5, fl_cy + 1.8, 0.4))
# ---- Audio RC LPF: R1 + C5 ----
pcb.append(fp_start("R1", cx + 31, cy + 12))
pcb.append(smd("1", -1.0, 0, 1.0, 1.2, 16, "SIG_8")) # from D10 (audio PWM)
pcb.append(smd("2", 1.0, 0, 1.0, 1.2, 0, "AUDIO_FILT")) # to cap & JST
pcb.append(" )")
pcb.append(txt("R1 4.7K", cx+31, cy+10.8, 0.4))
pcb.append(gr_text("C3", fl_cx + 5.5, fl_cy - 2.3, 0.4))
pcb.append(gr_text("100n", fl_cx + 5.5, fl_cy + 0.3, 0.35))
pcb.append(fp_start("C5", cx + 31, cy + 14.5))
pcb.append(smd("1", -1.0, 0, 1.0, 1.2, 0, "AUDIO_FILT"))
pcb.append(smd("2", 1.0, 0, 1.0, 1.2, 1, "GND"))
# Flash CS pullup R2
pcb.append(fp_hdr("R2", fl_cx + 5.5, fl_cy + 2))
pcb.append(pad_smd("1", -1.0, 0, 1.0, 1.2, 7, "FLASH_CS"))
pcb.append(pad_smd("2", 1.0, 0, 1.0, 1.2, 3, "3V3"))
pcb.append(" )")
pcb.append(txt("C5 10n", cx+31, cy+13.3, 0.4))
# ---- TPS61220 BOOST ----
boost_cx = cx + 31
boost_cy = cy + 19
pcb.append(gr_text("R2", fl_cx + 5.5, fl_cy + 0.8, 0.4))
pcb.append(gr_text("10K", fl_cx + 5.5, fl_cy + 3.3, 0.35))
# Pin 1 dot
pcb.append(circle_mark(boost_cx - 1.2, boost_cy + 1.5))
pcb.append(txt("U1 TPS61220", boost_cx, boost_cy - 2.5, 0.5))
pcb.append(txt("●1", boost_cx - 1.2, boost_cy + 2.2, 0.4))
# ================================================================
# CARRIER: Audio RC filter (R1 + C5)
# ================================================================
pcb.append(fp_start("TPS61220", boost_cx, boost_cy))
pcb.append(smd("1", -0.65, 0.7, 0.4, 0.55, 2, "VBAT")) # VIN
pcb.append(smd("2", 0, 0.7, 0.4, 0.55, 1, "GND")) # GND
pcb.append(smd("3", 0.65, 0.7, 0.4, 0.55, 2, "VBAT")) # EN (tied to VIN)
pcb.append(smd("4", 0.65,-0.7, 0.4, 0.55, 3, "3V3")) # VOUT
pcb.append(smd("5", -0.65,-0.7, 0.4, 0.55, 8, "BOOST_SW")) # L (switch)
rc_x = ox + 32
rc_y = oy + 14
pcb.append(fp_hdr("R1", rc_x, rc_y))
pcb.append(pad_smd("1", -1.0, 0, 1.0, 1.2, 17, "S8")) # from XIAO D10
pcb.append(pad_smd("2", 1.0, 0, 1.0, 1.2, 9, "AUDIO_FILT"))
pcb.append(" )")
pcb.append(gr_text("R1 4.7K", rc_x, rc_y - 1.3, 0.4))
pcb.append(fp_hdr("C5", rc_x, rc_y + 3))
pcb.append(pad_smd("1", -1.0, 0, 1.0, 1.2, 9, "AUDIO_FILT"))
pcb.append(pad_smd("2", 1.0, 0, 1.0, 1.2, 1, "GND"))
pcb.append(" )")
pcb.append(gr_text("C5 10n", rc_x, rc_y + 1.7, 0.4))
# ================================================================
# CARRIER: TPS61220 Boost Converter
# ================================================================
bx = ox + 32
by = oy + 21
# SC-70-5 outline
pcb.append(gr_rect(bx - 1.2, by - 1.1, bx + 1.2, by + 1.1))
pcb.append(gr_circle(bx - 0.8, by + 0.6, 0.2)) # pin 1 dot
pcb.append(gr_text("U1", bx, by - 2, 0.5))
pcb.append(gr_text("TPS61220", bx, by + 2, 0.4))
pcb.append(gr_text("●1", bx - 1.5, by + 0.7, 0.35))
pcb.append(fp_hdr("U1_Boost", bx, by))
pcb.append(pad_smd("1", -0.65, 0.7, 0.4, 0.55, 2, "VBAT")) # VIN
pcb.append(pad_smd("2", 0, 0.7, 0.4, 0.55, 1, "GND")) # GND
pcb.append(pad_smd("3", 0.65, 0.7, 0.4, 0.55, 2, "VBAT")) # EN=VIN
pcb.append(pad_smd("4", 0.65, -0.7, 0.4, 0.55, 3, "3V3")) # VOUT
pcb.append(pad_smd("5", -0.65, -0.7, 0.4, 0.55, 8, "BOOST_SW")) # L
pcb.append(" )")
# L1 inductor (0805)
pcb.append(fp_start("L1", boost_cx - 4, boost_cy))
pcb.append(smd("1", -1.0, 0, 1.0, 1.2, 8, "BOOST_SW"))
pcb.append(smd("2", 1.0, 0, 1.0, 1.2, 2, "VBAT"))
# L1 (0805)
pcb.append(fp_hdr("L1", bx - 4, by))
pcb.append(pad_smd("1", -1.0, 0, 1.0, 1.2, 8, "BOOST_SW"))
pcb.append(pad_smd("2", 1.0, 0, 1.0, 1.2, 2, "VBAT"))
pcb.append(" )")
pcb.append(txt("L1 4.7u", boost_cx - 4, boost_cy - 1.2, 0.4))
pcb.append(gr_text("L1 4.7u", bx - 4, by - 1.3, 0.4))
# C1 boost input (0805)
pcb.append(fp_start("C1", boost_cx - 4, boost_cy + 3))
pcb.append(smd("1", -1.0, 0, 1.0, 1.2, 2, "VBAT"))
pcb.append(smd("2", 1.0, 0, 1.0, 1.2, 1, "GND"))
# C1 input (0805)
pcb.append(fp_hdr("C1", bx - 4, by + 3))
pcb.append(pad_smd("1", -1.0, 0, 1.0, 1.2, 2, "VBAT"))
pcb.append(pad_smd("2", 1.0, 0, 1.0, 1.2, 1, "GND"))
pcb.append(" )")
pcb.append(txt("C1 10u", boost_cx - 4, boost_cy + 1.8, 0.4))
pcb.append(gr_text("C1 10u", bx - 4, by + 1.7, 0.4))
# C2 boost output (0805)
pcb.append(fp_start("C2", boost_cx + 4, boost_cy))
pcb.append(smd("1", -1.0, 0, 1.0, 1.2, 3, "3V3"))
pcb.append(smd("2", 1.0, 0, 1.0, 1.2, 1, "GND"))
# C2 output (0805)
pcb.append(fp_hdr("C2", bx + 4, by))
pcb.append(pad_smd("1", -1.0, 0, 1.0, 1.2, 3, "3V3"))
pcb.append(pad_smd("2", 1.0, 0, 1.0, 1.2, 1, "GND"))
pcb.append(" )")
pcb.append(txt("C2 10u", boost_cx + 4, boost_cy - 1.2, 0.4))
# ---- JST SH 10-pin connector (top-right of carrier) ----
jst_cx = cx + 38
jst_cy = cy + 10
pcb.append(gr_text("C2 10u", bx + 4, by - 1.3, 0.4))
pcb.append(txt("J1 TO TOY →", jst_cx - 5, jst_cy - 4, 0.5))
pcb.append(txt("1=VBAT 2=GND 3-10=SIG", jst_cx - 5, jst_cy + 5, 0.35))
# Pin 1 mark
pcb.append(circle_mark(jst_cx - 4.5 * JST_PITCH - 0.8, jst_cy))
# ================================================================
# CARRIER: FPC-20 Connector (0.5mm pitch, bottom-contact)
# ================================================================
jst_pin_nets = [
fpc_cx = ox + 38
fpc_cy = oy + 14
fpc_pitch = 0.5
n_fpc = 20
# FPC connector outline
fpc_w = (n_fpc - 1) * fpc_pitch + 2.0
pcb.append(gr_rect(fpc_cx - fpc_w/2, fpc_cy - 2, fpc_cx + fpc_w/2, fpc_cy + 2))
pcb.append(gr_text("J1 FPC-20", fpc_cx, fpc_cy - 3, 0.5))
pcb.append(gr_text("0.5mm TO TOY", fpc_cx, fpc_cy + 3, 0.35))
pcb.append(gr_circle(fpc_cx - (n_fpc-1)*fpc_pitch/2 - 0.8, fpc_cy, 0.2)) # pin 1
# FPC pin assignment:
# 1=VBAT 2=GND 3=S1 4=S2 ... 10=S8 11-16=S9-S14(spare) 17-20=spare/GND
fpc_nets = [
(1, 2, "VBAT"), (2, 1, "GND"),
(3, 9, "SIG_1"), (4, 10, "SIG_2"), (5, 11, "SIG_3"),
(6, 12, "SIG_4"), (7, 13, "SIG_5"), (8, 14, "SIG_6"),
(9, 15, "SIG_7"), (10, 16, "SIG_8"),
(3, 10, "S1"), (4, 11, "S2"), (5, 12, "S3"), (6, 13, "S4"),
(7, 14, "S5"), (8, 15, "S6"), (9, 16, "S7"), (10, 17, "S8"),
(11, 18, "S9"), (12, 19, "S10"), (13, 20, "S11"), (14, 21, "S12"),
(15, 22, "S13"), (16, 23, "S14"), (17, 24, "S15"), (18, 25, "S16"),
(19, 1, "GND"), (20, 1, "GND"),
]
pcb.append(fp_start("J1_JST_SH10", jst_cx, jst_cy))
for pin, net, nn in jst_pin_nets:
x = -(9 * JST_PITCH / 2) + (pin - 1) * JST_PITCH
pcb.append(smd(str(pin), x, 0, 0.6, 1.2, net, nn))
# Anchor pads
pcb.append(smd("S1", -6, 2.15, 1.2, 1.8, 0, "", mask=False))
pcb.append(smd("S2", 6, 2.15, 1.2, 1.8, 0, "", mask=False))
pcb.append(fp_hdr("J1_FPC20", fpc_cx, fpc_cy))
for pin, net, nn in fpc_nets:
x = -((n_fpc - 1) * fpc_pitch / 2) + (pin - 1) * fpc_pitch
pcb.append(pad_smd(str(pin), x, 0, 0.3, 1.2, net, nn))
# Anchor/lock pads
pcb.append(pad_smd("S1", -(fpc_w/2 - 0.5), 2.5, 1.5, 1.0, 0, ""))
pcb.append(pad_smd("S2", (fpc_w/2 - 0.5), 2.5, 1.5, 1.0, 0, ""))
pcb.append(" )")
# ---- Mounting holes ----
for mx, my in [(cx + 1.5, cy + 1.5), (cx + CARRIER_W - 4, cy + CARRIER_H - 2)]:
pcb.append(fp_start(f"MH", mx, my))
# ================================================================
# CARRIER: Mounting holes
# ================================================================
for mx, my in [(ox + 2, oy + CARRIER_H - 2), (ox + CARRIER_W - 3, oy + CARRIER_H - 2)]:
pcb.append(fp_hdr("MH", mx, my))
pcb.append(f' (pad "" thru_hole circle (at 0 0) (size 3.5 3.5) (drill 2.2) (layers "*.Cu" "*.Mask") (uuid "{uid()}"))')
pcb.append(" )")
# ---- Carrier silkscreen ----
pcb.append(txt("Baby Mobile Carrier v3.0", cx + CARRIER_W/2, cy - 0.5, 0.7, bold=True))
pcb.append(txt("3V3", cx + CARRIER_W/2 + 8, cy + CARRIER_H + 0.3, 0.4))
# Back silk: wiring guide
pcb.append(txt("XIAO D0-D6 = SIG_1-7 (buttons/speaker/motor)", cx + CARRIER_W/2, cy + 5, 0.4, "B.SilkS"))
pcb.append(txt("XIAO D7=MISO D8=MOSI D9=SCK D10=AUDIO", cx + CARRIER_W/2, cy + 7, 0.4, "B.SilkS"))
pcb.append(txt("JST: 1=VBAT 2=GND 3-10=SIG_1-8", cx + CARRIER_W/2, cy + 9, 0.4, "B.SilkS"))
# ============================
# BREAKAWAY ADAPTERS
# ============================
# Carrier back silk
pcb.append(gr_text("XIAO: D0-D6SIG1-7 D7=MISO D8=MOSI D9=SCK D10=AUDIO(S8)", ox + CARRIER_W/2, oy + CARRIER_H/2, 0.4, "B.SilkS"))
pcb.append(gr_text("FPC: 1=VBAT 2=GND 3-10=S1-S8 19-20=GND", ox + CARRIER_W/2, oy + CARRIER_H/2 + 2, 0.4, "B.SilkS"))
def make_adapter(name, cx, cy, pins_per_side, row_span, n_jst_pins):
# ================================================================
# ADAPTERS: Castellated SOP breakouts with FPC connectors
# ================================================================
SOP_PITCH = 1.27
def make_adapter(name, board_cx, board_cy, pins_per_side, row_span, n_fpc_pins):
"""
Castellated SOP adapter.
Board edges run through pad centers on both long sides.
JST SH connector on one short edge (top).
Adapter board with:
- Castellated half-holes on TOP and BOTTOM long edges (SOP pads)
- FPC connector centered on F.Cu
- Board edges through SOP pad centers
"""
total_pins = pins_per_side * 2
row_len = (pins_per_side - 1) * SOP_PITCH
# Board dimensions
bw = row_len + 2.0 # a bit wider than pin span
bh = row_span # exactly the distance between pin rows — edges at pad centers
jst_tab = 3.0 # extra tab at top for JST connector
total_h = bh + jst_tab
bw = row_len + 3.0 # board width (along pin rows)
bh = row_span # board height = exactly SOP row spacing
bx = cx - bw / 2
by = cy - total_h / 2
# Board outline — edges at castellation centers
bl = board_cx - bw/2
br = board_cx + bw/2
bt = board_cy - bh/2 # top edge (pins N+1..2N)
bb = board_cy + bh/2 # bottom edge (pins 1..N)
# Board outline
pcb.append(line(bx, by, bx + bw, by))
pcb.append(line(bx + bw, by, bx + bw, by + total_h))
pcb.append(line(bx + bw, by + total_h, bx, by + total_h))
pcb.append(line(bx, by + total_h, bx, by))
# Don't draw left/right edges if they coincide with panel v-score
# (they'll be part of the panel outline or v-score already)
# Draw top and bottom edges
pcb.append(gr_line(bl, bt, br, bt))
pcb.append(gr_line(br, bt, br, bb))
pcb.append(gr_line(br, bb, bl, bb))
pcb.append(gr_line(bl, bb, bl, bt))
# Labels
pcb.append(txt(name, cx, by - 1.0, 0.6, bold=True))
# Label
pcb.append(gr_text(name, board_cx, board_cy, 0.6))
# Castellated pads — pins 1..N on bottom edge, pins N+1..2N on top edge
# Bottom edge (y = by + total_h): pins 1 through pins_per_side
# Top edge (y = by + jst_tab): pins 2N down to pins_per_side+1 (SOP convention)
pcb.append(fp_start(f"{name}_pads", cx, cy + jst_tab/2, "B.Cu"))
# Castellated pads footprint
pcb.append(fp_hdr(f"{name}_cast", board_cx, board_cy, "B.Cu"))
for i in range(pins_per_side):
pin = i + 1
x = -row_len/2 + i * SOP_PITCH
# Bottom row castellations (pins 1..N)
pcb.append(cast(str(pin), x, bh/2, 0, f""))
# Top row castellations (pins 2N..N+1, reversed per SOP convention)
net_id = pin + 9 # S1=net10, S2=net11, etc.
# Bottom edge: pins 1..N
pcb.append(pad_cast(str(pin), x, bh/2, net_id, f"S{pin}"))
# Top edge: pins 2N..N+1 (reversed, SOP convention)
top_pin = total_pins - i
pcb.append(cast(str(top_pin), x, -bh/2, 0, f""))
top_net = top_pin + 9
pcb.append(pad_cast(str(top_pin), x, -bh/2, top_net, f"S{top_pin}"))
pcb.append(" )")
# Pin number labels on top silk
# Pin labels on front silk
for i in range(pins_per_side):
x = cx - row_len/2 + i * SOP_PITCH
# Bottom row labels
pcb.append(txt(str(i+1), x, cy + jst_tab/2 + bh/2 + 0.8, 0.35))
# Top row labels
pcb.append(txt(str(total_pins - i), x, cy + jst_tab/2 - bh/2 - 0.8, 0.35))
x = board_cx - row_len/2 + i * SOP_PITCH
pcb.append(gr_text(str(i+1), x, bb + 0.7, 0.3))
pcb.append(gr_text(str(total_pins - i), x, bt - 0.7, 0.3))
# Pin 1 dot
pcb.append(circle_mark(cx - row_len/2 - 0.6, cy + jst_tab/2 + bh/2 + 0.5, 0.25))
pcb.append(gr_circle(board_cx - row_len/2 - 0.5, bb + 0.5, 0.2))
pcb.append(gr_text("●1", board_cx - row_len/2 - 0.5, bb + 1.3, 0.3))
# JST SH connector at the top tab
jst_y = by + jst_tab / 2
pcb.append(fp_start(f"{name}_JST", cx, jst_y))
for j in range(n_jst_pins):
x = -((n_jst_pins - 1) * JST_PITCH / 2) + j * JST_PITCH
pcb.append(smd(f"J{j+1}", x, 0, 0.6, 1.0, 0, ""))
# Pin labels on back silk (mirror, for soldering reference)
pcb.append(gr_text(f"{name} ●1→", board_cx, board_cy, 0.5, "B.SilkS", mirror=True))
# FPC connector centered
fpc_pitch_a = 0.5
pcb.append(fp_hdr(f"{name}_FPC", board_cx, board_cy))
for j in range(n_fpc_pins):
x = -((n_fpc_pins - 1) * fpc_pitch_a / 2) + j * fpc_pitch_a
# Map FPC pins to SOP pin nets
if j < total_pins:
sop_pin = j + 1
net_id = sop_pin + 9
nn = f"S{sop_pin}"
elif j == n_fpc_pins - 2:
net_id = 1; nn = "GND" # second-to-last = GND
elif j == n_fpc_pins - 1:
net_id = 2; nn = "VBAT" # last = VBAT
else:
net_id = 0; nn = ""
pcb.append(pad_smd(f"F{j+1}", x, 0, 0.3, 1.0, net_id, nn))
# Anchor pads
aw = (n_fpc_pins - 1) * fpc_pitch_a / 2 + 1.5
pcb.append(pad_smd("SA", -aw, 1.5, 1.2, 0.8, 0, ""))
pcb.append(pad_smd("SB", aw, 1.5, 1.2, 0.8, 0, ""))
pcb.append(" )")
pcb.append(txt("JST", cx, jst_y - 1.5, 0.35))
pcb.append(circle_mark(cx - (n_jst_pins-1)*JST_PITCH/2 - 0.8, jst_y, 0.2))
pcb.append(gr_text("FPC", board_cx, board_cy - 1.8, 0.35))
pcb.append(gr_circle(board_cx - (n_fpc_pins-1)*fpc_pitch_a/2 - 0.6, board_cy, 0.15))
# Place adapters in the strip
strip_cy = vs_y + 0.5 + ADAPTER_STRIP_H / 2
# --- SOP-8 adapter ---
make_adapter("SOP-8",
ox + 6,
vscore_y + 1 + ADAPTER_AREA_H/2,
SOP8_PINS_PER_SIDE,
3.9, # 150mil narrow row span
8) # 8 JST pins (all SOP pins)
# SOP-8: 4+4 pins, 3.9mm span, 10-pin FPC (8 signals + GND + VBAT)
make_adapter("SOP-8", ox + a8_w/2 + 0.5, strip_cy, 4, 3.9, 10)
# --- SOP-16 narrow adapter ---
make_adapter("SOP-16N",
ox + 20,
vscore_y + 1 + ADAPTER_AREA_H/2,
SOP16_PINS_PER_SIDE,
6.0, # narrow SOP-16 lead span
10) # 10 JST pins (all 16 SOP + VBAT + GND → 10 JST via mapping)
# SOP-16 narrow: 8+8 pins, 6.0mm span, 20-pin FPC (16 signals + GND + VBAT + 2 spare)
make_adapter("SOP-16N", vs_x1 + a16n_w/2, strip_cy, 8, 6.0, 20)
# --- SOP-16 wide adapter ---
make_adapter("SOP-16W",
ox + 37,
vscore_y + 1 + ADAPTER_AREA_H/2,
SOP16_PINS_PER_SIDE,
10.3, # wide SOP-16 (300mil body) lead span
10)
# SOP-16 wide: 8+8 pins, 10.3mm span, 20-pin FPC
make_adapter("SOP-16W", vs_x2 + a16w_w/2, strip_cy, 8, 10.3, 20)
# Adapter strip instructions on back silk
pcb.append(txt("Snap off adapter matching your toy's MCU. Castellations solder into SOP footprint.",
ox + PANEL_W/2, vscore_y + ADAPTER_AREA_H + 1, 0.4, "B.SilkS"))
pcb.append(txt("JST cable connects adapter to carrier board J1.",
ox + PANEL_W/2, vscore_y + ADAPTER_AREA_H + 2, 0.4, "B.SilkS"))
# ================================================================
# Panel title
# ================================================================
pcb.append(gr_text("Baby Mobile v4 — JLCPCB Panel", ox + PANEL_W/2, oy - 2, 1.0))
pcb.append(gr_text("Castellated Holes=YES V-Score=YES 2-Layer 1.6mm", ox + PANEL_W/2, oy + PANEL_H + 2, 0.5))
pcb.append(")")
return "\n".join(pcb)
# ============================================================
# Project file
# ============================================================
def generate_project():
return json.dumps({
"meta": {"filename": "baby-mobile-panel.kicad_pro", "version": 1},
"board": {"design_settings": {"rules": {"min_track_width": 0.15, "min_via_diameter": 0.4}}},
"net_settings": {"classes": [
{"name": "Default", "clearance": 0.15, "track_width": 0.25},
{"name": "Power", "clearance": 0.2, "track_width": 0.5},
]},
}, indent=2)
# ============================================================
# Main
# ============================================================
if __name__ == "__main__":
outdir = os.path.dirname(os.path.abspath(__file__))
pcb_text = generate_panel()
pcb_text = generate()
pcb_path = os.path.join(outdir, "baby-mobile-panel.kicad_pcb")
with open(pcb_path, "w") as f:
f.write(pcb_text)
print(f"Written: baby-mobile-panel.kicad_pcb ({len(pcb_text)} bytes)")
print(f"Written: {pcb_path} ({len(pcb_text)} bytes)")
proj_text = generate_project()
proj = json.dumps({
"meta": {"filename": "baby-mobile-panel.kicad_pro", "version": 1},
"board": {"design_settings": {"rules": {"min_track_width": 0.15}}},
"net_settings": {"classes": [{"name":"Default","clearance":0.15,"track_width":0.25}]},
}, indent=2)
with open(os.path.join(outdir, "baby-mobile-panel.kicad_pro"), "w") as f:
f.write(proj_text)
f.write(proj)
print(f"\nPanel contents:")
print(f" Carrier board: 42mm × 24mm")
print(f" - XIAO socket (2×7 TH, 2.54mm, 17.78mm apart)")
print(f" - IS25LP128F SOIC-8 with pin 1 dot")
print(f" - TPS61220 SC-70-5 with pin 1 dot")
print(f" - L1 4.7µH, C1/C2 10µF, C3 100nF, R1 4.7K, C5 10nF, R2 10K")
print(f" - JST SH 10-pin right-angle SMD (J1)")
print(f" - 2× M2 mounting holes")
print(f" Breakaway adapters:")
print(f" - SOP-8 (4+4 pins, 3.9mm span, 8-pin JST)")
print(f" - SOP-16 narrow (8+8 pins, 6.0mm span, 10-pin JST)")
print(f" - SOP-16 wide (8+8 pins, 10.3mm span, 10-pin JST)")
print(f" All adapters: castellated edges, pin 1 dots, numbered silk")
print("\nPanel:")
print(" Carrier (44×26mm): XIAO(accurate outline+USB-C), IS25LP128F(pin1 dot+notch), TPS61220(pin1 dot), FPC-20")
print(" SOP-8 adapter: castellation on long edges, 10-pin FPC center")
print(" SOP-16N adapter: castellation on long edges, 20-pin FPC center")
print(" SOP-16W adapter: castellation on long edges, 20-pin FPC center")
print(" V-score between carrier and adapter strip, and between each adapter")
print(" All polarized parts have ●1 markers")