merge panels

This commit is contained in:
zyphlar
2026-07-04 03:28:12 -07:00
parent 5a18a689e0
commit f770479fc4
11 changed files with 1758 additions and 1638 deletions
+505
View File
@@ -0,0 +1,505 @@
#!/usr/bin/env python3
"""
Generate KiCad 7 combined panel PCB for Baby Mobile project.
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] │ │
│ └──────────┘ └────────────┘ └────────────┘ │
└─────────────────────────────────────────────────────┘
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)
"""
import uuid, os, json
def uid():
return str(uuid.uuid4())
# ============================================================
# Geometry Constants
# ============================================================
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
SOP_PITCH = 1.27
SOP8_PINS_PER_SIDE = 4
SOP16_PINS_PER_SIDE = 8
# SOP pad dimensions
PAD_W = 0.6
PAD_H = 1.5
# Castellated hole
CAST_DRILL = 0.6
CAST_PAD = 1.0
# 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"):
return f"""
(footprint "panel:{name}" (layer "{layer}")
(at {x} {y})
(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))"""
# ============================================================
# Generate Panel
# ============================================================
def generate_panel():
ox, oy = 100, 100 # KiCad origin offset
pcb = []
# --- Header ---
pcb.append(f"""(kicad_pcb (version 20221018) (generator "panel_gen_v3")
(general (thickness 1.6))
(paper "A4")
(title_block
(title "Baby Mobile — Combined Panel")
(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")
)
(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")
)
(setup (pad_to_mask_clearance 0.05))
(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")
""")
# ============================
# PANEL OUTLINE (Edge.Cuts)
# ============================
# 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))
# 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
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
# ============================
cx = ox + 3 # carrier area left edge (with margin)
cy = oy + 1 # carrier area top edge
# ---- XIAO SOCKET ----
xiao_cx = cx + 13
xiao_cy = cy + CARRIER_H/2
xiao_w, xiao_h = 21, 17.5
row_half = 17.78 / 2
# 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))
# Pin labels along XIAO outline
left_labels = ["D0", "D1", "D2", "D3", "D4", "D5", "D6"]
right_labels = ["D10", "D9", "D8", "D7", "3V3", "GND", "5V"]
# 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))
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}"))
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))
# 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))
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))
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
soic_span = 5.4
# 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))
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"),
(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"),
]
pcb.append(fp_start("IS25LP128F", 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(" )")
# 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"))
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(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"))
pcb.append(" )")
pcb.append(txt("C5 10n", cx+31, cy+13.3, 0.4))
# ---- TPS61220 BOOST ----
boost_cx = cx + 31
boost_cy = cy + 19
# 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))
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)
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"))
pcb.append(" )")
pcb.append(txt("L1 4.7u", boost_cx - 4, boost_cy - 1.2, 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"))
pcb.append(" )")
pcb.append(txt("C1 10u", boost_cx - 4, boost_cy + 1.8, 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"))
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(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))
jst_pin_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"),
]
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(" )")
# ---- 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))
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
# ============================
def make_adapter(name, cx, cy, pins_per_side, row_span, n_jst_pins):
"""
Castellated SOP adapter.
Board edges run through pad centers on both long sides.
JST SH connector on one short edge (top).
"""
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
bx = cx - bw / 2
by = cy - total_h / 2
# 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))
# Labels
pcb.append(txt(name, cx, by - 1.0, 0.6, bold=True))
# 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"))
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)
top_pin = total_pins - i
pcb.append(cast(str(top_pin), x, -bh/2, 0, f""))
pcb.append(" )")
# Pin number labels on top 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))
# Pin 1 dot
pcb.append(circle_mark(cx - row_len/2 - 0.6, cy + jst_tab/2 + bh/2 + 0.5, 0.25))
# 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, ""))
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))
# --- 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-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 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)
# 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"))
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_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)")
proj_text = generate_project()
with open(os.path.join(outdir, "baby-mobile-panel.kicad_pro"), "w") as f:
f.write(proj_text)
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")