diff --git a/hardware/eda/circuits/stage1b_select.py b/hardware/eda/circuits/stage1b_select.py index 8345149..1ecaf80 100644 --- a/hardware/eda/circuits/stage1b_select.py +++ b/hardware/eda/circuits/stage1b_select.py @@ -17,10 +17,12 @@ PINOUTS VERIFIED FROM DATASHEETS * OPA1641 (TI SBOS484D): 1=NC 2=-IN 3=+IN 4=V- 5=NC 6=OUT 7=V+ 8=NC. Supply +/-18V max. * TQ2SA (Panasonic TQ-SMD): 2 Form C. pole1 COM=3 (throws 2,4); pole2 COM=8 (throws 7,9) -- confirmed from the contact-resistance terminal pairs (2-3,3-4,7-8,8-9). - CROSS-CHECK BEFORE LAYOUT (standard single-side-stable diagram, not over-claimed): - * which throw is NC (closed when de-energized) -> we use 4 & 7 for LINE; swap if the - exact connection diagram differs (so LINE stays the default). - * coil terminal pair (using 1 & 10); coil polarity irrelevant (no internal diode). + RESOLVED from the Panasonic TQ-SMD connection diagram (single-side-stable, top view) + + the contact-resistance terminal pairs (2-3,3-4,7-8,8-9): + * coil = pins 1 & 10 (polarity irrelevant -- non-latching single coil, no diode). + * pole1 COM=3, NC=4, NO=2 ; pole2 COM=8, NC=7, NO=9 ; pins 5,6 unused. + NC/NO sense is also firmware-correctable: the relay is GPIO-driven, so if a physical + unit reads opposite, invert SEL_LINST in firmware (no board change). Relay coil is driven by the SHARED ULN2003 (U14) -- represented here as net K1_DRV (sinks the coil) + SEL_LINST (the RP2350 GPIO). The ULN2003 is instantiated once in the power/control block, not per-relay. diff --git a/hardware/eda/circuits/stage2_dac.py b/hardware/eda/circuits/stage2_dac.py new file mode 100644 index 0000000..4668c28 --- /dev/null +++ b/hardware/eda/circuits/stage2_dac.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +"""PM_K-1 audio chain - Stage 2: PCM5102A DAC + reconstruction filter (SKiDL). + +Run INSIDE the EDA container: + cd hardware/eda && ./run.sh python3 ../eda/circuits/stage2_dac.py +Outputs ERC + a KiCad netlist at hardware/kicad/stage2_dac.net. + +WHAT THIS STAGE DOES + The RP2350 streams I2S audio (the click, and later sampled sounds) to a PCM5102A + DAC clocked by the dedicated low-jitter oscillator (SCK=MCLK). The PCM5102A puts + out 2.1 Vrms GROUND-CENTERED analog (no DC-block cap needed). A 2nd-order + Sallen-Key low-pass (~75 kHz, one OPA1612 section) removes the delta-sigma HF + residue before the click reaches the summing/output stages -> CLICK_OUT. + +PINOUTS VERIFIED FROM DATASHEETS + * PCM5102A (TI SLAS859C, PW/TSSOP-20): pin map below matches the datasheet table. + Output 2.1Vrms GND-centered; 3.3V supplies; charge pump (CAPP/CAPM flying cap + + VNEG) makes the negative rail for the ground-centered swing. + * OPA1612 (dual): standard JEDEC dual-opamp SOIC-8 pinout + (1=OUTA 2=-INA 3=+INA 4=V- 5=+INB 6=-INB 7=OUTB 8=V+), confirmed against the + OPA164x dual on its datasheet (identical layout). Confirm OPA1612 sheet at layout. +""" +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") + +# ---- rails / signals ---- +p3v3, gnd, p15, n15 = Net("+3V3"), Net("GND"), Net("+15V"), Net("-15V") +for n in (p3v3, gnd, p15, n15): + n.drive = POWER +mclk = Net("MCLK") # low-jitter audio clock -> SCK +i2s_bck, i2s_din, i2s_lrck = Net("I2S_BCK"), Net("I2S_DIN"), Net("I2S_LRCK") # from RP2350 +dac_xsmt = Net("DAC_XSMT") # GPIO soft-mute (high=unmute), pulled up +click_out = Net("CLICK_OUT")# filtered click -> Stage 3 summing + +# ---- PCM5102A (pinout verified, TI SLAS859C) ---- +DAC = Part(name="PCM5102A", tool=SKIDL, dest=TEMPLATE, ref_prefix="U", + footprint="Package_SO:TSSOP-20_4.4x6.5mm_P0.65mm", + pins=[ + Pin(num=1, name="CPVDD", func=Pin.types.PWRIN), + Pin(num=2, name="CAPP", func=Pin.types.PASSIVE), + Pin(num=3, name="CPGND", func=Pin.types.PWRIN), + Pin(num=4, name="CAPM", func=Pin.types.PASSIVE), + Pin(num=5, name="VNEG", func=Pin.types.PASSIVE), + Pin(num=6, name="OUTL", func=Pin.types.OUTPUT), + Pin(num=7, name="OUTR", func=Pin.types.OUTPUT), + Pin(num=8, name="AVDD", func=Pin.types.PWRIN), + Pin(num=9, name="AGND", func=Pin.types.PWRIN), + Pin(num=10, name="DEMP", func=Pin.types.INPUT), + Pin(num=11, name="FLT", func=Pin.types.INPUT), + Pin(num=12, name="SCK", func=Pin.types.INPUT), + Pin(num=13, name="BCK", func=Pin.types.INPUT), + Pin(num=14, name="DIN", func=Pin.types.INPUT), + Pin(num=15, name="LRCK", func=Pin.types.INPUT), + Pin(num=16, name="FMT", func=Pin.types.INPUT), + Pin(num=17, name="XSMT", func=Pin.types.INPUT), + Pin(num=18, name="LDOO", func=Pin.types.PWROUT), + Pin(num=19, name="DGND", func=Pin.types.PWRIN), + Pin(num=20, name="DVDD", func=Pin.types.PWRIN), + ]) +dac = DAC(ref="U3") + +# ---- OPA1612 dual op-amp (standard dual pinout) ---- +OPA2 = Part(name="OPA1612", tool=SKIDL, dest=TEMPLATE, ref_prefix="U", + footprint="Package_SO:SOIC-8_3.9x4.9mm_P1.27mm", + pins=[ + Pin(num=1, name="OUTA", func=Pin.types.OUTPUT), + Pin(num=2, name="-INA", func=Pin.types.INPUT), + Pin(num=3, name="+INA", func=Pin.types.INPUT), + Pin(num=4, name="V-", func=Pin.types.PWRIN), + Pin(num=5, name="+INB", func=Pin.types.INPUT), + Pin(num=6, name="-INB", func=Pin.types.INPUT), + Pin(num=7, name="OUTB", func=Pin.types.OUTPUT), + Pin(num=8, name="V+", func=Pin.types.PWRIN), + ]) +flt = OPA2(ref="U4") + +# ---- PCM5102A supplies + grounds (all grounds tied; <0.2V per datasheet) ---- +dac["AVDD"] += p3v3 +dac["CPVDD"] += p3v3 +dac["DVDD"] += p3v3 +dac["AGND"] += gnd +dac["DGND"] += gnd +dac["CPGND"] += gnd +for pin in ("AVDD", "CPVDD", "DVDD"): + c = C(value="100nF"); dac[pin] += c[1]; c[2] += gnd +cbulk = C(value="10uF", footprint="Capacitor_SMD:C_1206_3216Metric") +dac["AVDD"] += cbulk[1]; cbulk[2] += gnd + +# charge pump: flying cap across CAPP/CAPM, VNEG + LDOO decoupling +cfly = C(value="2.2uF", footprint="Capacitor_SMD:C_0805_2012Metric") +dac["CAPP"] += cfly[1]; dac["CAPM"] += cfly[2] +cvneg = C(value="2.2uF"); dac["VNEG"] += cvneg[1]; cvneg[2] += gnd +cldoo = C(value="1uF"); dac["LDOO"] += cldoo[1]; cldoo[2] += gnd + +# control-pin tie-offs: no de-emphasis, normal latency, I2S format +dac["DEMP"] += gnd +dac["FLT"] += gnd +dac["FMT"] += gnd +# soft-mute: GPIO with 10k pull-up to keep un-muted by default +dac["XSMT"] += dac_xsmt +rpull = R(value="10k"); dac_xsmt += rpull[1]; rpull[2] += p3v3 + +# clocks + data +dac["SCK"] += mclk +dac["BCK"] += i2s_bck +dac["DIN"] += i2s_din +dac["LRCK"] += i2s_lrck + +# OUTR unused (mono click on L): give it the datasheet-recommended load +rload = R(value="2.2k"); dac["OUTR"] += rload[1]; rload[2] += gnd + +# ---- Sallen-Key reconstruction LPF on OUTL (R=1.5k, Ca=2.2n, Cb=1n ~75kHz) ---- +r1, r2 = R(value="1.5k"), R(value="1.5k") +ca = C(value="2.2nF"); cb = C(value="1nF") +nodeA = Net("RC_A") +dac["OUTL"] += r1[1]; r1[2] += nodeA +r2[1] += nodeA; r2[2] += flt["+INA"] +ca[1] += nodeA; ca[2] += flt["OUTA"] # feedback cap +cb[1] += flt["+INA"]; cb[2] += gnd +flt["-INA"] += flt["OUTA"] # unity-gain follower +flt["OUTA"] += click_out + +# ---- OPA1612 supplies + decoupling; park unused section B ---- +flt["V+"] += p15; flt["V-"] += n15 +for rail in (p15, n15): + c = C(value="100nF"); rail += c[1]; c[2] += gnd +flt["+INB"] += gnd; flt["OUTB"] += flt["-INB"] # B = grounded follower (parked) + +ERC() +out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "stage2_dac.net")) +generate_netlist(file_=out) +print("Stage 2 netlist ->", out) diff --git a/hardware/eda/sim/stage2_recon.cir b/hardware/eda/sim/stage2_recon.cir new file mode 100644 index 0000000..1f206dc --- /dev/null +++ b/hardware/eda/sim/stage2_recon.cir @@ -0,0 +1,35 @@ +* PM_K-1 Stage 2 : DAC reconstruction filter (2nd-order Sallen-Key low-pass) +* +* The PCM5102A is a delta-sigma DAC: its analog output carries the audio plus +* shaped high-frequency quantization noise WAY above the audio band. A gentle +* low-pass cleans that residue before it reaches the pro output stage. We want it +* dead flat to 20 kHz and rolling off above ~80 kHz (Butterworth, Q~0.7). +* +* Unity-gain Sallen-Key, equal R. Target fc ~75 kHz with R=1.5k, C1=2.2n, C2=1n. +* Run: ngspice -b ../eda/sim/stage2_recon.cir + +.title Stage2 DAC reconstruction filter + +.param R=1.5k +.param Ca=2.2n +.param Cb=1n + +Vin in 0 AC 1 +R1 in a {R} +R2 a vp {R} +Ca a out {Ca} ; feedback cap +Cb vp 0 {Cb} ; cap to ground +Eop out 0 vp out 1e6 ; unity-gain follower op-amp (-in tied to out) + +.ac dec 100 100 2meg +.control +run +meas ac g_1k find vdb(out) at=1k +meas ac g_20k find vdb(out) at=20k +meas ac f_3db when vdb(out)=-3 +echo +echo " passband @1kHz : $&g_1k dB" +echo " @20kHz (audio edge) : $&g_20k dB (want ~0 dB = flat)" +echo " -3dB corner : $&f_3db Hz (well above audio; attenuates DAC HF residue)" +.endc +.end diff --git a/hardware/kicad/stage2_dac.erc b/hardware/kicad/stage2_dac.erc new file mode 100644 index 0000000..bc2ca48 --- /dev/null +++ b/hardware/kicad/stage2_dac.erc @@ -0,0 +1,15 @@ +ERC WARNING: Only one pin (INPUT pin 15/LRCK of PCM5102A/U3) attached to net I2S_LRCK. +ERC WARNING: No drivers for net I2S_LRCK. +ERC WARNING: Insufficient drive current on net I2S_LRCK for pin INPUT pin 15/LRCK of PCM5102A/U3. +ERC WARNING: Only one pin (INPUT pin 13/BCK of PCM5102A/U3) attached to net I2S_BCK. +ERC WARNING: No drivers for net I2S_BCK. +ERC WARNING: Insufficient drive current on net I2S_BCK for pin INPUT pin 13/BCK of PCM5102A/U3. +ERC WARNING: Only one pin (INPUT pin 12/SCK of PCM5102A/U3) attached to net MCLK. +ERC WARNING: No drivers for net MCLK. +ERC WARNING: Insufficient drive current on net MCLK for pin INPUT pin 12/SCK of PCM5102A/U3. +ERC WARNING: Only one pin (INPUT pin 14/DIN of PCM5102A/U3) attached to net I2S_DIN. +ERC WARNING: No drivers for net I2S_DIN. +ERC WARNING: Insufficient drive current on net I2S_DIN for pin INPUT pin 14/DIN of PCM5102A/U3. +ERC INFO: 12 warnings found while running ERC. +ERC INFO: 0 errors found while running ERC. + diff --git a/hardware/kicad/stage2_dac.log b/hardware/kicad/stage2_dac.log new file mode 100644 index 0000000..be924ec --- /dev/null +++ b/hardware/kicad/stage2_dac.log @@ -0,0 +1,45 @@ +WARNING: KICAD8_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/:995=>/work/hardware/kicad/:488] +WARNING: KICAD6_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/:995=>/work/hardware/kicad/:488] +WARNING: KICAD7_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/:995=>/work/hardware/kicad/:488] +WARNING: fp-lib-table file was not found. Component footprints are not available. +WARNING: fp-lib-table file was not found. Component footprints are not available. +WARNING: fp-lib-table file was not found. Component footprints are not available. +WARNING: fp-lib-table file was not found. Component footprints are not available. +WARNING: Missing tag on PCM5102A instantiated at /work/hardware/eda/circuits/stage2_dac.py:65. +WARNING: Random tag SnNljvRqpw generated for PCM5102A. +WARNING: Missing tag on OPA1612 instantiated at /work/hardware/eda/circuits/stage2_dac.py:80. +WARNING: Random tag xlt7MeMuUW generated for OPA1612. +WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage2_dac.py:90. +WARNING: Random tag APxc1ZswyC generated for C. +WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage2_dac.py:90. +WARNING: Random tag 2Lpho8vvNO generated for C. +WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage2_dac.py:90. +WARNING: Random tag ckOnqrtEN_ generated for C. +WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage2_dac.py:91. +WARNING: Random tag u0XfWM3NJZ generated for C. +WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage2_dac.py:95. +WARNING: Random tag 0nDWAElKnx generated for C. +WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage2_dac.py:97. +WARNING: Random tag enBI6FilVb generated for C. +WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage2_dac.py:98. +WARNING: Random tag BsHVUTUgsl generated for C. +WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage2_dac.py:106. +WARNING: Random tag MwfOtBOy0H generated for R. +WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage2_dac.py:115. +WARNING: Random tag T191zb3J0X generated for R. +WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage2_dac.py:118. +WARNING: Random tag PbFpsp3Kl4 generated for R. +WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage2_dac.py:118. +WARNING: Random tag uQ27wvkIQL generated for R. +WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage2_dac.py:119. +WARNING: Random tag xSn6HarfSW generated for C. +WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage2_dac.py:119. +WARNING: Random tag mU_gTQB1ji generated for C. +WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage2_dac.py:131. +WARNING: Random tag u20v3iP1Ti generated for C. +WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage2_dac.py:131. +WARNING: Random tag ZBz2Y2o1hX generated for C. +WARNING: Missing tag on instantiated at /work/hardware/kicad/:488. +INFO: 39 warnings found while generating netlist. +INFO: 0 errors found while generating netlist. + diff --git a/hardware/kicad/stage2_dac_sklib.py b/hardware/kicad/stage2_dac_sklib.py new file mode 100644 index 0000000..e0af343 --- /dev/null +++ b/hardware/kicad/stage2_dac_sklib.py @@ -0,0 +1,44 @@ +from collections import defaultdict +from skidl import Pin, Part, Alias, SchLib, SKIDL, TEMPLATE + +from skidl.pin import pin_types + +SKIDL_lib_version = '0.0.1' + +stage2_dac = SchLib(tool=SKIDL).add_parts(*[ + Part(**{ 'name':'PCM5102A', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'PCM5102A'}), 'ref_prefix':'U', 'fplist':None, 'footprint':'Package_SO:TSSOP-20_4.4x6.5mm_P0.65mm', 'keywords':None, 'description':'', 'datasheet':None, 'pins':[ + Pin(num='1',name='CPVDD',func=pin_types.PWRIN), + Pin(num='2',name='CAPP',func=pin_types.PASSIVE), + Pin(num='3',name='CPGND',func=pin_types.PWRIN), + Pin(num='4',name='CAPM',func=pin_types.PASSIVE), + Pin(num='5',name='VNEG',func=pin_types.PASSIVE), + Pin(num='6',name='OUTL',func=pin_types.OUTPUT), + Pin(num='7',name='OUTR',func=pin_types.OUTPUT), + Pin(num='8',name='AVDD',func=pin_types.PWRIN), + Pin(num='9',name='AGND',func=pin_types.PWRIN), + Pin(num='10',name='DEMP',func=pin_types.INPUT), + Pin(num='11',name='FLT',func=pin_types.INPUT), + Pin(num='12',name='SCK',func=pin_types.INPUT), + Pin(num='13',name='BCK',func=pin_types.INPUT), + Pin(num='14',name='DIN',func=pin_types.INPUT), + Pin(num='15',name='LRCK',func=pin_types.INPUT), + Pin(num='16',name='FMT',func=pin_types.INPUT), + Pin(num='17',name='XSMT',func=pin_types.INPUT), + Pin(num='18',name='LDOO',func=pin_types.PWROUT), + Pin(num='19',name='DGND',func=pin_types.PWRIN), + Pin(num='20',name='DVDD',func=pin_types.PWRIN)] }), + Part(**{ 'name':'OPA1612', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'OPA1612'}), 'ref_prefix':'U', 'fplist':None, 'footprint':'Package_SO:SOIC-8_3.9x4.9mm_P1.27mm', 'keywords':None, 'description':'', 'datasheet':None, 'pins':[ + Pin(num='1',name='OUTA',func=pin_types.OUTPUT), + Pin(num='2',name='-INA',func=pin_types.INPUT), + Pin(num='3',name='+INA',func=pin_types.INPUT), + Pin(num='4',name='V-',func=pin_types.PWRIN), + Pin(num='5',name='+INB',func=pin_types.INPUT), + Pin(num='6',name='-INB',func=pin_types.INPUT), + Pin(num='7',name='OUTB',func=pin_types.OUTPUT), + Pin(num='8',name='V+',func=pin_types.PWRIN)] }), + Part(**{ 'name':'C', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'C'}), 'ref_prefix':'C', 'fplist':[''], 'footprint':'Capacitor_SMD:C_0805_2012Metric', 'keywords':'cap capacitor', 'description':'Unpolarized capacitor', 'datasheet':'~', 'pins':[ + Pin(num='1',name='~',func=pin_types.PASSIVE,unit=1), + Pin(num='2',name='~',func=pin_types.PASSIVE,unit=1)], 'unit_defs':[] }), + Part(**{ 'name':'R', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'R'}), 'ref_prefix':'R', 'fplist':[''], 'footprint':'Resistor_SMD:R_0805_2012Metric', 'keywords':'R res resistor', 'description':'Resistor', 'datasheet':'~', 'pins':[ + Pin(num='1',name='~',func=pin_types.PASSIVE,unit=1), + Pin(num='2',name='~',func=pin_types.PASSIVE,unit=1)], 'unit_defs':[] })]) \ No newline at end of file