Files
le-mystere-professeur-zacus/hardware/projects/plip-telephone/tools/gen_si3210_lib.py
T
Claude Worker claude2 e1a116d0f5
Repo State / repo-state (push) Failing after 48s
Validate Zacus refactor / validate (pull_request) Successful in 11m10s
Repo State / repo-state (pull_request) Failing after 12m5s
feat(hw): PLIP telephone KiCad project
Custom ESP32-WROOM-32E + Si3210 SLIC board replacing the ESP32-A1S
dev kit (final PLIP target, RJ9 handset). Generated via tools/
gen_si3210_lib.py (symbol + QFN-38 footprint), gen_schematic.py
(parses standard KiCad libs, 45 instances, 157 global labels on pin
ends) and gen_pcb.py (58x38mm outline, 41 footprints placed per the
plan, nets assigned, unrouted).

ERC: 0 violations. Schematic follows the spec GPIO map and the plan's
passive network. PCB placed not routed (Freerouting + DRC + gerbers
remain — see README). BOM in JLCPCB format; CP2102N/transformer/RJ9
LCSC parts marked TBD.

WARNINGS (README): the Si3210 -72V DC-DC stage is simplified and must
be checked against the Skyworks datasheet/AN35 before fab; line
protection is minimal; human review required before ordering.
2026-06-10 12:52:43 +02:00

159 lines
6.5 KiB
Python

#!/usr/bin/env python3
"""Generate the custom Si3210 KiCad library: symbol + QFN-38 footprint.
Pin numbers and groups come from the hardware plan
(docs/superpowers/plans/2026-04-08-plip-telephone-hardware.md, Task 2/3).
Run from anywhere: python3 gen_si3210_lib.py
Outputs land in ../libs/.
"""
from pathlib import Path
LIBS = Path(__file__).resolve().parent.parent / "libs"
# (name, number, electrical_type) — order top→bottom per side.
LEFT = [
("SCLK", "34", "input"), ("SDI", "33", "input"), ("SDO", "32", "output"),
("~{CS}", "35", "input"), None,
("PCLK", "37", "input"), ("FSYNC", "2", "input"), ("DTX", "1", "output"),
("DRX", "38", "input"), None,
("~{RESET}", "3", "input"), ("~{INT}", "36", "open_collector"),
("TEST", "28", "input"), None,
("SDCH", "4", "passive"), ("SDCL", "5", "passive"),
("SDITHRU", "31", "passive"),
]
RIGHT = [
("STIPAC", "16", "passive"), ("STIPDC", "11", "passive"),
("STIPE", "13", "passive"), ("ITIPP", "24", "passive"),
("ITIPN", "25", "passive"), None,
("SRINGAC", "17", "passive"), ("SRINGDC", "12", "passive"),
("SRINGE", "15", "passive"), ("IRINGP", "22", "passive"),
("IRINGN", "21", "passive"), None,
("DCDRV", "30", "output"), ("DCFF", "29", "input"),
("SVBAT", "14", "passive"), None,
("IREF", "7", "passive"), ("CAPP", "8", "passive"),
("CAPM", "10", "passive"), ("IGMP", "20", "passive"),
("IGMN", "18", "passive"),
]
TOP = [("VDDA1", "6"), ("VDDA2", "23"), ("VDDD", "26")] # power_in
BOTTOM = [("GNDA", "19"), ("GNDD", "27"), ("QGND", "9"), ("EP", "39")] # power_in
HALF_W = 15.24
ROWS = max(len(LEFT), len(RIGHT))
TOP_Y = (ROWS - 1) * 2.54 / 2.0
HALF_H = TOP_Y + 2.54 # one row of margin above/below
PIN_LEN = 2.54
def pin(name, num, etype, x, y, angle):
return f""" (pin {etype} line (at {x:g} {y:g} {angle}) (length {PIN_LEN:g})
(name "{name}" (effects (font (size 1.27 1.27))))
(number "{num}" (effects (font (size 1.27 1.27))))
)"""
def gen_symbol() -> str:
pins = []
y = TOP_Y
for entry in LEFT:
if entry:
n, num, t = entry
pins.append(pin(n, num, t, -(HALF_W + PIN_LEN), y, 0))
y -= 2.54
y = TOP_Y
for entry in RIGHT:
if entry:
n, num, t = entry
pins.append(pin(n, num, t, HALF_W + PIN_LEN, y, 180))
y -= 2.54
xs = [-(len(TOP) - 1) * 2.54 / 2 + i * 2.54 for i in range(len(TOP))]
for (n, num), x in zip(TOP, xs):
pins.append(pin(n, num, "power_in", x, HALF_H + PIN_LEN, 270))
xs = [-(len(BOTTOM) - 1) * 2.54 / 2 + i * 2.54 for i in range(len(BOTTOM))]
for (n, num), x in zip(BOTTOM, xs):
pins.append(pin(n, num, "power_in", x, -(HALF_H + PIN_LEN), 90))
body = "\n".join(pins)
return f"""(kicad_symbol_lib (version 20231120) (generator gen_si3210_lib)
(symbol "Si3210" (in_bom yes) (on_board yes)
(property "Reference" "U" (at {-HALF_W:g} {HALF_H + 1.27:g} 0)
(effects (font (size 1.27 1.27)) (justify left)))
(property "Value" "Si3210" (at {HALF_W:g} {HALF_H + 1.27:g} 0)
(effects (font (size 1.27 1.27)) (justify right)))
(property "Footprint" "Si3210:Si3210_QFN38" (at 0 0 0)
(effects (font (size 1.27 1.27)) hide))
(property "Datasheet" "https://www.skyworksinc.com/-/media/SkyWorks/SL/documents/public/data-sheets/Si3210.pdf" (at 0 0 0)
(effects (font (size 1.27 1.27)) hide))
(property "Description" "ProSLIC subscriber line interface circuit, integrated codec + ring generator, QFN-38" (at 0 0 0)
(effects (font (size 1.27 1.27)) hide))
(symbol "Si3210_0_1"
(rectangle (start {-HALF_W:g} {HALF_H:g}) (end {HALF_W:g} {-HALF_H:g})
(stroke (width 0.254) (type default)) (fill (type background)))
)
(symbol "Si3210_1_1"
{body}
)
)
)
"""
def gen_footprint() -> str:
# QFN-38 5x7 mm, 0.5 mm pitch, pads 0.25x0.75, EP 3.4x5.4 (plan Task 3).
# 9 pads on the 7 mm (left/right) edges, 10 on the 5 mm (top/bottom).
# CCW numbering from pin 1 at top of the left edge.
pads = []
pitch = 0.5
pad_l, pad_w = 0.75, 0.25
half_body_x, half_body_y = 2.5, 3.5 # body 5 x 7
px = half_body_x - pad_l / 2 + 0.05 # pad centre just inside the edge
py = half_body_y - pad_l / 2 + 0.05
def smd(num, x, y, w, h):
pads.append(
f' (pad "{num}" smd roundrect (at {x:.3f} {y:.3f}) '
f"(size {w:g} {h:g}) (layers \"F.Cu\" \"F.Paste\" \"F.Mask\") "
f"(roundrect_rratio 0.25))"
)
n = 1
ys = [-(9 - 1) * pitch / 2 + i * pitch for i in range(9)]
for y in ys: # left edge, top→bottom
smd(n, -px, y, pad_l, pad_w); n += 1
xs = [-(10 - 1) * pitch / 2 + i * pitch for i in range(10)]
for x in xs: # bottom edge, left→right
smd(n, x, py, pad_w, pad_l); n += 1
for y in reversed(ys): # right edge, bottom→top
smd(n, px, y, pad_l, pad_w); n += 1
for x in reversed(xs): # top edge, right→left
smd(n, x, -py, pad_w, pad_l); n += 1
assert n == 39, n
pads.append(' (pad "39" smd rect (at 0 0) (size 3.4 5.4) '
'(layers "F.Cu" "F.Paste" "F.Mask"))')
return f"""(footprint "Si3210_QFN38" (version 20231120) (generator gen_si3210_lib)
(layer "F.Cu")
(descr "QFN-38 5x7mm pitch 0.5mm, exposed pad 3.4x5.4mm (Si3210 ProSLIC)")
(attr smd)
(fp_text reference "REF**" (at 0 -4.6) (layer "F.SilkS")
(effects (font (size 1 1) (thickness 0.15))))
(fp_text value "Si3210_QFN38" (at 0 4.6) (layer "F.Fab")
(effects (font (size 1 1) (thickness 0.15))))
(fp_line (start -2.5 -3.5) (end 2.5 -3.5) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
(fp_line (start 2.5 -3.5) (end 2.5 3.5) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
(fp_line (start 2.5 3.5) (end -2.5 3.5) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
(fp_line (start -2.5 3.5) (end -2.5 -3.5) (stroke (width 0.1) (type solid)) (layer "F.Fab"))
(fp_circle (center -3.2 -2.6) (end -3.0 -2.6) (stroke (width 0.2) (type solid)) (fill solid) (layer "F.SilkS"))
{chr(10).join(pads)}
)
"""
if __name__ == "__main__":
LIBS.mkdir(parents=True, exist_ok=True)
(LIBS / "Si3210.kicad_sym").write_text(gen_symbol(), encoding="utf-8")
fp_dir = LIBS / "Si3210.pretty"
fp_dir.mkdir(exist_ok=True)
(fp_dir / "Si3210_QFN38.kicad_mod").write_text(gen_footprint(), encoding="utf-8")
print("wrote", LIBS / "Si3210.kicad_sym")
print("wrote", fp_dir / "Si3210_QFN38.kicad_mod")