Examples / TI op amp handbook / Summers
Voltage summer
SBOA092B page 63, The Voltage Summer: E1, E2 and E3 each through its own resistor into the summing point, RO from the output back to it, and the non-inverting input on ground.
E_O = -R_O (E1/R1 + E2/R2 + E3/R3)The circuit
Section titled “The circuit”The schematic is drawn by copperhead’s
drafting engine from this circuit’s netlist, with KiCad’s own library symbols,
and it opens in KiCad as figure/voltage_summer.kicad_sch.
The op amp is KiCad’s generic one, since the handbook’s are ideal, and each
terminal is a test point named as the program names it. KiCad reads back from
the sheet exactly the connections the circuit has; draw_figures.py refuses to write
one that does not.
The interconnect view is fang’s own projection. It names the parts as the program does, so it reads against the code below.
What the program says
Section titled “What the program says”The figure names the resistors and gives them no values, so the program
chooses them and records the choice as a decision (values): R_O = 100 kΩ and
R1, R2, R3 = 10, 20 and 50 kΩ, so the three weights, -10, -5 and -2, all
differ and an input wired to the wrong resistor would show. Each weight is a
parameter held to the parts:
require(equals(self.a_1, negative(over(r_o, self.r_1.resistance))))The page draws a dotted line for “any number” of inputs; the program builds the three drawn.
What the simulation found
Section titled “What the simulation found”out/simulation.txt, from the decks under
out/spice/:
| Run | Measured | Claimed |
|---|---|---|
e1_alone, E1 = 1 V | -10 | -10 (a_1), holds |
e2_alone, E2 = 1 V | -5 | -5 (a_2), holds |
e3_alone, E3 = 1 V | -2 | -2 (a_3), holds |
all_three, E1..E3 = 0.5, 0.3, -0.4 V: E_O | -5.7 V | -5.7 V, holds |
all_three: the summing point | 5.7 µV | 0 ± 10 µV, holds |
all_three: I_O through R_O | 57 µA | 57 µA, holds |
all_three: I1 + I2 + I3 | 57 µA | 57 µA, holds |
The last three are the page’s two “summing point restraints”: the summing point sits at ground (to within E_O over the open-loop gain), and the current into it through R_O balances the currents in.
Running it
Section titled “Running it”fang check examples/ti_opamp_handbook/summers/voltage_summer/voltage_summer.pypython examples/regenerate.py ti_opamp_handbook/summers/voltage_summer # needs ngspiceThe whole program
Section titled “The whole program”"""The voltage summer, SBOA092B page 63.Show 14 more lines
E_O = -R_O (E1/R1 + E2/R2 + E3/R3)
Each input drives a current E_n/R_n into the summing point, which the loopholds at ground, and all of it leaves through R_O, so each input reaches theoutput multiplied by -R_O/R_n. The page draws three inputs and a dotted lineabove the third for "any number"; the program builds the three drawn.
The figure names the resistors and gives them no values, so `values` recordsthe set chosen here: R_O = 100 kOhm and R1, R2, R3 = 10, 20 and 50 kOhm, sothat the three weights, -10, -5 and -2, are all different and a swapped inputwould show. The bench drives each input alone and reads its weight, then allthree at once and reads the weighted sum."""
import sysfrom pathlib import Path
# The handbook's shared parts and bench live in the folder above the sections.sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from fang.lang import Parameter, System, kOhm, requirefrom fang.parts import Resistorfrom fang.rationale import Chooses, Citesfrom fang.simulation import OperatingPoint
from handbook import ( Bench, Claim, Ground, OpAmp, Run, Terminal, equals, negative, over, ratio,)
class VoltageSummer(System): """E1, E2, E3 each through its own R_n into the summing point, R_O back."""
figure = Cites( "E_O = -R_O (E1/R1 + E2/R2 + E3/R3 + ...). Thus, each input, E_n, is " "multiplied by a factor, -R_O/R_n, before summing", document="SBOA092B, Handbook of Operational Amplifier Applications", locator="page 63, The Voltage Summer", )
values = Chooses( "What are R1, R2, R3 and R_O?", selected="R1 10 kOhm, R2 20 kOhm, R3 50 kOhm, R_O 100 kOhm: weights -10, -5, -2", alternatives=[ { "option": "all four equal", "reason": "that is the adder on the next page, and equal weights cannot " "show that each input gets its own", }, { "option": "leave them unknown", "reason": "a weight nobody can compute is not a claim anything can check", }, ], rationale=( "the figure names the resistors and gives no values", "three different weights, so an input wired to the wrong resistor fails", "standard values, and weights that keep E_O inside the swing for inputs " "under a volt", ), )
a_1 = Parameter("1", default=-10 * ratio, description="-R_O / R1") a_2 = Parameter("1", default=-5 * ratio, description="-R_O / R2") a_3 = Parameter("1", default=-2 * ratio, description="-R_O / R3")
e1 = Terminal() e2 = Terminal() e3 = Terminal() common = Terminal() e_out = Terminal()
r_1 = Resistor(resistance=10 * kOhm) r_2 = Resistor(resistance=20 * kOhm) r_3 = Resistor(resistance=50 * kOhm) r_out = Resistor(resistance=100 * kOhm) amp = OpAmp() ground = Ground()
def architecture(self): self.e1.probe >> self.r_1.p1 self.e2.probe >> self.r_2.p1 self.e3.probe >> self.r_3.p1 self.r_1.p2 >> self.amp.inverting.signal self.r_2.p2 >> self.amp.inverting.signal self.r_3.p2 >> self.amp.inverting.signal self.amp.inverting.signal >> self.r_out.p1 self.r_out.p2 >> self.amp.output.signal self.amp.output.signal >> self.e_out.probe self.amp.non_inverting.signal >> self.ground.node self.common.probe >> self.ground.node
def constraints(self): r_o = self.r_out.resistance require(equals(self.a_1, negative(over(r_o, self.r_1.resistance)))) require(equals(self.a_2, negative(over(r_o, self.r_2.resistance)))) require(equals(self.a_3, negative(over(r_o, self.r_3.resistance))))
def _alone(n: int) -> Run: drive = {f"e{k}": ("DC 1" if k == n else "DC 0") for k in range(1, 4)} return Run( f"e{n}_alone", OperatingPoint(), drive=drive, measure={f"gain_e{n}": f"v({{e_out.1}}) / v({{e{n}.1}})"}, claims=[Claim(f"gain_e{n}", f"a_{n}", within=0.001)], )
BENCH = Bench( page=63, title="The Voltage Summer", runs=[ _alone(1), _alone(2), _alone(3), Run( "all_three", OperatingPoint(), drive={"e1": "DC 0.5", "e2": "DC 0.3", "e3": "DC -0.4"}, measure={ "e_o": "v({e_out.1})", "summing_point": "v({amp.IN-})", "i_o": "(v({amp.IN-}) - v({e_out.1})) / 100k", "i_in": "-(i(vdrive_e1) + i(vdrive_e2) + i(vdrive_e3))", }, claims=[ Claim("e_o", -5.7, within=0.001, unit="V", note="-(10 x 0.5 + 5 x 0.3 + 2 x (-0.4)) = -5.7 V"), Claim("summing_point", 0, within=0.00001, absolute=True, unit="V", note=( "Pin (1), the summing point, at ground: the page's first " "restraint. It sits at -E_O over the open-loop gain, 5.7 uV." )), Claim("i_o", 57e-6, within=0.001, unit="A", note="I_O = -E_O/R_O, the current through R_O"), Claim("i_in", 57e-6, within=0.001, unit="A", note="I1 + I2 + I3: the page's second restraint, -I_O + I1 + I2 + I3 = 0"), ], ), ],)The files it writes
Section titled “The files it writes”The parts, then the nets and the pads on them.
GND1 Ground -R1 10 kOhm -R2 20 kOhm -R3 50 kOhm -R4 100 kOhm -TP1 Terminal -TP2 Terminal -TP3 Terminal -TP4 Terminal -TP5 Terminal -U1 OpAmp -Net-(GND1-Pad1) GND1.1 TP1.1 U1.IN+Net-(R1-Pad1) R1.1 TP2.1Net-(R1-Pad2) R1.2 R2.2 R3.2 R4.1 U1.IN-Net-(R2-Pad1) R2.1 TP3.1Net-(R3-Pad1) R3.1 TP4.1Net-(R4-Pad2) R4.2 TP5.1 U1.OUTEvery check that ran, and every one left undecided.
3 checks, 0 failed, 0 undecidedWhat the elaborated graph contains, by entity kind.
1 block 11 component 22 connection 3 constraint 1 decision 1 evidence 3 interface 17 pin 17 port 76 totalsnapshot sha256:16fe87a4d76917e44c633d56c67ca05ee6c7365f132d2c72d0f8dd8655bdab02All of it, including the KiCad netlist, is in
examples/ti_opamp_handbook/summers/voltage_summer/out/. Rebuild it with:
fang build examples/ti_opamp_handbook/summers/voltage_summer/voltage_summer.py