Files
baby-mobile/generate_kicad.py
T
zyphlar 5a18a689e0 nRF
2026-07-04 03:28:11 -07:00

554 lines
29 KiB
Python

#!/usr/bin/env python3
"""
Generate KiCad 7 schematic for Baby Mobile Audio Board v2.
Design: MDBT50Q (nRF52840) + IS25LP128F SPI Flash + PAM8302A Class-D Amp
Power: 2xAA → TPS61220 boost → 3.3V, USB-C via MCP1700 LDO, diode OR-ing
Audio: 30+ min at 8kHz/8-bit mono via 16MB SPI flash
USB: Mass storage for drag-and-drop audio upload
BLE: Wireless audio transfer from phone
FIX: Sub-symbol names (the _0_1, _1_1 variants) must NOT include the
library prefix. Only the outer symbol declaration gets "baby_mobile:".
"""
import uuid
import json
import os
LIB = "baby_mobile"
def uid():
return str(uuid.uuid4())
# ============================================================
# SYMBOL HELPERS
# ============================================================
def _sym_start(name, ref_prefix, default_value, footprint, extra_props=""):
"""Outer symbol with library prefix."""
return f"""
(symbol "{LIB}:{name}" (in_bom yes) (on_board yes)
(property "Reference" "{ref_prefix}" (at 0 1 0) (effects (font (size 1.27 1.27))))
(property "Value" "{default_value}" (at 0 -1 0) (effects (font (size 1.27 1.27))))
(property "Footprint" "{footprint}" (at 0 0 0) (effects (font (size 1.27 1.27)) hide))
(property "Datasheet" "" (at 0 0 0) (effects (font (size 1.27 1.27)) hide))
{extra_props}"""
def _unit(name, unit_id):
"""Inner sub-symbol WITHOUT library prefix — this was the bug."""
return f""" (symbol "{name}_{unit_id}" """
# ============================================================
# SYMBOL DEFINITIONS
# ============================================================
def sym_mdbt50q():
"""MDBT50Q-P nRF52840 BLE module, 32 pins exposed."""
s = _sym_start("MDBT50Q", "U", "MDBT50Q-P1MV2",
"RF_Module:Raytac_MDBT50Q",
"")
s += f"""
(symbol "MDBT50Q_0_1"
(rectangle (start -20 30) (end 20 -30) (stroke (width 0.254) (type default)) (fill (type background)))
)
(symbol "MDBT50Q_1_1"
(pin power_in line (at -5 -33 90) (length 3) (name "GND" (effects (font (size 1 1)))) (number "1" (effects (font (size 1 1)))))
(pin power_in line (at -5 33 270) (length 3) (name "VCC" (effects (font (size 1 1)))) (number "32" (effects (font (size 1 1)))))
(pin bidirectional line (at -23 20 0) (length 3) (name "P0.00/XL1" (effects (font (size 1 1)))) (number "2" (effects (font (size 1 1)))))
(pin bidirectional line (at -23 17.5 0) (length 3) (name "P0.01/XL2" (effects (font (size 1 1)))) (number "3" (effects (font (size 1 1)))))
(pin bidirectional line (at -23 15 0) (length 3) (name "P0.02/AIN0" (effects (font (size 1 1)))) (number "4" (effects (font (size 1 1)))))
(pin bidirectional line (at -23 12.5 0) (length 3) (name "P0.03/AIN1" (effects (font (size 1 1)))) (number "5" (effects (font (size 1 1)))))
(pin bidirectional line (at -23 10 0) (length 3) (name "P0.04/AIN2" (effects (font (size 1 1)))) (number "6" (effects (font (size 1 1)))))
(pin bidirectional line (at -23 7.5 0) (length 3) (name "P0.05/AIN3" (effects (font (size 1 1)))) (number "7" (effects (font (size 1 1)))))
(pin bidirectional line (at -23 5 0) (length 3) (name "P0.06" (effects (font (size 1 1)))) (number "8" (effects (font (size 1 1)))))
(pin bidirectional line (at -23 2.5 0) (length 3) (name "P0.07" (effects (font (size 1 1)))) (number "9" (effects (font (size 1 1)))))
(pin bidirectional line (at -23 0 0) (length 3) (name "P0.08" (effects (font (size 1 1)))) (number "10" (effects (font (size 1 1)))))
(pin bidirectional line (at -23 -2.5 0) (length 3) (name "P0.09/NFC1" (effects (font (size 1 1)))) (number "11" (effects (font (size 1 1)))))
(pin bidirectional line (at -23 -5 0) (length 3) (name "P0.10/NFC2" (effects (font (size 1 1)))) (number "12" (effects (font (size 1 1)))))
(pin bidirectional line (at -23 -7.5 0) (length 3) (name "P0.11" (effects (font (size 1 1)))) (number "13" (effects (font (size 1 1)))))
(pin bidirectional line (at -23 -10 0) (length 3) (name "P0.12" (effects (font (size 1 1)))) (number "14" (effects (font (size 1 1)))))
(pin bidirectional line (at -23 -12.5 0) (length 3) (name "P0.13" (effects (font (size 1 1)))) (number "15" (effects (font (size 1 1)))))
(pin bidirectional line (at -23 -15 0) (length 3) (name "P0.14" (effects (font (size 1 1)))) (number "16" (effects (font (size 1 1)))))
(pin bidirectional line (at -23 -17.5 0) (length 3) (name "P0.15" (effects (font (size 1 1)))) (number "17" (effects (font (size 1 1)))))
(pin bidirectional line (at 23 20 180) (length 3) (name "P0.16" (effects (font (size 1 1)))) (number "18" (effects (font (size 1 1)))))
(pin bidirectional line (at 23 17.5 180) (length 3) (name "P0.17" (effects (font (size 1 1)))) (number "19" (effects (font (size 1 1)))))
(pin bidirectional line (at 23 15 180) (length 3) (name "P0.18/~{{RESET}}" (effects (font (size 1 1)))) (number "20" (effects (font (size 1 1)))))
(pin bidirectional line (at 23 12.5 180) (length 3) (name "P0.19" (effects (font (size 1 1)))) (number "21" (effects (font (size 1 1)))))
(pin bidirectional line (at 23 10 180) (length 3) (name "P0.20" (effects (font (size 1 1)))) (number "22" (effects (font (size 1 1)))))
(pin bidirectional line (at 23 7.5 180) (length 3) (name "P0.21" (effects (font (size 1 1)))) (number "23" (effects (font (size 1 1)))))
(pin bidirectional line (at 23 5 180) (length 3) (name "P0.22" (effects (font (size 1 1)))) (number "24" (effects (font (size 1 1)))))
(pin bidirectional line (at 23 2.5 180) (length 3) (name "P0.23" (effects (font (size 1 1)))) (number "25" (effects (font (size 1 1)))))
(pin bidirectional line (at 23 0 180) (length 3) (name "P0.24" (effects (font (size 1 1)))) (number "26" (effects (font (size 1 1)))))
(pin bidirectional line (at 23 -2.5 180) (length 3) (name "P1.00" (effects (font (size 1 1)))) (number "27" (effects (font (size 1 1)))))
(pin bidirectional line (at 23 -5 180) (length 3) (name "D-" (effects (font (size 1 1)))) (number "28" (effects (font (size 1 1)))))
(pin bidirectional line (at 23 -7.5 180) (length 3) (name "D+" (effects (font (size 1 1)))) (number "29" (effects (font (size 1 1)))))
(pin bidirectional line (at 23 -10 180) (length 3) (name "P0.25" (effects (font (size 1 1)))) (number "30" (effects (font (size 1 1)))))
(pin bidirectional line (at 23 -12.5 180) (length 3) (name "P0.26" (effects (font (size 1 1)))) (number "31" (effects (font (size 1 1)))))
(pin input line (at 23 -17.5 180) (length 3) (name "SWDIO" (effects (font (size 1 1)))) (number "33" (effects (font (size 1 1)))))
(pin input line (at 23 -20 180) (length 3) (name "SWDCLK" (effects (font (size 1 1)))) (number "34" (effects (font (size 1 1)))))
)
)"""
return s
def sym_is25lp128f():
s = _sym_start("IS25LP128F", "U", "IS25LP128F", "Package_SO:SOIC-8_3.9x4.9mm_P1.27mm")
s += f"""
(symbol "IS25LP128F_0_1"
(rectangle (start -7 6) (end 7 -6) (stroke (width 0.254) (type default)) (fill (type background)))
)
(symbol "IS25LP128F_1_1"
(pin input line (at -10 4 0) (length 3) (name "~{{CS}}" (effects (font (size 1 1)))) (number "1" (effects (font (size 1 1)))))
(pin output line (at -10 1.5 0) (length 3) (name "SO/IO1" (effects (font (size 1 1)))) (number "2" (effects (font (size 1 1)))))
(pin input line (at -10 -1.5 0) (length 3) (name "~{{WP}}/IO2" (effects (font (size 1 1)))) (number "3" (effects (font (size 1 1)))))
(pin power_in line (at -10 -4 0) (length 3) (name "GND" (effects (font (size 1 1)))) (number "4" (effects (font (size 1 1)))))
(pin input line (at 10 -4 180) (length 3) (name "SI/IO0" (effects (font (size 1 1)))) (number "5" (effects (font (size 1 1)))))
(pin input line (at 10 -1.5 180) (length 3) (name "SCLK" (effects (font (size 1 1)))) (number "6" (effects (font (size 1 1)))))
(pin input line (at 10 1.5 180) (length 3) (name "~{{HOLD}}/IO3" (effects (font (size 1 1)))) (number "7" (effects (font (size 1 1)))))
(pin power_in line (at 10 4 180) (length 3) (name "VCC" (effects (font (size 1 1)))) (number "8" (effects (font (size 1 1)))))
)
)"""
return s
def sym_pam8302a():
s = _sym_start("PAM8302A", "U", "PAM8302A", "Package_SO:SOIC-8_3.9x4.9mm_P1.27mm")
s += f"""
(symbol "PAM8302A_0_1"
(rectangle (start -8 8) (end 8 -8) (stroke (width 0.254) (type default)) (fill (type background)))
)
(symbol "PAM8302A_1_1"
(pin input line (at -11 5 0) (length 3) (name "~{{SD}}" (effects (font (size 1 1)))) (number "1" (effects (font (size 1 1)))))
(pin power_in line (at -11 2 0) (length 3) (name "VDD" (effects (font (size 1 1)))) (number "2" (effects (font (size 1 1)))))
(pin power_in line (at -11 -1 0) (length 3) (name "GND" (effects (font (size 1 1)))) (number "3" (effects (font (size 1 1)))))
(pin input line (at -11 -4 0) (length 3) (name "A+" (effects (font (size 1 1)))) (number "4" (effects (font (size 1 1)))))
(pin input line (at -11 -7 0) (length 3) (name "A-" (effects (font (size 1 1)))) (number "5" (effects (font (size 1 1)))))
(pin output line (at 11 2 180) (length 3) (name "VO+" (effects (font (size 1 1)))) (number "8" (effects (font (size 1 1)))))
(pin output line (at 11 -1 180) (length 3) (name "VO-" (effects (font (size 1 1)))) (number "7" (effects (font (size 1 1)))))
(pin power_in line (at 11 -4 180) (length 3) (name "PAD" (effects (font (size 1 1)))) (number "6" (effects (font (size 1 1)))))
)
)"""
return s
def sym_tps61220():
s = _sym_start("TPS61220", "U", "TPS61220DCK", "Package_TO_SOT_SMD:SOT-353_SC-70-5")
s += f"""
(symbol "TPS61220_0_1"
(rectangle (start -7 5) (end 7 -5) (stroke (width 0.254) (type default)) (fill (type background)))
)
(symbol "TPS61220_1_1"
(pin power_in line (at -10 3 0) (length 3) (name "VIN" (effects (font (size 1 1)))) (number "1" (effects (font (size 1 1)))))
(pin power_in line (at -10 -3 0) (length 3) (name "GND" (effects (font (size 1 1)))) (number "2" (effects (font (size 1 1)))))
(pin input line (at 10 -3 180) (length 3) (name "EN" (effects (font (size 1 1)))) (number "3" (effects (font (size 1 1)))))
(pin power_out line (at 10 3 180) (length 3) (name "VOUT" (effects (font (size 1 1)))) (number "4" (effects (font (size 1 1)))))
(pin passive line (at 0 8 270) (length 3) (name "L" (effects (font (size 1 1)))) (number "5" (effects (font (size 1 1)))))
)
)"""
return s
def sym_mcp1700():
s = _sym_start("MCP1700", "U", "MCP1700-3302E", "Package_TO_SOT_SMD:SOT-23")
s += f"""
(symbol "MCP1700_0_1"
(rectangle (start -6 4) (end 6 -4) (stroke (width 0.254) (type default)) (fill (type background)))
)
(symbol "MCP1700_1_1"
(pin power_in line (at -9 2 0) (length 3) (name "VIN" (effects (font (size 1 1)))) (number "1" (effects (font (size 1 1)))))
(pin power_in line (at 0 -7 90) (length 3) (name "GND" (effects (font (size 1 1)))) (number "2" (effects (font (size 1 1)))))
(pin power_out line (at 9 2 180) (length 3) (name "VOUT" (effects (font (size 1 1)))) (number "3" (effects (font (size 1 1)))))
)
)"""
return s
def sym_generic_2pin(name, ref, value, fp):
"""Generic 2-pin symbol for caps, resistors, diodes, connectors."""
s = _sym_start(name, ref, value, fp)
s += f"""
(symbol "{name}_0_1"
(polyline (pts (xy -1.5 1) (xy 1.5 1)) (stroke (width 0.4) (type default)) (fill (type none)))
(polyline (pts (xy -1.5 -1) (xy 1.5 -1)) (stroke (width 0.4) (type default)) (fill (type none)))
)
(symbol "{name}_1_1"
(pin passive line (at 0 3.5 270) (length 2.5) (name "1" (effects (font (size 1 1)))) (number "1" (effects (font (size 1 1)))))
(pin passive line (at 0 -3.5 90) (length 2.5) (name "2" (effects (font (size 1 1)))) (number "2" (effects (font (size 1 1)))))
)
)"""
return s
def sym_usb_c():
s = _sym_start("USB_C", "J", "USB_C_Receptacle", "Connector_USB:USB_C_Receptacle_GCT_USB4085")
s += f"""
(symbol "USB_C_0_1"
(rectangle (start -6 8) (end 6 -8) (stroke (width 0.254) (type default)) (fill (type background)))
)
(symbol "USB_C_1_1"
(pin power_in line (at -9 6 0) (length 3) (name "VBUS" (effects (font (size 1 1)))) (number "A4" (effects (font (size 1 1)))))
(pin passive line (at -9 3 0) (length 3) (name "CC1" (effects (font (size 1 1)))) (number "A5" (effects (font (size 1 1)))))
(pin passive line (at -9 0 0) (length 3) (name "CC2" (effects (font (size 1 1)))) (number "B5" (effects (font (size 1 1)))))
(pin bidirectional line (at -9 -3 0) (length 3) (name "D+" (effects (font (size 1 1)))) (number "A6" (effects (font (size 1 1)))))
(pin bidirectional line (at -9 -6 0) (length 3) (name "D-" (effects (font (size 1 1)))) (number "A7" (effects (font (size 1 1)))))
(pin power_in line (at 9 -6 180) (length 3) (name "GND" (effects (font (size 1 1)))) (number "A1" (effects (font (size 1 1)))))
(pin passive line (at 9 0 180) (length 3) (name "SHIELD" (effects (font (size 1 1)))) (number "S1" (effects (font (size 1 1)))))
)
)"""
return s
def sym_switch():
s = _sym_start("Switch", "SW", "SW", "Button_Switch_SMD:SW_SPST_TL3342")
s += f"""
(symbol "Switch_0_1"
(circle (center -2 0) (radius 0.4) (stroke (width 0) (type default)) (fill (type none)))
(circle (center 2 0) (radius 0.4) (stroke (width 0) (type default)) (fill (type none)))
(polyline (pts (xy -1.6 0) (xy 1.6 1)) (stroke (width 0.254) (type default)) (fill (type none)))
)
(symbol "Switch_1_1"
(pin passive line (at -5 0 0) (length 3) (name "1" (effects (font (size 1 1)))) (number "1" (effects (font (size 1 1)))))
(pin passive line (at 5 0 180) (length 3) (name "2" (effects (font (size 1 1)))) (number "2" (effects (font (size 1 1)))))
)
)"""
return s
def sym_nmos():
s = _sym_start("NMOS", "Q", "AO3400A", "Package_TO_SOT_SMD:SOT-23")
s += f"""
(symbol "NMOS_0_1"
(polyline (pts (xy -0.5 2) (xy -0.5 -2)) (stroke (width 0.254) (type default)) (fill (type none)))
)
(symbol "NMOS_1_1"
(pin passive line (at 0 5 270) (length 2.5) (name "D" (effects (font (size 1 1)))) (number "3" (effects (font (size 1 1)))))
(pin input line (at -4 0 0) (length 2.5) (name "G" (effects (font (size 1 1)))) (number "1" (effects (font (size 1 1)))))
(pin passive line (at 0 -5 90) (length 2.5) (name "S" (effects (font (size 1 1)))) (number "2" (effects (font (size 1 1)))))
)
)"""
return s
def sym_inductor():
s = _sym_start("Inductor", "L", "4.7uH", "Inductor_SMD:L_0805_2012Metric")
s += f"""
(symbol "Inductor_0_1"
(arc (start 0 2.5) (mid 0.75 1.875) (end 0 1.25) (stroke (width 0.254) (type default)) (fill (type none)))
(arc (start 0 1.25) (mid 0.75 0.625) (end 0 0) (stroke (width 0.254) (type default)) (fill (type none)))
(arc (start 0 0) (mid 0.75 -0.625) (end 0 -1.25) (stroke (width 0.254) (type default)) (fill (type none)))
(arc (start 0 -1.25) (mid 0.75 -1.875) (end 0 -2.5) (stroke (width 0.254) (type default)) (fill (type none)))
)
(symbol "Inductor_1_1"
(pin passive line (at 0 5 270) (length 2.5) (name "1" (effects (font (size 1 1)))) (number "1" (effects (font (size 1 1)))))
(pin passive line (at 0 -5 90) (length 2.5) (name "2" (effects (font (size 1 1)))) (number "2" (effects (font (size 1 1)))))
)
)"""
return s
def sym_conn(name, ref, npins, fp):
s = _sym_start(name, ref, name, fp)
h = npins * 2.5 + 1
s += f"""
(symbol "{name}_0_1"
(rectangle (start -3 {h}) (end 3 {-h}) (stroke (width 0.254) (type default)) (fill (type background)))
)
(symbol "{name}_1_1"
"""
for i in range(npins):
y = h - 2 - i * 2.5
s += f""" (pin passive line (at -6 {y:.1f} 0) (length 3) (name "P{i+1}" (effects (font (size 1 1)))) (number "{i+1}" (effects (font (size 1 1)))))
"""
s += """ )
)"""
return s
# ============================================================
# INSTANCE HELPER
# ============================================================
def _inst(lib_name, ref, value, footprint, x, y, angle=0):
return f""" (symbol (lib_id "{LIB}:{lib_name}") (at {x} {y} {angle}) (unit 1)
(in_bom yes) (on_board yes) (dnp no)
(uuid "{uid()}")
(property "Reference" "{ref}" (at {x+3} {y-3} 0) (effects (font (size 1.27 1.27))))
(property "Value" "{value}" (at {x+3} {y+3} 0) (effects (font (size 1.27 1.27))))
(property "Footprint" "{footprint}" (at {x} {y} 0) (effects (font (size 1.27 1.27)) hide))
(property "Datasheet" "" (at {x} {y} 0) (effects (font (size 1.27 1.27)) hide))
)"""
# ============================================================
# MAIN SCHEMATIC
# ============================================================
def generate_schematic():
sch = []
sch.append(f"""(kicad_sch (version 20230121) (generator "baby_mobile_gen_v2")
(uuid "{uid()}")
(paper "A3")
(title_block
(title "Baby Mobile Audio Board v2")
(date "2026-03-09")
(rev "2.0")
(comment 1 "MDBT50Q (nRF52840) + IS25LP128F + PAM8302A")
(comment 2 "2xAA Battery + USB-C, Diode OR, TPS61220 Boost")
(comment 3 "BLE + USB mass storage for audio upload")
(comment 4 "8-ohm 0.25W Speaker, DC Motor, 8 Buttons")
)
""")
# ---- LIB_SYMBOLS ----
sch.append(" (lib_symbols")
sch.append(sym_mdbt50q())
sch.append(sym_is25lp128f())
sch.append(sym_pam8302a())
sch.append(sym_tps61220())
sch.append(sym_mcp1700())
sch.append(sym_generic_2pin("Capacitor", "C", "C", "Capacitor_SMD:C_0805_2012Metric"))
sch.append(sym_generic_2pin("Resistor", "R", "R", "Resistor_SMD:R_0805_2012Metric"))
sch.append(sym_generic_2pin("Diode_Schottky", "D", "BAT54S", "Diode_SMD:D_SOT-23_ANK"))
sch.append(sym_generic_2pin("LED", "D", "LED", "LED_SMD:LED_0805_2012Metric"))
sch.append(sym_generic_2pin("Conn_2Pin", "J", "Conn_2", "Connector_JST:JST_PH_S2B-PH-K_1x02_P2.00mm_Horizontal"))
sch.append(sym_switch())
sch.append(sym_nmos())
sch.append(sym_inductor())
sch.append(sym_usb_c())
sch.append(sym_conn("SWD_Header", "J", 4, "Connector_PinHeader_2.54mm:PinHeader_1x04_P2.54mm_Vertical"))
sch.append(sym_conn("Serial_Header", "J", 3, "Connector_PinHeader_2.54mm:PinHeader_1x03_P2.54mm_Vertical"))
sch.append(" )") # end lib_symbols
# ---- COMPONENT INSTANCES ----
# == POWER: Battery path ==
sch.append(_inst("Conn_2Pin", "J1", "BATTERY", "Connector_JST:JST_PH_S2B-PH-K_1x02_P2.00mm_Horizontal", 20, 40))
sch.append(_inst("Switch", "SW9", "PWR", "Button_Switch_THT:SW_SPDT_CK-JS102011SAQN", 38, 35))
sch.append(_inst("Diode_Schottky", "D1", "BAT54S", "Diode_SMD:D_SOT-23_ANK", 55, 35)) # battery diode
sch.append(_inst("Capacitor", "C1", "100uF", "Capacitor_THT:CP_Radial_D5.0mm_P2.00mm", 48, 45))
# == POWER: Boost converter TPS61220 ==
sch.append(_inst("TPS61220", "U4", "TPS61220DCK", "Package_TO_SOT_SMD:SOT-353_SC-70-5", 80, 40))
sch.append(_inst("Inductor", "L1", "4.7uH", "Inductor_SMD:L_0805_2012Metric", 80, 28))
sch.append(_inst("Capacitor", "C9", "10uF", "Capacitor_SMD:C_0805_2012Metric", 68, 45)) # boost input
sch.append(_inst("Capacitor", "C10", "10uF", "Capacitor_SMD:C_0805_2012Metric", 95, 45)) # boost output
# == POWER: USB path ==
sch.append(_inst("USB_C", "J6", "USB_C", "Connector_USB:USB_C_Receptacle_GCT_USB4085", 20, 80))
sch.append(_inst("Resistor", "R7", "5.1K", "Resistor_SMD:R_0805_2012Metric", 38, 83)) # CC1
sch.append(_inst("Resistor", "R8", "5.1K", "Resistor_SMD:R_0805_2012Metric", 38, 88)) # CC2
sch.append(_inst("Diode_Schottky", "D3", "BAT54S", "Diode_SMD:D_SOT-23_ANK", 55, 74)) # USB diode
sch.append(_inst("MCP1700", "U5", "MCP1700-3302E", "Package_TO_SOT_SMD:SOT-23", 72, 74))
sch.append(_inst("Capacitor", "C11", "1uF", "Capacitor_SMD:C_0805_2012Metric", 62, 82)) # LDO in
sch.append(_inst("Capacitor", "C12", "1uF", "Capacitor_SMD:C_0805_2012Metric", 82, 82)) # LDO out
# == MCU: MDBT50Q (nRF52840 module) ==
sch.append(_inst("MDBT50Q", "U1", "MDBT50Q-P1MV2", "RF_Module:Raytac_MDBT50Q", 160, 100))
# MCU decoupling
sch.append(_inst("Capacitor", "C2", "100nF", "Capacitor_SMD:C_0805_2012Metric", 135, 60))
sch.append(_inst("Capacitor", "C3", "10uF", "Capacitor_SMD:C_0805_2012Metric", 128, 60))
# == SPI FLASH: IS25LP128F ==
sch.append(_inst("IS25LP128F", "U2", "IS25LP128F", "Package_SO:SOIC-8_3.9x4.9mm_P1.27mm", 260, 60))
sch.append(_inst("Capacitor", "C5", "100nF", "Capacitor_SMD:C_0805_2012Metric", 285, 50))
# == AUDIO AMP: PAM8302A ==
sch.append(_inst("PAM8302A", "U3", "PAM8302A", "Package_SO:SOIC-8_3.9x4.9mm_P1.27mm", 260, 140))
sch.append(_inst("Resistor", "R2", "4.7K", "Resistor_SMD:R_0805_2012Metric", 220, 135)) # LPF R
sch.append(_inst("Capacitor", "C7", "10nF", "Capacitor_SMD:C_0805_2012Metric", 230, 145)) # LPF C
sch.append(_inst("Capacitor", "C6", "1uF", "Capacitor_SMD:C_0805_2012Metric", 240, 135)) # coupling
sch.append(_inst("Capacitor", "C8", "10uF", "Capacitor_SMD:C_0805_2012Metric", 285, 130)) # amp bypass
sch.append(_inst("Resistor", "R3", "100K", "Resistor_SMD:R_0805_2012Metric", 240, 155)) # SD pulldown
sch.append(_inst("Conn_2Pin", "J2", "SPEAKER", "Connector_JST:JST_PH_S2B-PH-K_1x02_P2.00mm_Horizontal", 290, 145))
# == MOTOR DRIVER ==
sch.append(_inst("NMOS", "Q1", "AO3400A", "Package_TO_SOT_SMD:SOT-23", 160, 200))
sch.append(_inst("Resistor", "R4", "100R", "Resistor_SMD:R_0805_2012Metric", 140, 200))
sch.append(_inst("Resistor", "R5", "100K", "Resistor_SMD:R_0805_2012Metric", 150, 212))
sch.append(_inst("Diode_Schottky", "D4", "SS14", "Diode_SMD:D_SMA", 175, 190, 90))
sch.append(_inst("Conn_2Pin", "J3", "MOTOR", "Connector_JST:JST_PH_S2B-PH-K_1x02_P2.00mm_Horizontal", 180, 180))
# == 8 BUTTONS ==
for i in range(8):
y = 80 + i * 14
sch.append(_inst("Switch", f"SW{i+1}", f"BTN{i+1}", "Button_Switch_SMD:SW_SPST_TL3342", 300, y))
# == STATUS LED ==
sch.append(_inst("LED", "D2", "LED_GRN", "LED_SMD:LED_0805_2012Metric", 115, 200))
sch.append(_inst("Resistor", "R6", "1K", "Resistor_SMD:R_0805_2012Metric", 105, 200))
# == HEADERS ==
sch.append(_inst("SWD_Header", "J4", "SWD", "Connector_PinHeader_2.54mm:PinHeader_1x04_P2.54mm_Vertical", 200, 60))
sch.append(_inst("Serial_Header", "J5", "SERIAL", "Connector_PinHeader_2.54mm:PinHeader_1x03_P2.54mm_Vertical", 200, 30))
# ---- NET LABELS ----
labels = [
# 3.3V rail (output of boost OR LDO)
("3V3", 95, 37), ("3V3", 135, 55), ("3V3", 128, 55),
("3V3", 285, 45), ("3V3", 285, 125), ("3V3", 155, 67),
("3V3", 82, 72), ("3V3", 180, 175),
# Battery rail (pre-boost)
("VBAT", 48, 35), ("VBAT", 55, 30),
("VBAT", 68, 37),
# Boost input/output
("VBOOST_IN", 65, 37), ("VBOOST_IN", 70, 37),
("3V3", 90, 37),
# USB VBUS
("USB_VBUS", 29, 74), ("USB_VBUS", 55, 69),
("USB_LDO_OUT", 81, 72),
# GND everywhere
("GND", 48, 55), ("GND", 68, 55), ("GND", 95, 55),
("GND", 80, 50), ("GND", 155, 133),
("GND", 260, 70), ("GND", 285, 55), ("GND", 285, 155),
("GND", 160, 215), ("GND", 150, 218),
("GND", 230, 155), ("GND", 240, 165),
("GND", 62, 92), ("GND", 82, 92), ("GND", 72, 81),
("GND", 38, 93), ("GND", 38, 98),
("GND", 135, 65), ("GND", 128, 65),
# USB data
("USB_D+", 29, 77), ("USB_D+", 183, 92.5),
("USB_D-", 29, 80), ("USB_D-", 183, 95),
# USB CC (to pulldowns)
("USB_CC1", 29, 83), ("USB_CC1", 38, 78),
("USB_CC2", 29, 86), ("USB_CC2", 38, 83),
# SPI bus
("SPI_MOSI", 183, 87.5), ("SPI_MOSI", 250, 56),
("SPI_MISO", 183, 85), ("SPI_MISO", 250, 61.5),
("SPI_SCK", 183, 82.5), ("SPI_SCK", 270, 58.5),
("FLASH_CS", 183, 80), ("FLASH_CS", 250, 64),
# Audio
("AUDIO_PWM", 183, 100), ("AUDIO_PWM", 210, 135),
("AMP_SD", 183, 102.5), ("AMP_SD", 240, 150),
# Motor
("MOTOR_PWM", 137, 112.5), ("MOTOR_PWM", 130, 200),
# Buttons (directly to GPIOs)
("BTN1", 295, 80), ("BTN1", 137, 120),
("BTN2", 295, 94), ("BTN2", 137, 117.5),
("BTN3", 295, 108), ("BTN3", 137, 115),
("BTN4", 295, 122), ("BTN4", 137, 112.5),
("BTN5", 295, 136), ("BTN5", 137, 110),
("BTN6", 295, 150), ("BTN6", 137, 107.5),
("BTN7", 295, 164), ("BTN7", 137, 105),
("BTN8", 295, 178), ("BTN8", 137, 102.5),
# Button GND side
("GND", 305, 80), ("GND", 305, 94), ("GND", 305, 108), ("GND", 305, 122),
("GND", 305, 136), ("GND", 305, 150), ("GND", 305, 164), ("GND", 305, 178),
# LED
("LED_OUT", 95, 200), ("LED_OUT", 137, 100),
# SWD header
("SWDIO", 194, 55.5), ("SWDIO", 183, 82.5),
("SWDCLK", 194, 58), ("SWDCLK", 183, 80),
("3V3", 194, 53), ("GND", 194, 60.5),
# Serial header
("TXD", 194, 25.5), ("TXD", 137, 80),
("RXD", 194, 28), ("RXD", 137, 82.5),
("GND", 194, 30.5),
# Flash WP and HOLD tied to VCC
("3V3", 250, 58.5), ("3V3", 270, 61.5),
# Boost EN tied to VIN (always on when battery present)
("VBAT", 90, 43),
]
for name, x, y in labels:
sch.append(f""" (label "{name}" (at {x} {y} 0) (effects (font (size 1.27 1.27))) (uuid "{uid()}"))""")
# ---- TEXT NOTES ----
notes = [
(10, 12, "POWER: 2xAA → BAT54S → TPS61220 boost → 3.3V (1.8-3.2V input)"),
(10, 15, "USB: VBUS → BAT54S → MCP1700 LDO → 3.3V (safe with any battery chemistry)"),
(10, 18, "MCU: MDBT50Q (nRF52840) — BLE 5.0, USB 2.0, 1.7-5.5V, UF2 bootloader"),
(10, 21, "FLASH: IS25LP128F 16MB (2.3-3.6V) = ~33 min @ 8kHz 8-bit PCM"),
(10, 24, "AMP: PAM8302A Class-D, 90% eff, Vmin=2.0V, shutdown pin for power save"),
(10, 27, "MOTOR: AO3400A logic-level N-MOSFET, PWM speed control"),
(10, 230, "USB-C: Drag-and-drop audio as mass storage device"),
(10, 233, "BLE: Wireless audio upload from phone (Web Bluetooth compatible)"),
(10, 236, "FIRMWARE: UF2 bootloader — update by drag-and-drop, no programmer needed"),
(10, 239, "RUNTIME: Audio+Motor ~25h, Audio only ~167h, Sleep ~years on 2xAA"),
]
for x, y, text in notes:
sch.append(f""" (text "{text}" (at {x} {y} 0)
(effects (font (size 1.5 1.5)) (justify left))
(uuid "{uid()}")
)""")
sch.append(")") # end kicad_sch
return "\n".join(sch)
# ============================================================
# PROJECT FILE
# ============================================================
def generate_project():
return json.dumps({
"meta": {"filename": "baby-mobile-v2.kicad_pro", "version": 1},
"board": {
"design_settings": {
"defaults": {"board_outline_line_width": 0.1, "copper_line_width": 0.2},
"rules": {"min_copper_edge_clearance": 0.3, "min_track_width": 0.2,
"min_via_diameter": 0.6, "min_via_annular_width": 0.13}
}
},
"net_settings": {
"classes": [{"name": "Default", "clearance": 0.2, "track_width": 0.3,
"via_diameter": 0.6, "via_drill": 0.3}]
},
"schematic": {"legacy_lib_dir": "", "legacy_lib_list": []},
"sheets": [["", ""]]
}, indent=2)
# ============================================================
# MAIN
# ============================================================
if __name__ == "__main__":
outdir = os.path.dirname(os.path.abspath(__file__))
sch = generate_schematic()
sch_path = os.path.join(outdir, "baby-mobile-v2.kicad_sch")
with open(sch_path, "w") as f:
f.write(sch)
print(f"Written: {sch_path} ({len(sch)} bytes)")
proj = generate_project()
proj_path = os.path.join(outdir, "baby-mobile-v2.kicad_pro")
with open(proj_path, "w") as f:
f.write(proj)
print(f"Written: {proj_path} ({len(proj)} bytes)")
# Validate: check no sub-symbol has library prefix
import re
outer = re.findall(r'\(symbol "baby_mobile:[^"]+?"', sch)
inner_bad = re.findall(r'\(symbol "baby_mobile:[^"]+?_[01]_1"', sch)
print(f"\nValidation: {len(outer)} outer symbols, {len(inner_bad)} bad inner symbols")
if inner_bad:
print("ERROR: Found inner symbols with library prefix:")
for m in inner_bad:
print(f" {m}")
else:
print("OK: All inner sub-symbols correctly omit library prefix")