PM_K-1 hardware: Stage 4 -- balanced output driver (completes the audio chain)
THAT1646 balanced driver (pinout verified, THAT doc 600078 rev07) closes the chain: MIX_OUT -> 25-turn level-cal trim -> THAT1646 (+6dB, sense tied local) -> 47ohm build-out -> fail-safe mute relay K2 -> balanced AOUT_HOT/COLD on the interconnect; ground-lift relay K3 (de-energized=bonded, soft-lift 100R||10nF) -> CHASSIS. - Phase: Stage 3 inversion corrected via HOT<-OUT-, COLD<-OUT+. - Level cal trim ahead of the driver (its +6dB gain is fixed). - K2 fail-safe: de-energized shorts both legs to GND after the build-out (driver current-limited). K3 ground-lift in series with a face panel switch. - stage4_driver.cir: differential flat +4.76dB (1k=20k), legs antiphase (0 vs pi rad), build-out+cable rolloff above audio. ERC 0 errors; netlist 0 errors. AUDIO CHAIN COMPLETE: stages 1, 1b, 2, 3, 4 all captured + simulated + ERC-clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6b6a58fa56
commit
5a75dbbbdc
5 changed files with 232 additions and 0 deletions
113
hardware/eda/circuits/stage4_driver.py
Normal file
113
hardware/eda/circuits/stage4_driver.py
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""PM_K-1 audio chain - Stage 4: balanced output driver (THAT1646) + mute + ground-lift.
|
||||||
|
|
||||||
|
Run INSIDE the EDA container:
|
||||||
|
cd hardware/eda && ./run.sh python3 ../eda/circuits/stage4_driver.py
|
||||||
|
Outputs ERC + a KiCad netlist at hardware/kicad/stage4_driver.net. This closes the
|
||||||
|
audio chain: MIX_OUT -> level trim -> THAT1646 -> build-out -> mute relay -> balanced
|
||||||
|
output on the analog interconnect; plus the ground-lift relay.
|
||||||
|
|
||||||
|
PINOUT VERIFIED (THAT doc 600078 rev 07, SO-8):
|
||||||
|
THAT1646: 1=Out- 2=Sns- 3=Gnd 4=In 5=Vee(V-) 6=Vcc(V+) 7=Sns+ 8=Out+.
|
||||||
|
Fixed +6 dB differential gain; sense pins tie to the output pins (local sense) with
|
||||||
|
the 47 ohm build-out OUTSIDE the loop for cable stability. Supply 4-18V (we use +/-15).
|
||||||
|
TQ2SA relay pinout per stage1b (coil 1/10; pole1 COM=3 NC=4 NO=2; pole2 COM=8 NC=7 NO=9).
|
||||||
|
|
||||||
|
KEY CHOICES
|
||||||
|
* Level cal: the THAT1646 gain is FIXED (+6 dB), so the calibration trim is a 25-turn
|
||||||
|
pot ahead of it (DAC full-scale -> +4 dBu, accounting for summer + the +6 dB).
|
||||||
|
* Phase: Stage 3's summer inverted the signal; corrected here by taking AOUT_HOT from
|
||||||
|
Out- and AOUT_COLD from Out+ (absolute polarity preserved).
|
||||||
|
* Mute (K2): fail-safe -- de-energized shorts both legs to GND (after the 47 ohm
|
||||||
|
build-out, so the driver is current-limited). Energized = un-muted. Driven by the
|
||||||
|
hardware rail-supervisor via K2_DRV (in the power block); MCU can also assert.
|
||||||
|
* Ground-lift (K3): de-energized = bonded (GND<->CHASSIS); energize to LIFT. Soft-lift
|
||||||
|
100 ohm || 10 nF across the contact keeps an RF/safety path. A face panel switch sits
|
||||||
|
in series downstream (CHASSIS pin on the interconnect) -> both-must-close to bond.
|
||||||
|
"""
|
||||||
|
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")
|
||||||
|
|
||||||
|
p15, n15, gnd, p5 = Net("+15V"), Net("-15V"), Net("GND"), Net("+5V")
|
||||||
|
for n in (p15, n15, gnd, p5):
|
||||||
|
n.drive = POWER
|
||||||
|
mix_out = Net("MIX_OUT") # from Stage 3 summer (inverted)
|
||||||
|
aout_hot, aout_cold = Net("AOUT_HOT"), Net("AOUT_COLD") # -> analog interconnect
|
||||||
|
chassis = Net("CHASSIS") # shield -> interconnect (face panel switch + chassis)
|
||||||
|
k2_drv, k3_drv = Net("K2_DRV"), Net("K3_DRV") # relay coil drives
|
||||||
|
|
||||||
|
# ---- THAT1646 balanced line driver (pinout verified) ----
|
||||||
|
DRV = Part(name="THAT1646", tool=SKIDL, dest=TEMPLATE, ref_prefix="U",
|
||||||
|
footprint="Package_SO:SOIC-8_3.9x4.9mm_P1.27mm",
|
||||||
|
pins=[
|
||||||
|
Pin(num=1, name="OUT-", func=Pin.types.OUTPUT),
|
||||||
|
Pin(num=2, name="SNS-", func=Pin.types.INPUT),
|
||||||
|
Pin(num=3, name="GND", func=Pin.types.PWRIN),
|
||||||
|
Pin(num=4, name="IN", func=Pin.types.INPUT),
|
||||||
|
Pin(num=5, name="V-", func=Pin.types.PWRIN),
|
||||||
|
Pin(num=6, name="V+", func=Pin.types.PWRIN),
|
||||||
|
Pin(num=7, name="SNS+", func=Pin.types.INPUT),
|
||||||
|
Pin(num=8, name="OUT+", func=Pin.types.OUTPUT),
|
||||||
|
])
|
||||||
|
drv = DRV(ref="U6")
|
||||||
|
|
||||||
|
# ---- TQ2SA relay (verified pinout, reused def) ----
|
||||||
|
def tq2sa(ref):
|
||||||
|
p = Part(name="TQ2SA-5V", tool=SKIDL, dest=TEMPLATE, ref_prefix="K",
|
||||||
|
footprint="Relay_SMD:Relay_DPDT_Panasonic_TQ2-SA",
|
||||||
|
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),
|
||||||
|
])
|
||||||
|
return p(ref=ref)
|
||||||
|
k2 = tq2sa("K2") # mute
|
||||||
|
k3 = tq2sa("K3") # ground-lift
|
||||||
|
|
||||||
|
# ---- level-cal trim (25-turn pot): MIX_OUT (top) / GND (bottom) / wiper -> THAT1646 IN ----
|
||||||
|
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] += drv["IN"]
|
||||||
|
|
||||||
|
# ---- THAT1646: sense ties (local) + supplies ----
|
||||||
|
drv["SNS-"] += drv["OUT-"]
|
||||||
|
drv["SNS+"] += drv["OUT+"]
|
||||||
|
drv["GND"] += gnd
|
||||||
|
drv["V+"] += p15; drv["V-"] += n15
|
||||||
|
for rail in (p15, n15):
|
||||||
|
c = C(value="100nF"); rail += c[1]; c[2] += gnd
|
||||||
|
|
||||||
|
# ---- 47 ohm build-out (phase-corrected: HOT<-OUT-, COLD<-OUT+) ----
|
||||||
|
rbo_h, rbo_c = R(value="47"), R(value="47")
|
||||||
|
drv["OUT-"] += rbo_h[1]; rbo_h[2] += aout_hot
|
||||||
|
drv["OUT+"] += rbo_c[1]; rbo_c[2] += aout_cold
|
||||||
|
|
||||||
|
# ---- mute relay K2: de-energized shorts both legs to GND (fail-safe) ----
|
||||||
|
k2["P1_COM"] += aout_hot; k2["P1_NC"] += gnd # de-energized: hot -> GND (muted)
|
||||||
|
k2["P2_COM"] += aout_cold; k2["P2_NC"] += gnd # de-energized: cold -> GND
|
||||||
|
k2["COIL_A"] += p5; k2["COIL_B"] += k2_drv # energize (rails OK) = un-mute
|
||||||
|
|
||||||
|
# ---- ground-lift K3: de-energized bonds GND<->CHASSIS; energize to lift ----
|
||||||
|
k3["P1_COM"] += gnd; k3["P1_NC"] += chassis # de-energized: bonded
|
||||||
|
rlift = R(value="100"); clift = C(value="10nF")
|
||||||
|
gnd += rlift[1]; rlift[2] += chassis # soft-lift: 100 ohm || 10 nF
|
||||||
|
clift[1] += gnd; clift[2] += chassis
|
||||||
|
k3["COIL_A"] += p5; k3["COIL_B"] += k3_drv
|
||||||
|
|
||||||
|
ERC()
|
||||||
|
out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "stage4_driver.net"))
|
||||||
|
generate_netlist(file_=out)
|
||||||
|
print("Stage 4 netlist ->", out)
|
||||||
38
hardware/eda/sim/stage4_driver.cir
Normal file
38
hardware/eda/sim/stage4_driver.cir
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
* PM_K-1 Stage 4 : balanced output driver -- antiphase + build-out into a cable
|
||||||
|
*
|
||||||
|
* The THAT1646 turns the single-ended mix into a balanced (differential) output:
|
||||||
|
* Out+ and Out- swing equal-and-opposite, so noise picked up equally on both wires
|
||||||
|
* cancels at the far end. It has a fixed +6 dB gain (differential = 2x input).
|
||||||
|
* We model it as two ideal sources (Out+ = +Vin, Out- = -Vin) and add the 47 ohm
|
||||||
|
* per-leg build-out resistors driving a 600 ohm load plus cable capacitance.
|
||||||
|
*
|
||||||
|
* Shows: audio band is dead flat, and the build-out + cable rolloff sits far above
|
||||||
|
* audio -- i.e. the build-out tames long/capacitive cables without dulling the sound.
|
||||||
|
* Run: ngspice -b ../eda/sim/stage4_driver.cir
|
||||||
|
|
||||||
|
.title Stage4 balanced driver
|
||||||
|
|
||||||
|
Vin in 0 AC 1
|
||||||
|
Eop op 0 in 0 1 ; Out+ = +Vin
|
||||||
|
Eon on 0 0 in 1 ; Out- = -Vin -> differential (op-on) = 2*Vin = +6 dB
|
||||||
|
Rbp op hot 47 ; build-out, hot leg
|
||||||
|
Rbn on cold 47 ; build-out, cold leg
|
||||||
|
Rl hot cold 600 ; receiver / load
|
||||||
|
Cch hot 0 1n ; ~cable capacitance per leg
|
||||||
|
Ccc cold 0 1n
|
||||||
|
|
||||||
|
.ac dec 20 10 10meg
|
||||||
|
.control
|
||||||
|
run
|
||||||
|
let vddb = db(v(hot) - v(cold)) ; differential magnitude in dB
|
||||||
|
meas ac d_1k find vddb at=1k
|
||||||
|
meas ac d_20k find vddb at=20k
|
||||||
|
meas ac d_1meg find vddb at=1meg
|
||||||
|
meas ac ph_hot find vp(hot) at=1k
|
||||||
|
meas ac ph_cold find vp(cold) at=1k
|
||||||
|
echo
|
||||||
|
echo " differential @1kHz : $&d_1k dB @20kHz : $&d_20k dB (flat across audio)"
|
||||||
|
echo " hot phase: $&ph_hot rad ; cold phase: $&ph_cold rad (~pi rad = 180 deg apart = balanced/antiphase)"
|
||||||
|
echo " differential @1MHz : $&d_1meg dB (build-out+cable rolloff stays above audio)"
|
||||||
|
.endc
|
||||||
|
.end
|
||||||
12
hardware/kicad/stage4_driver.erc
Normal file
12
hardware/kicad/stage4_driver.erc
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
ERC WARNING: Only one pin (PASSIVE pin 10/COIL_B of TQ2SA-5V/K2) attached to net K2_DRV.
|
||||||
|
ERC WARNING: Only one pin (PASSIVE pin 1/1 of R_Potentiometer/RV1) attached to net MIX_OUT.
|
||||||
|
ERC WARNING: Only one pin (PASSIVE pin 10/COIL_B of TQ2SA-5V/K3) attached to net K3_DRV.
|
||||||
|
ERC WARNING: Unconnected pin: PASSIVE pin 2/P1_NO of TQ2SA-5V/K2.
|
||||||
|
ERC WARNING: Unconnected pin: PASSIVE pin 9/P2_NO of TQ2SA-5V/K2.
|
||||||
|
ERC WARNING: Unconnected pin: PASSIVE pin 2/P1_NO of TQ2SA-5V/K3.
|
||||||
|
ERC WARNING: Unconnected pin: PASSIVE pin 8/P2_COM of TQ2SA-5V/K3.
|
||||||
|
ERC WARNING: Unconnected pin: PASSIVE pin 7/P2_NC of TQ2SA-5V/K3.
|
||||||
|
ERC WARNING: Unconnected pin: PASSIVE pin 9/P2_NO of TQ2SA-5V/K3.
|
||||||
|
ERC INFO: 9 warnings found while running ERC.
|
||||||
|
ERC INFO: 0 errors found while running ERC.
|
||||||
|
|
||||||
31
hardware/kicad/stage4_driver.log
Normal file
31
hardware/kicad/stage4_driver.log
Normal file
|
|
@ -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/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: KICAD6_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>:488]
|
||||||
|
WARNING: KICAD7_SYMBOL_DIR environment variable is missing, so the default KiCad symbol libraries won't be searched. @ [/work/hardware/kicad/<frozen importlib._bootstrap_external>:995=>/work/hardware/kicad/<frozen importlib._bootstrap>: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 THAT1646 instantiated at /work/hardware/eda/circuits/stage4_driver.py:57.
|
||||||
|
WARNING: Random tag jOycpJtysP generated for THAT1646.
|
||||||
|
WARNING: Missing tag on TQ2SA-5V instantiated at /work/hardware/eda/circuits/stage4_driver.py:75.
|
||||||
|
WARNING: Random tag 8YvHjEQvvL generated for TQ2SA-5V.
|
||||||
|
WARNING: Missing tag on TQ2SA-5V instantiated at /work/hardware/eda/circuits/stage4_driver.py:75.
|
||||||
|
WARNING: Random tag gbZbaJRgHN generated for TQ2SA-5V.
|
||||||
|
WARNING: Missing tag on R_Potentiometer instantiated at /work/hardware/eda/circuits/stage4_driver.py:82.
|
||||||
|
WARNING: Random tag TtWL0siwIj generated for R_Potentiometer.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage4_driver.py:91.
|
||||||
|
WARNING: Random tag Il2xsDxaas generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage4_driver.py:91.
|
||||||
|
WARNING: Random tag h4JnvQCBxp generated for C.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage4_driver.py:94.
|
||||||
|
WARNING: Random tag 49MKQ_ERoo generated for R.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage4_driver.py:94.
|
||||||
|
WARNING: Random tag L4esGcufU5 generated for R.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage4_driver.py:105.
|
||||||
|
WARNING: Random tag xtw8oitG50 generated for R.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage4_driver.py:105.
|
||||||
|
WARNING: Random tag 5KGv1Z40Bi generated for C.
|
||||||
|
WARNING: Missing tag on instantiated at /work/hardware/kicad/<frozen importlib._bootstrap>:488.
|
||||||
|
INFO: 25 warnings found while generating netlist.
|
||||||
|
INFO: 0 errors found while generating netlist.
|
||||||
|
|
||||||
38
hardware/kicad/stage4_driver_sklib.py
Normal file
38
hardware/kicad/stage4_driver_sklib.py
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
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'
|
||||||
|
|
||||||
|
stage4_driver = SchLib(tool=SKIDL).add_parts(*[
|
||||||
|
Part(**{ 'name':'THAT1646', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'THAT1646'}), '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='OUT-',func=pin_types.OUTPUT),
|
||||||
|
Pin(num='2',name='SNS-',func=pin_types.INPUT),
|
||||||
|
Pin(num='3',name='GND',func=pin_types.PWRIN),
|
||||||
|
Pin(num='4',name='IN',func=pin_types.INPUT),
|
||||||
|
Pin(num='5',name='V-',func=pin_types.PWRIN),
|
||||||
|
Pin(num='6',name='V+',func=pin_types.PWRIN),
|
||||||
|
Pin(num='7',name='SNS+',func=pin_types.INPUT),
|
||||||
|
Pin(num='8',name='OUT+',func=pin_types.OUTPUT)] }),
|
||||||
|
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':'R_Potentiometer', 'dest':TEMPLATE, 'tool':SKIDL, 'aliases':Alias({'R_Potentiometer'}), 'ref_prefix':'RV', 'fplist':[''], 'footprint':'Potentiometer_THT:Potentiometer_Bourns_3296W_Vertical', 'keywords':'resistor variable', 'description':'Potentiometer', 'datasheet':'~', 'pins':[
|
||||||
|
Pin(num='1',name='1',func=pin_types.PASSIVE,unit=1),
|
||||||
|
Pin(num='3',name='3',func=pin_types.PASSIVE,unit=1),
|
||||||
|
Pin(num='2',name='2',func=pin_types.PASSIVE,unit=1)], 'unit_defs':[] }),
|
||||||
|
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':[] })])
|
||||||
Loading…
Reference in a new issue