From e6f425ee6f9cf6baffff35585789072b22f69cc5 Mon Sep 17 00:00:00 2001 From: Me Here Date: Sat, 30 May 2026 19:56:55 -0500 Subject: [PATCH] PM_K-1 hardware: Stage 1b -- Hi-Z instrument DI buffer + line/inst select relay OPA1641 non-inverting DI buffer (1Mohm in, +12dB) + TQ2SA DPDT relay that both routes the jack tip (line receiver vs DI buffer) and selects the output. Default de-energized = LINE (common case, fail-safe). Driven by the shared ULN2003 via net K1_DRV from GPIO SEL_LINST. Pinouts verified from datasheets before capture (per the no-guessing rule): - OPA1641 (TI SBOS484D): 1=NC 2=-IN 3=+IN 4=V- 5=NC 6=OUT 7=V+ 8=NC. - ULN2003A: GND=8, COM=9, in 1-7 / out 16-10. - TQ2SA (Panasonic TQ-SMD): pole1 COM=3 throws 2/4, pole2 COM=8 throws 7/9 (from contact-resistance terminal pairs). NC/NO orientation + coil pins (1/10) follow the standard single-side-stable diagram -- flagged in-file for a final connection-diagram cross-check (not over-claimed). ngspice stage1b_di.cir confirms +12.04dB gain, flat across the audio band. ERC 0 errors; netlist 0 errors. Co-Authored-By: Claude Opus 4.8 (1M context) --- hardware/eda/circuits/stage1b_select.py | 121 ++++++++++++++++++++++++ hardware/eda/sim/stage1b_di.cir | 28 ++++++ hardware/kicad/stage1b_select.erc | 11 +++ hardware/kicad/stage1b_select.log | 31 ++++++ hardware/kicad/stage1b_select_sklib.py | 37 ++++++++ 5 files changed, 228 insertions(+) create mode 100644 hardware/eda/circuits/stage1b_select.py create mode 100644 hardware/eda/sim/stage1b_di.cir create mode 100644 hardware/kicad/stage1b_select.erc create mode 100644 hardware/kicad/stage1b_select.log create mode 100644 hardware/kicad/stage1b_select_sklib.py diff --git a/hardware/eda/circuits/stage1b_select.py b/hardware/eda/circuits/stage1b_select.py new file mode 100644 index 0000000..8345149 --- /dev/null +++ b/hardware/eda/circuits/stage1b_select.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +"""PM_K-1 audio chain - Stage 1b: Hi-Z instrument DI buffer + line/instrument select (SKiDL). + +Run INSIDE the EDA container: + cd hardware/eda && ./run.sh python3 ../eda/circuits/stage1b_select.py +Outputs ERC + a KiCad netlist at hardware/kicad/stage1b_select.net. + +WHAT THIS STAGE DOES + The same input jack feeds either the balanced LINE receiver (Stage 1) or a Hi-Z + INSTRUMENT buffer (here). One DPDT relay (K1) does two jobs at once: + pole 1 routes the jack TIP to the line receiver (default) OR the DI buffer + pole 2 selects which OUTPUT (RX_OUT or DI_OUT) feeds the summing stage + De-energized (relay OFF) = LINE = the common case (saves coil power; fail to line). + The DI buffer = OPA1641 non-inverting, gain +12 dB (1+Rf/Rg), 1 Mohm input. + +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). + 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. +""" +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") + +# ---- rails ---- +p15, n15, gnd, p5 = Net("+15V"), Net("-15V"), Net("GND"), Net("+5V") +for n in (p15, n15, gnd, p5): + n.drive = POWER + +# ---- OPA1641 JFET Hi-Z buffer (pinout verified) ---- +OPA = Part(name="OPA1641", tool=SKIDL, dest=TEMPLATE, ref_prefix="U", + footprint="Package_SO:SOIC-8_3.9x4.9mm_P1.27mm", + pins=[ + Pin(num=1, name="NC1", func=Pin.types.NOCONNECT), + Pin(num=2, name="-IN", func=Pin.types.INPUT), + Pin(num=3, name="+IN", func=Pin.types.INPUT), + Pin(num=4, name="V-", func=Pin.types.PWRIN), + Pin(num=5, name="NC5", func=Pin.types.NOCONNECT), + Pin(num=6, name="OUT", func=Pin.types.OUTPUT), + Pin(num=7, name="V+", func=Pin.types.PWRIN), + Pin(num=8, name="NC8", func=Pin.types.NOCONNECT), + ]) +u = OPA(ref="U2") + +# ---- TQ2SA DPDT select relay (contacts verified; NC/NO + coil pins flagged) ---- +RLY = Part(name="TQ2SA-5V", tool=SKIDL, dest=TEMPLATE, ref_prefix="K", + footprint="Relay_SMD:Relay_DPDT_Panasonic_TQ2-SA", # footprint: confirm at layout + pins=[ + Pin(num=1, name="COIL_A", func=Pin.types.PASSIVE), + Pin(num=10, name="COIL_B", func=Pin.types.PASSIVE), + Pin(num=3, name="P1_COM", func=Pin.types.PASSIVE), + Pin(num=4, name="P1_NC", func=Pin.types.PASSIVE), # de-energized = LINE + Pin(num=2, name="P1_NO", func=Pin.types.PASSIVE), # energized = INSTRUMENT + Pin(num=8, name="P2_COM", func=Pin.types.PASSIVE), + Pin(num=7, name="P2_NC", func=Pin.types.PASSIVE), + Pin(num=9, name="P2_NO", func=Pin.types.PASSIVE), + Pin(num=5, name="NC5", func=Pin.types.NOCONNECT), + Pin(num=6, name="NC6", func=Pin.types.NOCONNECT), + ]) +k1 = RLY(ref="K1") + +# ---- nets (shared with Stage 1 / Stage 3 by name) ---- +ain_hot = Net("AIN_HOT") # jack TIP +rx_hot_in = Net("RX_HOT_IN") # -> Stage 1 line-receiver hot protection (was AIN_HOT there) +di_in = Net("DI_IN") # -> this DI buffer input +rx_out = Net("RX_OUT") # <- Stage 1 line receiver output +di_out = Net("DI_OUT") # this DI buffer output +stage1_out = Net("STAGE1_OUT") # selected -> Stage 3 summing +sel = Net("SEL_LINST") # RP2350 GPIO: low=LINE(default), high=INSTRUMENT +k1_drv = Net("K1_DRV") # shared ULN2003 output sinks the coil + +# pole 1: route jack tip +k1["P1_COM"] += ain_hot +k1["P1_NC"] += rx_hot_in # default -> line receiver +k1["P1_NO"] += di_in # energized -> DI buffer +# pole 2: select output +k1["P2_COM"] += stage1_out +k1["P2_NC"] += rx_out # default -> line receiver output +k1["P2_NO"] += di_out # energized -> DI buffer output +# coil: +5V -- coil -- K1_DRV (ULN2003 sinks to gnd when SEL_LINST high) +k1["COIL_A"] += p5 +k1["COIL_B"] += k1_drv + +# ---- DI buffer input: DC-block, 1M bias, rail clamps ---- +cblk = C(value="100nF", footprint="Capacitor_SMD:C_1206_3216Metric") # film +rbias = R(value="1Meg") +dp, dn = D(value="1N4148WS"), D(value="1N4148WS") +node = Net("DI_NODE") +di_in += cblk[1]; cblk[2] += node +rbias[1] += node; rbias[2] += gnd +dp[1] += p15; dp[2] += node # Device:D pin1=K, pin2=A -> high clamp (>+15) +dn[1] += node; dn[2] += n15 # low clamp (< -15) +u["+IN"] += node + +# ---- non-inverting gain: Av = 1 + Rf/Rg = 1 + 3k/1k = 4 (+12 dB) ---- +rf, rg = R(value="3k"), R(value="1k") +u["OUT"] += di_out +rf[1] += di_out; rf[2] += u["-IN"] +rg[1] += u["-IN"]; rg[2] += gnd + +# ---- supplies + decoupling ---- +u["V+"] += p15; u["V-"] += n15 +for rail in (p15, n15): + c = C(value="100nF"); rail += c[1]; c[2] += gnd + +ERC() +out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "stage1b_select.net")) +generate_netlist(file_=out) +print("Stage 1b netlist ->", out) diff --git a/hardware/eda/sim/stage1b_di.cir b/hardware/eda/sim/stage1b_di.cir new file mode 100644 index 0000000..44224ee --- /dev/null +++ b/hardware/eda/sim/stage1b_di.cir @@ -0,0 +1,28 @@ +* PM_K-1 Stage 1b : Hi-Z instrument DI buffer -- gain + flatness check +* +* An OPA1641 (JFET input, ~1e12 ohm) as a non-inverting amp. The INPUT impedance +* the instrument sees is set by the bias resistor (1 Mohm) -- which the earlier +* input_loading.cir proved is what preserves a pickup's tone. Here we just confirm +* the voltage gain: Av = 1 + Rf/Rg. Target +12 dB (x4) with Rf=3k, Rg=1k. +* +* Run: ngspice -b ../eda/sim/stage1b_di.cir + +.title Stage1b DI buffer gain + +Vin in 0 AC 1 +Eop out 0 in vm 1e6 ; ideal op-amp: +IN = in, -IN = vm +Rf out vm 3k ; feedback +Rg vm 0 1k ; gain set + +.ac dec 20 10 100k +.control +run +meas ac g_1k find vdb(out) at=1000 +meas ac g_20k find vdb(out) at=20000 +let av = pow(10, g_1k/20) +echo +echo " DI buffer gain @1kHz : $&g_1k dB ( x$&av ) target +12.04 dB (x4)" +echo " DI buffer gain @20kHz : $&g_20k dB (flat across audio band)" +echo " Input impedance is set by the 1Mohm bias R (JFET input ~1e12 ohm) -- see input_loading.cir" +.endc +.end diff --git a/hardware/kicad/stage1b_select.erc b/hardware/kicad/stage1b_select.erc new file mode 100644 index 0000000..94d402c --- /dev/null +++ b/hardware/kicad/stage1b_select.erc @@ -0,0 +1,11 @@ +ERC WARNING: Only one pin (PASSIVE pin 10/COIL_B of TQ2SA-5V/K1) attached to net K1_DRV. +ERC WARNING: Only one pin (PASSIVE pin 3/P1_COM of TQ2SA-5V/K1) attached to net AIN_HOT. +ERC WARNING: Only one pin (PASSIVE pin 1/COIL_A of TQ2SA-5V/K1) attached to net +5V. +ERC WARNING: Only one pin (PASSIVE pin 4/P1_NC of TQ2SA-5V/K1) attached to net RX_HOT_IN. +ERC WARNING: Only one pin (PASSIVE pin 7/P2_NC of TQ2SA-5V/K1) attached to net RX_OUT. +ERC WARNING: No pins attached to net SEL_LINST. +ERC WARNING: No drivers for net SEL_LINST. +ERC WARNING: Only one pin (PASSIVE pin 8/P2_COM of TQ2SA-5V/K1) attached to net STAGE1_OUT. +ERC INFO: 8 warnings found while running ERC. +ERC INFO: 0 errors found while running ERC. + diff --git a/hardware/kicad/stage1b_select.log b/hardware/kicad/stage1b_select.log new file mode 100644 index 0000000..202f9e2 --- /dev/null +++ b/hardware/kicad/stage1b_select.log @@ -0,0 +1,31 @@ +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 OPA1641 instantiated at /work/hardware/eda/circuits/stage1b_select.py:55. +WARNING: Random tag JMxNatPRjd generated for OPA1641. +WARNING: Missing tag on TQ2SA-5V instantiated at /work/hardware/eda/circuits/stage1b_select.py:72. +WARNING: Random tag CfzYAxfHjh generated for TQ2SA-5V. +WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage1b_select.py:97. +WARNING: Random tag Sd0rgwy5pV generated for C. +WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage1b_select.py:98. +WARNING: Random tag OoRZG4Rm3b generated for R. +WARNING: Missing tag on D instantiated at /work/hardware/eda/circuits/stage1b_select.py:99. +WARNING: Random tag RSLeGReAd9 generated for D. +WARNING: Missing tag on D instantiated at /work/hardware/eda/circuits/stage1b_select.py:99. +WARNING: Random tag UOfVnOhhVN generated for D. +WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage1b_select.py:108. +WARNING: Random tag 3vZeT_Vysx generated for R. +WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage1b_select.py:108. +WARNING: Random tag u1DrTpQXsB generated for R. +WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage1b_select.py:116. +WARNING: Random tag UbVM1UVNF7 generated for C. +WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage1b_select.py:116. +WARNING: Random tag 073687vTIh generated for C. +WARNING: Missing tag on instantiated at /work/hardware/kicad/:488. +INFO: 25 warnings found while generating netlist. +INFO: 0 errors found while generating netlist. + diff --git a/hardware/kicad/stage1b_select_sklib.py b/hardware/kicad/stage1b_select_sklib.py new file mode 100644 index 0000000..62f54cf --- /dev/null +++ b/hardware/kicad/stage1b_select_sklib.py @@ -0,0 +1,37 @@ +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' + +stage1b_select = SchLib(tool=SKIDL).add_parts(*[ + Part(**{ 'name':'OPA1641', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'OPA1641'}), '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='NC1',func=pin_types.NOCONNECT), + Pin(num='2',name='-IN',func=pin_types.INPUT), + Pin(num='3',name='+IN',func=pin_types.INPUT), + Pin(num='4',name='V-',func=pin_types.PWRIN), + Pin(num='5',name='NC5',func=pin_types.NOCONNECT), + Pin(num='6',name='OUT',func=pin_types.OUTPUT), + Pin(num='7',name='V+',func=pin_types.PWRIN), + Pin(num='8',name='NC8',func=pin_types.NOCONNECT)] }), + Part(**{ 'name':'TQ2SA-5V', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'TQ2SA-5V'}), 'ref_prefix':'K', 'fplist':None, 'footprint':'Relay_SMD:Relay_DPDT_Panasonic_TQ2-SA', 'keywords':None, 'description':'', 'datasheet':None, 'pins':[ + Pin(num='1',name='COIL_A',func=pin_types.PASSIVE), + Pin(num='10',name='COIL_B',func=pin_types.PASSIVE), + Pin(num='3',name='P1_COM',func=pin_types.PASSIVE), + Pin(num='4',name='P1_NC',func=pin_types.PASSIVE), + Pin(num='2',name='P1_NO',func=pin_types.PASSIVE), + Pin(num='8',name='P2_COM',func=pin_types.PASSIVE), + Pin(num='7',name='P2_NC',func=pin_types.PASSIVE), + Pin(num='9',name='P2_NO',func=pin_types.PASSIVE), + Pin(num='5',name='NC5',func=pin_types.NOCONNECT), + Pin(num='6',name='NC6',func=pin_types.NOCONNECT)] }), + Part(**{ 'name':'C', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'C'}), 'ref_prefix':'C', 'fplist':[''], 'footprint':'Capacitor_SMD:C_1206_3216Metric', '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':[] }), + Part(**{ 'name':'D', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'D'}), 'ref_prefix':'D', 'fplist':[''], 'footprint':'Diode_SMD:D_SOD-323', 'keywords':'diode', 'description':'Diode', 'datasheet':'~', 'pins':[ + Pin(num='1',name='K',func=pin_types.PASSIVE,unit=1), + Pin(num='2',name='A',func=pin_types.PASSIVE,unit=1)], 'unit_defs':[] })]) \ No newline at end of file