merge panels
This commit is contained in:
@@ -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")
|
||||
Reference in New Issue
Block a user