PM_K-1 hardware: Stage 3 -- summing node (selected input + click)
Inverting summing amp (OPA1612 section) mixes STAGE1_OUT (line/instrument) and CLICK_OUT (filtered DAC) at unity into MIX_OUT. Each source enters its own 10k into the op-amp virtual ground, so they sum with no interaction. stage3_sum.cir confirms: each input alone = 0 dB, both together = +6.02 dB, and each input's gain is unchanged by the other (virtual-ground isolation). ERC/netlist 0 errors. Note: inverting summer flips phase -> corrected at the Stage 4 balanced driver via hot/cold assignment. At integration, this summer can use the parked 2nd half of the Stage 2 filter OPA1612 (U4) instead of a separate package. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
2f44be6f63
commit
6b6a58fa56
5 changed files with 160 additions and 0 deletions
69
hardware/eda/circuits/stage3_sum.py
Normal file
69
hardware/eda/circuits/stage3_sum.py
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""PM_K-1 audio chain - Stage 3: summing node (selected input + click) (SKiDL).
|
||||||
|
|
||||||
|
Run INSIDE the EDA container:
|
||||||
|
cd hardware/eda && ./run.sh python3 ../eda/circuits/stage3_sum.py
|
||||||
|
Outputs ERC + a KiCad netlist at hardware/kicad/stage3_sum.net.
|
||||||
|
|
||||||
|
WHAT THIS STAGE DOES
|
||||||
|
Inverting summing amp (OPA1612 section) mixes STAGE1_OUT (the line/instrument input,
|
||||||
|
unity) and CLICK_OUT (the filtered DAC click). Each source enters through its own
|
||||||
|
10k resistor into the op-amp's virtual-ground node, so the two never interact. The
|
||||||
|
"digital mix" lives upstream: click level is set by the DAC; the input passes at
|
||||||
|
unity. Output MIX_OUT -> Stage 4 balanced driver.
|
||||||
|
Vout = -(STAGE1_OUT + CLICK_OUT) (Rf = Ri = 10k)
|
||||||
|
|
||||||
|
POLARITY: an inverting summer flips phase. That is corrected for free at the Stage 4
|
||||||
|
balanced driver by assigning hot/cold accordingly (absolute polarity preserved).
|
||||||
|
|
||||||
|
OPA1612 dual: standard JEDEC pinout (1=OUTA 2=-INA 3=+INA 4=V- 5=+INB 6=-INB 7=OUTB
|
||||||
|
8=V+). At INTEGRATION this summer can use the PARKED 2nd half of the Stage 2 filter's
|
||||||
|
OPA1612 (U4) instead of a separate package -- noted for the merge step.
|
||||||
|
"""
|
||||||
|
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 = Net("+15V"), Net("-15V"), Net("GND")
|
||||||
|
for n in (p15, n15, gnd):
|
||||||
|
n.drive = POWER
|
||||||
|
stage1_out = Net("STAGE1_OUT") # from Stage 1b relay (selected input)
|
||||||
|
click_out = Net("CLICK_OUT") # from Stage 2 reconstruction filter
|
||||||
|
mix_out = Net("MIX_OUT") # -> Stage 4 balanced driver
|
||||||
|
|
||||||
|
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),
|
||||||
|
])
|
||||||
|
u = OPA2(ref="U5")
|
||||||
|
|
||||||
|
# inverting summer: each source -> 10k -> virtual-ground (-INA); Rf 10k; +INA -> gnd
|
||||||
|
ri_in, ri_clk, rf = R(value="10k"), R(value="10k"), R(value="10k")
|
||||||
|
stage1_out += ri_in[1]; ri_in[2] += u["-INA"]
|
||||||
|
click_out += ri_clk[1]; ri_clk[2] += u["-INA"]
|
||||||
|
rf[1] += u["-INA"]; rf[2] += u["OUTA"]
|
||||||
|
u["+INA"] += gnd
|
||||||
|
u["OUTA"] += mix_out
|
||||||
|
|
||||||
|
# supplies + decoupling; park unused section B
|
||||||
|
u["V+"] += p15; u["V-"] += n15
|
||||||
|
for rail in (p15, n15):
|
||||||
|
c = C(value="100nF"); rail += c[1]; c[2] += gnd
|
||||||
|
u["+INB"] += gnd; u["OUTB"] += u["-INB"] # parked follower
|
||||||
|
|
||||||
|
ERC()
|
||||||
|
out = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "kicad", "stage3_sum.net"))
|
||||||
|
generate_netlist(file_=out)
|
||||||
|
print("Stage 3 netlist ->", out)
|
||||||
40
hardware/eda/sim/stage3_sum.cir
Normal file
40
hardware/eda/sim/stage3_sum.cir
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
* PM_K-1 Stage 3 : summing node (selected input + click)
|
||||||
|
*
|
||||||
|
* An inverting summing amp mixes STAGE1_OUT (line/instrument) and CLICK_OUT (DAC).
|
||||||
|
* Each source feeds the op-amp's inverting input through its own 10k resistor; the
|
||||||
|
* feedback holds that node at a "virtual ground" (~0V). Because BOTH sources see 0V
|
||||||
|
* there, neither can load or pull on the other -- they sum with no interaction.
|
||||||
|
* Vout = -(Rf/Ri1)*V1 - (Rf/Ri2)*V2 ; with Rf=Ri=10k -> Vout = -(V1+V2).
|
||||||
|
*
|
||||||
|
* We confirm: each input alone = 0 dB (gain -1), both together = +6 dB (they add),
|
||||||
|
* and each input's gain is unchanged by the other (isolation).
|
||||||
|
* Run: ngspice -b ../eda/sim/stage3_sum.cir
|
||||||
|
|
||||||
|
.title Stage3 inverting summer
|
||||||
|
|
||||||
|
Vinp inp 0 AC 1 ; selected input (STAGE1_OUT)
|
||||||
|
Vclk clk 0 AC 1 ; filtered click (CLICK_OUT)
|
||||||
|
Ri1 inp vm 10k
|
||||||
|
Ri2 clk vm 10k
|
||||||
|
Rf vm out 10k
|
||||||
|
Eop out 0 0 vm 1e6 ; +in = gnd, -in = vm -> inverting; feedback makes vm a virtual ground
|
||||||
|
|
||||||
|
.ac dec 10 100 20k
|
||||||
|
.control
|
||||||
|
* both sources active -> they sum
|
||||||
|
run
|
||||||
|
meas ac g_both find vdb(out) at=1k
|
||||||
|
echo " both = $&g_both dB (+6 dB over each-alone = the two sum)"
|
||||||
|
* input alone (click muted)
|
||||||
|
alter @Vclk[acmag]=0
|
||||||
|
run
|
||||||
|
meas ac g_in find vdb(out) at=1k
|
||||||
|
echo " input alone = $&g_in dB (gain -1, i.e. 0 dB)"
|
||||||
|
* click alone (input muted)
|
||||||
|
alter @Vinp[acmag]=0
|
||||||
|
alter @Vclk[acmag]=1
|
||||||
|
run
|
||||||
|
meas ac g_clk find vdb(out) at=1k
|
||||||
|
echo " click alone = $&g_clk dB (gain -1; unchanged by the input = virtual-ground isolation)"
|
||||||
|
.endc
|
||||||
|
.end
|
||||||
5
hardware/kicad/stage3_sum.erc
Normal file
5
hardware/kicad/stage3_sum.erc
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
ERC WARNING: Only one pin (PASSIVE pin 1/~ of R/R1) attached to net STAGE1_OUT.
|
||||||
|
ERC WARNING: Only one pin (PASSIVE pin 1/~ of R/R2) attached to net CLICK_OUT.
|
||||||
|
ERC INFO: 2 warnings found while running ERC.
|
||||||
|
ERC INFO: 0 errors found while running ERC.
|
||||||
|
|
||||||
23
hardware/kicad/stage3_sum.log
Normal file
23
hardware/kicad/stage3_sum.log
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
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 OPA1612 instantiated at /work/hardware/eda/circuits/stage3_sum.py:50.
|
||||||
|
WARNING: Random tag 5n2wh6xRRe generated for OPA1612.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage3_sum.py:53.
|
||||||
|
WARNING: Random tag PsTgUzSm6S generated for R.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage3_sum.py:53.
|
||||||
|
WARNING: Random tag MJ5MDnj_yt generated for R.
|
||||||
|
WARNING: Missing tag on R instantiated at /work/hardware/eda/circuits/stage3_sum.py:53.
|
||||||
|
WARNING: Random tag RyhIEBAL2O generated for R.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage3_sum.py:63.
|
||||||
|
WARNING: Random tag oOB0ftP80i generated for C.
|
||||||
|
WARNING: Missing tag on C instantiated at /work/hardware/eda/circuits/stage3_sum.py:63.
|
||||||
|
WARNING: Random tag NmK6U68Pr1 generated for C.
|
||||||
|
WARNING: Missing tag on instantiated at /work/hardware/kicad/<frozen importlib._bootstrap>:488.
|
||||||
|
INFO: 17 warnings found while generating netlist.
|
||||||
|
INFO: 0 errors found while generating netlist.
|
||||||
|
|
||||||
23
hardware/kicad/stage3_sum_sklib.py
Normal file
23
hardware/kicad/stage3_sum_sklib.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
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'
|
||||||
|
|
||||||
|
stage3_sum = SchLib(tool=SKIDL).add_parts(*[
|
||||||
|
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':'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':'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':[] })])
|
||||||
Loading…
Reference in a new issue