silkscreen, rotate panels, fpc connector, breakout nets
This commit is contained in:
+399
-361
@@ -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-8 │ ┃ │SOP-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-D6→SIG1-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")
|
||||
|
||||
Reference in New Issue
Block a user