metronome/hardware/eda/circuits/audio_chain.py
Me Here 617bb5a8b2 PM_K-1 hardware: integrate audio chain into one netlist (dedup shared parts)
circuits/audio_chain.py wires stages 1/1b/2/3/4 with shared nets and deduplicated
parts: ONE OPA1612 (U4) does both the Stage-2 filter (A) and Stage-3 summer (B);
ONE ULN2003 (U6) drives all three relays (K1 select, K2 mute, K3 ground-lift).
54 components, ERC 0 errors, netlist 0 errors. Per-stage files remain as the
documented, individually-simulated building blocks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 20:41:39 -05:00

195 lines
11 KiB
Python

#!/usr/bin/env python3
"""PM_K-1 audio chain - INTEGRATED board netlist (SKiDL).
Wires stages 1, 1b, 2, 3, 4 into one netlist with shared nets and DEDUPLICATED parts:
* the Stage-2 reconstruction filter and the Stage-3 summer share ONE OPA1612 dual
(U4: section A = filter, section B = summer) -- no parked half.
* the three relays (K1 select, K2 mute, K3 ground-lift) share ONE ULN2003 (U6).
This is the source of truth for the integrated audio netlist; the per-stage circuits/
files remain as the documented, individually-simulated building blocks.
Run INSIDE the EDA container:
cd hardware/eda && ./run.sh python3 ../eda/circuits/audio_chain.py
Outputs ERC + hardware/kicad/audio_chain.net.
All IC pinouts datasheet-verified (see the per-stage files for the citations):
THAT1240 1=Ref 2=In- 3=In+ 4=Vee 5=Sns 6=Vout 7=Vcc 8=NC
OPA1641 1=NC 2=-IN 3=+IN 4=V- 5=NC 6=OUT 7=V+ 8=NC
OPA1612 1=OUTA 2=-INA 3=+INA 4=V- 5=+INB 6=-INB 7=OUTB 8=V+
PCM5102A (TSSOP-20) per TI SLAS859C
THAT1646 1=Out- 2=Sns- 3=Gnd 4=In 5=Vee 6=Vcc 7=Sns+ 8=Out+
TQ2SA coil 1/10; pole1 COM3/NC4/NO2; pole2 COM8/NC7/NO9
ULN2003A in 1B-7B=1..7, out 1C-7C=16..10, GND=8, COM=9
"""
import os
from skidl import *
set_default_tool(KICAD9)
R = Part("Device", "R", dest=TEMPLATE, footprint="Resistor_SMD:R_0805_2012Metric")
C = Part("Device", "C", dest=TEMPLATE, footprint="Capacitor_SMD:C_0805_2012Metric")
D = Part("Device", "D", dest=TEMPLATE, footprint="Diode_SMD:D_SOD-323")
# ---------------- nets ----------------
p15, n15, p3v3, p5, gnd = Net("+15V"), Net("-15V"), Net("+3V3"), Net("+5V"), Net("GND")
for n in (p15, n15, p3v3, p5, gnd):
n.drive = POWER
# audio signal flow
ain_hot, ain_cold = Net("AIN_HOT"), Net("AIN_COLD") # input jack tip/ring
rx_hot_in = Net("RX_HOT_IN") # relay-routed hot -> receiver
rx_out, di_in, di_out = Net("RX_OUT"), Net("DI_IN"), Net("DI_OUT")
stage1_out, click_out, mix_out = Net("STAGE1_OUT"), Net("CLICK_OUT"), Net("MIX_OUT")
aout_hot, aout_cold, chassis = Net("AOUT_HOT"), Net("AOUT_COLD"), Net("CHASSIS")
# DAC clocks/data + control
mclk, i2s_bck, i2s_din, i2s_lrck = Net("MCLK"), Net("I2S_BCK"), Net("I2S_DIN"), Net("I2S_LRCK")
dac_xsmt = Net("DAC_XSMT")
sel_linst, mute_en, gndlift_en = Net("SEL_LINST"), Net("MUTE_EN"), Net("GNDLIFT_EN")
k1_drv, k2_drv, k3_drv = Net("K1_DRV"), Net("K2_DRV"), Net("K3_DRV")
# ---------------- part definitions ----------------
def mk(name, pins, ref_prefix="U", fp="Package_SO:SOIC-8_3.9x4.9mm_P1.27mm"):
return Part(name=name, tool=SKIDL, dest=TEMPLATE, ref_prefix=ref_prefix, footprint=fp, pins=pins)
P = Pin.types
THAT1240 = mk("THAT1240", [Pin(num=1,name="REF",func=P.INPUT),Pin(num=2,name="IN-",func=P.INPUT),
Pin(num=3,name="IN+",func=P.INPUT),Pin(num=4,name="V-",func=P.PWRIN),Pin(num=5,name="SENSE",func=P.PASSIVE),
Pin(num=6,name="OUT",func=P.OUTPUT),Pin(num=7,name="V+",func=P.PWRIN),Pin(num=8,name="NC",func=P.NOCONNECT)])
OPA1641 = mk("OPA1641", [Pin(num=1,name="NC1",func=P.NOCONNECT),Pin(num=2,name="-IN",func=P.INPUT),
Pin(num=3,name="+IN",func=P.INPUT),Pin(num=4,name="V-",func=P.PWRIN),Pin(num=5,name="NC5",func=P.NOCONNECT),
Pin(num=6,name="OUT",func=P.OUTPUT),Pin(num=7,name="V+",func=P.PWRIN),Pin(num=8,name="NC8",func=P.NOCONNECT)])
OPA1612 = mk("OPA1612", [Pin(num=1,name="OUTA",func=P.OUTPUT),Pin(num=2,name="-INA",func=P.INPUT),
Pin(num=3,name="+INA",func=P.INPUT),Pin(num=4,name="V-",func=P.PWRIN),Pin(num=5,name="+INB",func=P.INPUT),
Pin(num=6,name="-INB",func=P.INPUT),Pin(num=7,name="OUTB",func=P.OUTPUT),Pin(num=8,name="V+",func=P.PWRIN)])
THAT1646 = mk("THAT1646", [Pin(num=1,name="OUT-",func=P.OUTPUT),Pin(num=2,name="SNS-",func=P.INPUT),
Pin(num=3,name="GND",func=P.PWRIN),Pin(num=4,name="IN",func=P.INPUT),Pin(num=5,name="V-",func=P.PWRIN),
Pin(num=6,name="V+",func=P.PWRIN),Pin(num=7,name="SNS+",func=P.INPUT),Pin(num=8,name="OUT+",func=P.OUTPUT)])
PCM5102A = mk("PCM5102A", [Pin(num=1,name="CPVDD",func=P.PWRIN),Pin(num=2,name="CAPP",func=P.PASSIVE),
Pin(num=3,name="CPGND",func=P.PWRIN),Pin(num=4,name="CAPM",func=P.PASSIVE),Pin(num=5,name="VNEG",func=P.PASSIVE),
Pin(num=6,name="OUTL",func=P.OUTPUT),Pin(num=7,name="OUTR",func=P.OUTPUT),Pin(num=8,name="AVDD",func=P.PWRIN),
Pin(num=9,name="AGND",func=P.PWRIN),Pin(num=10,name="DEMP",func=P.INPUT),Pin(num=11,name="FLT",func=P.INPUT),
Pin(num=12,name="SCK",func=P.INPUT),Pin(num=13,name="BCK",func=P.INPUT),Pin(num=14,name="DIN",func=P.INPUT),
Pin(num=15,name="LRCK",func=P.INPUT),Pin(num=16,name="FMT",func=P.INPUT),Pin(num=17,name="XSMT",func=P.INPUT),
Pin(num=18,name="LDOO",func=P.PWROUT),Pin(num=19,name="DGND",func=P.PWRIN),Pin(num=20,name="DVDD",func=P.PWRIN)],
fp="Package_SO:TSSOP-20_4.4x6.5mm_P0.65mm")
TQ2SA = mk("TQ2SA-5V", [Pin(num=1,name="COIL_A",func=P.PASSIVE),Pin(num=10,name="COIL_B",func=P.PASSIVE),
Pin(num=3,name="P1_COM",func=P.PASSIVE),Pin(num=4,name="P1_NC",func=P.PASSIVE),Pin(num=2,name="P1_NO",func=P.PASSIVE),
Pin(num=8,name="P2_COM",func=P.PASSIVE),Pin(num=7,name="P2_NC",func=P.PASSIVE),Pin(num=9,name="P2_NO",func=P.PASSIVE),
Pin(num=5,name="NC5",func=P.NOCONNECT),Pin(num=6,name="NC6",func=P.NOCONNECT)],
ref_prefix="K", fp="Relay_SMD:Relay_DPDT_Panasonic_TQ2-SA")
ULN2003 = mk("ULN2003A", [Pin(num=1,name="1B",func=P.INPUT),Pin(num=2,name="2B",func=P.INPUT),
Pin(num=3,name="3B",func=P.INPUT),Pin(num=4,name="4B",func=P.INPUT),Pin(num=5,name="5B",func=P.INPUT),
Pin(num=6,name="6B",func=P.INPUT),Pin(num=7,name="7B",func=P.INPUT),Pin(num=8,name="GND",func=P.PWRIN),
Pin(num=9,name="COM",func=P.PWRIN),Pin(num=10,name="7C",func=P.OPENCOLL),Pin(num=11,name="6C",func=P.OPENCOLL),
Pin(num=12,name="5C",func=P.OPENCOLL),Pin(num=13,name="4C",func=P.OPENCOLL),Pin(num=14,name="3C",func=P.OPENCOLL),
Pin(num=15,name="2C",func=P.OPENCOLL),Pin(num=16,name="1C",func=P.OPENCOLL)],
fp="Package_SO:SOIC-16_3.9x9.9mm_P1.27mm")
# instances (deduplicated)
u1 = THAT1240(ref="U1") # line receiver
u2 = OPA1641(ref="U2") # Hi-Z DI buffer
u3 = PCM5102A(ref="U3") # DAC
u4 = OPA1612(ref="U4") # A = recon filter, B = summer (shared dual)
u5 = THAT1646(ref="U5") # balanced driver
u6 = ULN2003(ref="U6") # shared relay driver
k1, k2, k3 = TQ2SA(ref="K1"), TQ2SA(ref="K2"), TQ2SA(ref="K3")
def dcp(rail): # 100nF decoupling helper
c = C(value="100nF"); rail += c[1]; c[2] += gnd
# ---------------- Stage 1: balanced line receiver + protection ----------------
def protect_bal(src, tag):
cblk = C(value="2.2uF", footprint="Capacitor_SMD:C_1206_3216Metric")
rs, rb = R(value="1k"), R(value="1Meg")
dp, dn = D(value="1N4148WS"), D(value="1N4148WS")
node = Net(tag)
src += cblk[1]; cblk[2] += rs[1]; rs[2] += node
rb[1] += node; rb[2] += gnd
dp[1] += p15; dp[2] += node # Device:D pin1=K,pin2=A : high clamp
dn[1] += node; dn[2] += n15 # low clamp
return node
u1["IN+"] += protect_bal(rx_hot_in, "RXIN_P") # hot comes from the select relay (NC)
u1["IN-"] += protect_bal(ain_cold, "RXIN_N") # cold is the ring (direct)
u1["REF"] += gnd; u1["SENSE"] += rx_out; u1["OUT"] += rx_out
u1["V+"] += p15; u1["V-"] += n15; dcp(p15); dcp(n15)
# ---------------- Stage 1b: Hi-Z DI buffer (OPA1641) ----------------
cblk = C(value="100nF", footprint="Capacitor_SMD:C_1206_3216Metric")
rbias = R(value="1Meg"); dp, dn = D(value="1N4148WS"), D(value="1N4148WS")
di_node = Net("DI_NODE")
di_in += cblk[1]; cblk[2] += di_node
rbias[1] += di_node; rbias[2] += gnd
dp[1] += p15; dp[2] += di_node
dn[1] += di_node; dn[2] += n15
u2["+IN"] += di_node
rf2, rg2 = R(value="3k"), R(value="1k") # +12 dB
u2["OUT"] += di_out
rf2[1] += di_out; rf2[2] += u2["-IN"]
rg2[1] += u2["-IN"]; rg2[2] += gnd
u2["V+"] += p15; u2["V-"] += n15; dcp(p15); dcp(n15)
# ---------------- Stage 1b: select relay K1 ----------------
k1["P1_COM"] += ain_hot; k1["P1_NC"] += rx_hot_in; k1["P1_NO"] += di_in # route tip
k1["P2_COM"] += stage1_out; k1["P2_NC"] += rx_out; k1["P2_NO"] += di_out # select output
k1["COIL_A"] += p5; k1["COIL_B"] += k1_drv
# ---------------- Stage 2: PCM5102A DAC ----------------
for pin in ("AVDD","CPVDD","DVDD"): u3[pin] += p3v3
for pin in ("AGND","DGND","CPGND"): u3[pin] += gnd
for pin in ("AVDD","CPVDD","DVDD"):
c = C(value="100nF"); u3[pin] += c[1]; c[2] += gnd
cb = C(value="10uF", footprint="Capacitor_SMD:C_1206_3216Metric"); u3["AVDD"] += cb[1]; cb[2] += gnd
cfly = C(value="2.2uF"); u3["CAPP"] += cfly[1]; u3["CAPM"] += cfly[2]
cvneg = C(value="2.2uF"); u3["VNEG"] += cvneg[1]; cvneg[2] += gnd
cldoo = C(value="1uF"); u3["LDOO"] += cldoo[1]; cldoo[2] += gnd
u3["DEMP"] += gnd; u3["FLT"] += gnd; u3["FMT"] += gnd
u3["XSMT"] += dac_xsmt; rpu = R(value="10k"); dac_xsmt += rpu[1]; rpu[2] += p3v3
u3["SCK"] += mclk; u3["BCK"] += i2s_bck; u3["DIN"] += i2s_din; u3["LRCK"] += i2s_lrck
rload = R(value="2.2k"); u3["OUTR"] += rload[1]; rload[2] += gnd
# ---------------- Stage 2: reconstruction filter = OPA1612 section A ----------------
r1, r2 = R(value="1.5k"), R(value="1.5k"); ca = C(value="2.2nF"); cbq = C(value="1nF")
rcA = Net("RC_A")
u3["OUTL"] += r1[1]; r1[2] += rcA
r2[1] += rcA; r2[2] += u4["+INA"]
ca[1] += rcA; ca[2] += u4["OUTA"]
cbq[1] += u4["+INA"]; cbq[2] += gnd
u4["-INA"] += u4["OUTA"]; u4["OUTA"] += click_out
u4["V+"] += p15; u4["V-"] += n15; dcp(p15); dcp(n15)
# ---------------- Stage 3: summer = OPA1612 section B (shared chip) ----------------
ri_in, ri_clk, rf3 = R(value="10k"), R(value="10k"), R(value="10k")
stage1_out += ri_in[1]; ri_in[2] += u4["-INB"]
click_out += ri_clk[1]; ri_clk[2] += u4["-INB"]
rf3[1] += u4["-INB"]; rf3[2] += u4["OUTB"]
u4["+INB"] += gnd; u4["OUTB"] += mix_out
# ---------------- Stage 4: level trim + THAT1646 + build-out ----------------
RV = Part("Device","R_Potentiometer", dest=TEMPLATE,
footprint="Potentiometer_THT:Potentiometer_Bourns_3296W_Vertical")
rv1 = RV(value="10k", ref="RV1")
mix_out += rv1[1]; rv1[3] += gnd; rv1[2] += u5["IN"]
u5["SNS-"] += u5["OUT-"]; u5["SNS+"] += u5["OUT+"]; u5["GND"] += gnd
u5["V+"] += p15; u5["V-"] += n15; dcp(p15); dcp(n15)
rbo_h, rbo_c = R(value="47"), R(value="47")
u5["OUT-"] += rbo_h[1]; rbo_h[2] += aout_hot # phase-corrected: HOT<-OUT-
u5["OUT+"] += rbo_c[1]; rbo_c[2] += aout_cold # COLD<-OUT+
# ---------------- Stage 4: mute relay K2 + ground-lift relay K3 ----------------
k2["P1_COM"] += aout_hot; k2["P1_NC"] += gnd # de-energized = muted (short to gnd)
k2["P2_COM"] += aout_cold; k2["P2_NC"] += gnd
k2["COIL_A"] += p5; k2["COIL_B"] += k2_drv
k3["P1_COM"] += gnd; k3["P1_NC"] += chassis # de-energized = bonded
rlift = R(value="100"); clift = C(value="10nF")
rlift[1] += gnd; rlift[2] += chassis; clift[1] += gnd; clift[2] += chassis
k3["COIL_A"] += p5; k3["COIL_B"] += k3_drv
# ---------------- shared relay driver U6 (ULN2003): 3 channels ----------------
u6["1B"] += sel_linst; u6["1C"] += k1_drv
u6["2B"] += mute_en; u6["2C"] += k2_drv
u6["3B"] += gndlift_en; u6["3C"] += k3_drv
u6["GND"] += gnd; u6["COM"] += p5 # flyback common to coil supply
ERC()
out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "audio_chain.net"))
generate_netlist(file_=out)
print("Integrated audio netlist ->", out)