Examples / TI op amp handbook / Differential input
Adder subtractor
SBOA092B page 68, Adder-Subtractor or Floating Input Combiner: E1 and E2 each through a 10 kΩ R into the inverting input with a third R back from the output, and E3 and E4 each through a 10 kΩ RI into the non-inverting input with a third RI from there to ground.
E_O = -E1 - E2 + E3 + E4The 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/adder_subtractor.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 gives every value, so nothing is chosen. What needed working out is why the + inputs are worth exactly 1 when the two sides are not built alike. The + input sits at (E3 + E4)/3, the average of E3, E4 and ground through three equal R_I. The - side’s noise gain is 1 + R/(R ∥ R) = 3. The two meet: 3 × 1/3 = 1. So the printed formula holds, and it holds for any R against R_I as long as each side is three equal resistors, which is what “R and R_I not necessarily equal” means.
Each input’s weight is a parameter written from the resistors (a_1 to a_4,
and noise_gain = 3), for example:
require( equals( self.a_3, over( product(parallel(self.r_4.resistance, r_i), self.noise_gain), total(self.r_3.resistance, parallel(self.r_4.resistance, r_i)), ), ))A fifth input on either side would upset the 3 × 1/3, and the check would say so.
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 | -1 | -1 (a_1), holds |
e2_alone, E2 = 1 V | -1 | -1 (a_2), holds |
e3_alone, E3 = 1 V | 1 | 1 (a_3), holds |
e4_alone, E4 = 1 V | 1 | 1 (a_4), holds |
all_four, E1..E4 = 0.1, 0.2, 0.4, 0.8 V: E_O | 0.9 V | 0.9 V, holds |
all_four: the + input | 0.4 V | (E3 + E4)/3, holds |
Running it
Section titled “Running it”fang check examples/ti_opamp_handbook/differential_input/adder_subtractor/adder_subtractor.pypython examples/regenerate.py ti_opamp_handbook/differential_input/adder_subtractor # needs ngspiceThe whole program
Section titled “The whole program”"""The adder-subtractor, or floating input combiner, SBOA092B page 68.Show 21 more lines
E_O = -E1 - E2 + E3 + E4
E1 and E2 each reach the inverting input through a 10 kOhm R, with a third Rfrom the output back to it. E3 and E4 each reach the non-inverting inputthrough a 10 kOhm R_I, and a third R_I runs from that input to ground.
The printed result is not the obvious one, because the two sides are notalike: the - side has two inputs and a feedback resistor, the + side twoinputs and a resistor to ground. It holds because the numbers happen to meet.The + input sits at the average of E3, E4 and ground, (E3 + E4)/3, and thenoise gain of the - side is 1 + R/(R || R) = 3, so each + input is worthexactly 1 at the output. With R and R_I each three equal resistors, the ratiobetween R and R_I does not matter, which is what the page means by "R and R_Inot necessarily equal". The program writes each input's weight from theresistors, so a fourth input on either side, which would break the 3 x 1/3,would fail the check.
The figure gives every value, so nothing was chosen. The bench drives eachinput alone, then all four at once with distinct voltages."""
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 Citesfrom fang.simulation import OperatingPoint
from handbook import ( Bench, Claim, Ground, OpAmp, Run, Terminal, equals, negative, over, parallel, product, ratio, total,)
class AdderSubtractor(System): """E1, E2 into the - input; E3, E4 into the + input over a third R_I to ground."""
figure = Cites( "E_O = -E1 - E2 + E3 + E4. R and R_I not necessarily equal", document="SBOA092B, Handbook of Operational Amplifier Applications", locator="page 68, Adder-Subtractor or Floating Input Combiner", )
a_1 = Parameter("1", default=-1 * ratio, description="E_O / E1, the others at ground") a_2 = Parameter("1", default=-1 * ratio, description="E_O / E2, the others at ground") a_3 = Parameter("1", default=1 * ratio, description="E_O / E3, the others at ground") a_4 = Parameter("1", default=1 * ratio, description="E_O / E4, the others at ground") noise_gain = Parameter( "1", default=3 * ratio, description="1 + R / (R || R): what the + input is worth" )
e1 = Terminal() e2 = Terminal() e3 = Terminal() e4 = Terminal() common = Terminal() e_out = Terminal()
r_1 = Resistor(resistance=10 * kOhm) r_2 = Resistor(resistance=10 * kOhm) r_feedback = Resistor(resistance=10 * kOhm) r_3 = Resistor(resistance=10 * kOhm) r_4 = Resistor(resistance=10 * kOhm) r_ground = Resistor(resistance=10 * kOhm) amp = OpAmp() ground = Ground()
def architecture(self): # The subtracting side: E1 and E2 into the summing point, R back. self.e1.probe >> self.r_1.p1 self.e2.probe >> self.r_2.p1 self.r_1.p2 >> self.amp.inverting.signal self.r_2.p2 >> self.amp.inverting.signal self.amp.inverting.signal >> self.r_feedback.p1 self.r_feedback.p2 >> self.amp.output.signal self.amp.output.signal >> self.e_out.probe
# The adding side: E3 and E4 into the + input, a third R_I to ground. self.e3.probe >> self.r_3.p1 self.e4.probe >> self.r_4.p1 self.r_3.p2 >> self.amp.non_inverting.signal self.r_4.p2 >> self.amp.non_inverting.signal self.amp.non_inverting.signal >> self.r_ground.p1
# The common terminal the figure draws at the bottom left, and E_O's. self.r_ground.p2 >> self.ground.node self.common.probe >> self.ground.node
def constraints(self): r, r_i = self.r_feedback.resistance, self.r_ground.resistance
# The - side: each input is an inverting amplifier of its own. require(equals(self.a_1, negative(over(r, self.r_1.resistance)))) require(equals(self.a_2, negative(over(r, self.r_2.resistance))))
# The noise gain the + input sees: R over the two input resistors in # parallel, plus one. require( equals( self.noise_gain, total(1 * ratio, over(r, parallel(self.r_1.resistance, self.r_2.resistance))), ) )
# The + side: each input divides against the other two resistors in # parallel, then the noise gain multiplies it back up. require( equals( self.a_3, over( product(parallel(self.r_4.resistance, r_i), self.noise_gain), total(self.r_3.resistance, parallel(self.r_4.resistance, r_i)), ), ) ) require( equals( self.a_4, over( product(parallel(self.r_3.resistance, r_i), self.noise_gain), total(self.r_4.resistance, parallel(self.r_3.resistance, r_i)), ), ) )
def _alone(n: int) -> Run: drive = {f"e{k}": ("DC 1" if k == n else "DC 0") for k in range(1, 5)} 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=68, title="Adder-Subtractor or Floating Input Combiner", runs=[ _alone(1), _alone(2), _alone(3), _alone(4), Run( "all_four", OperatingPoint(), drive={"e1": "DC 0.1", "e2": "DC 0.2", "e3": "DC 0.4", "e4": "DC 0.8"}, measure={"e_o": "v({e_out.1})", "e_plus": "v({amp.IN+})"}, claims=[ Claim("e_o", 0.9, within=0.001, unit="V", note="-0.1 - 0.2 + 0.4 + 0.8 = 0.9 V"), Claim("e_plus", 0.4, within=0.001, unit="V", note="(E3 + E4)/3: the + input sits at the average of E3, E4 and ground"), ], ), ],)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 10 kOhm -R3 10 kOhm -R4 10 kOhm -R5 10 kOhm -R6 10 kOhm -TP1 Terminal -TP2 Terminal -TP3 Terminal -TP4 Terminal -TP5 Terminal -TP6 Terminal -U1 OpAmp -Net-(GND1-Pad1) GND1.1 R6.2 TP1.1Net-(R1-Pad1) R1.1 TP2.1Net-(R1-Pad2) R1.2 R2.2 R5.1 U1.IN-Net-(R2-Pad1) R2.1 TP3.1Net-(R3-Pad1) R3.1 TP4.1Net-(R3-Pad2) R3.2 R4.2 R6.1 U1.IN+Net-(R4-Pad1) R4.1 TP5.1Net-(R5-Pad2) R5.2 TP6.1 U1.OUTEvery check that ran, and every one left undecided.
5 checks, 0 failed, 0 undecidedWhat the elaborated graph contains, by entity kind.
1 block 14 component 28 connection 5 constraint 1 evidence 3 interface 22 pin 22 port 96 totalsnapshot sha256:20214dee37dcf3f266296f3aebc03dca0cfa37d0169d7ee8318571c970c640a2All of it, including the KiCad netlist, is in
examples/ti_opamp_handbook/differential_input/adder_subtractor/out/. Rebuild it with:
fang build examples/ti_opamp_handbook/differential_input/adder_subtractor/adder_subtractor.py