Skip to content
copperhead.sh
Get started

Examples / TI op amp handbook / Differential input

Balanced output amplifier

SBOA092B page 69, The Differential (Balanced) Output Amplifier: an op amp with two outputs. E1 goes through RI into the inverting input with RO to the top output; E2 goes through RI into the non-inverting input with RO to the bottom output. The handbook calls the bottom output EP and the top one EO + EP, so EO is the difference between the two.

E_O = (R_O / R_I)(E2 - E1)
the schematic, drawn by copperhead from the circuit's netlist
The schematic, drawn by copperhead from the circuit's netlist

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/balanced_output_amplifier.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, fang's own projection
The interconnect view, fang's own projection

The interconnect view is fang’s own projection. It names the parts as the program does, so it reads against the code below.

The figure gives no values, so the program chooses them (values): R_I = 10 kΩ and R_O = 100 kΩ in both legs, a differential gain of 10. Three constraints: the two R_I are equal, the two R_O are equal (the page’s subtraction of the leg equations needs both), and a_d = R_O / R_I.

The page is careful to say that E_f and E_P, where the inputs and outputs sit together, are not set by the loop. The op amp sets them. The bench’s model (DifferentialOpAmp) holds the outputs’ common level at ground, so E_P comes out as -E_O/2. That is the model’s choice, not the handbook’s, and the program reports E_P without claiming it.

out/simulation.txt, from the decks under out/spice/:

RunMeasuredClaimed
difference, E1 = 0.2 V, E2 = 0.5 V: gain1010 (a_d), holds
difference: E_P-1.5 Vnot a claim
floating, E1 = 2.2 V, E2 = 2.5 V: E_O3 V3 V, holds
common_mode, E1 = E2 = 1 V: E_O1.7 fV0 V ± 100 µV, holds

The floating run is the page’s “ground reference is not critical”: the same 0.3 V difference lifted by 2.2 V gives the same E_O.

Terminal window
fang check examples/ti_opamp_handbook/differential_input/balanced_output_amplifier/balanced_output_amplifier.py
python examples/regenerate.py ti_opamp_handbook/differential_input/balanced_output_amplifier # needs ngspice
examples/ti_opamp_handbook/differential_input/balanced_output_amplifier/balanced_output_amplifier.py
"""The differential (balanced) output amplifier, SBOA092B page 69.
Show 16 more lines
E_O = (R_O / R_I) (E2 - E1)
An op amp with two outputs. E1 reaches the inverting input through R_I, with
R_O from there to the top output; E2 reaches the non-inverting input through
R_I, with R_O from there to the bottom output. The handbook calls the bottom
output E_P and the top one E_O + E_P, so E_O is the difference between them.
The page says what the loop does not set: "the values of E_f and E_P are not
uniquely determined". Where the two outputs sit together is the op amp's own
business, and the bench's model holds that common level at ground. So E_P is
reported and not claimed; only the difference is.
The figure names the resistors and gives no values, so `values` records the
pair chosen here: 10 kOhm and 100 kOhm, a differential gain of 10.
"""
import sys
from 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, require
from fang.parts import Resistor
from fang.rationale import Chooses, Cites
from fang.simulation import OperatingPoint
from handbook import (
Bench,
Claim,
DifferentialOpAmp,
Ground,
Run,
Terminal,
equals,
over,
ratio,
)
class BalancedOutputAmplifier(System):
"""Two inverting legs around one op amp with two outputs."""
figure = Cites(
"E_O = (R_O/R_I)(E2 - E1). Note that the values of E_f and E_P are not "
"uniquely determined by the above equations",
document="SBOA092B, Handbook of Operational Amplifier Applications",
locator="page 69, The Differential (Balanced) Output Amplifier",
)
values = Chooses(
"What are R_I and R_O?",
selected="10 kOhm and 100 kOhm in both legs, a differential gain of 10",
alternatives=[
{
"option": "leave them unknown",
"reason": "a gain nobody can compute is not a claim anything can check",
},
{
"option": "different pairs in the two legs",
"reason": (
"the page's subtraction of the two leg equations needs the "
"same R_I and R_O in both; the figure labels them alike"
),
},
],
rationale=(
"the figure names the resistors and gives no values",
"a gain of 10 keeps each output far inside the swing for the drives used",
),
)
a_d = Parameter("1", default=10 * ratio, description="E_O / (E2 - E1)")
e1 = Terminal()
e2 = Terminal()
e_o = Terminal()
e_p = Terminal()
common = Terminal()
r_in_top = Resistor(resistance=10 * kOhm)
r_out_top = Resistor(resistance=100 * kOhm)
r_in_bottom = Resistor(resistance=10 * kOhm)
r_out_bottom = Resistor(resistance=100 * kOhm)
amp = DifferentialOpAmp()
ground = Ground()
def architecture(self):
# The top leg: E1 into the - input, R_O to the output that falls when
# that input rises.
self.e1.probe >> self.r_in_top.p1
self.r_in_top.p2 >> self.amp.inverting.signal
self.amp.inverting.signal >> self.r_out_top.p1
self.r_out_top.p2 >> self.amp.output.signal
self.amp.output.signal >> self.e_o.probe
# The bottom leg: E2 into the + input, R_O to the other output.
self.e2.probe >> self.r_in_bottom.p1
self.r_in_bottom.p2 >> self.amp.non_inverting.signal
self.amp.non_inverting.signal >> self.r_out_bottom.p1
self.r_out_bottom.p2 >> self.amp.output_minus.signal
self.amp.output_minus.signal >> self.e_p.probe
# Nothing in the figure touches ground but the grounded terminal
# between E1 and E2: the op amp's common level is its own.
self.common.probe >> self.ground.node
def constraints(self):
require(equals(self.r_in_top.resistance, self.r_in_bottom.resistance))
require(equals(self.r_out_top.resistance, self.r_out_bottom.resistance))
require(equals(self.a_d, over(self.r_out_top.resistance, self.r_in_top.resistance)))
BENCH = Bench(
page=69,
title="The Differential (Balanced) Output Amplifier",
runs=[
Run(
"difference",
OperatingPoint(),
drive={"e1": "DC 0.2", "e2": "DC 0.5"},
measure={
"gain": "(v({e_o.1}) - v({e_p.1})) / (v({e2.1}) - v({e1.1}))",
"e_p": "v({e_p.1})",
},
claims=[Claim("gain", "a_d", within=0.001)],
units={"e_p": "V"},
note=(
"E_O is the top output less the bottom one. E_P, where the bottom "
"output sits, is set inside the op amp; this model centres the "
"pair on ground, so E_P is -E_O/2 here and not a handbook claim."
),
),
Run(
"floating",
OperatingPoint(),
drive={"e1": "DC 2.2", "e2": "DC 2.5"},
measure={"e_o": "v({e_o.1}) - v({e_p.1})"},
claims=[
Claim("e_o", 3.0, within=0.001, unit="V",
note="the same 0.3 V difference riding on 2.2 V: still 10 x 0.3 V"),
],
),
Run(
"common_mode",
OperatingPoint(),
drive={"e1": "DC 1", "e2": "DC 1"},
measure={"e_o": "v({e_o.1}) - v({e_p.1})"},
claims=[Claim("e_o", 0, within=1e-4, absolute=True, unit="V",
note="1 V on both inputs gives no difference out")],
),
],
)

The parts, then the nets and the pads on them.

out/netlist.txt
GND1 Ground -
R1 10 kOhm -
R2 10 kOhm -
R3 100 kOhm -
R4 100 kOhm -
TP1 Terminal -
TP2 Terminal -
TP3 Terminal -
TP4 Terminal -
TP5 Terminal -
U1 DifferentialOpAmp -
Net-(GND1-Pad1) GND1.1 TP1.1
Net-(R1-Pad1) R1.1 TP3.1
Net-(R1-Pad2) R1.2 R3.1 U1.IN+
Net-(R2-Pad1) R2.1 TP2.1
Net-(R2-Pad2) R2.2 R4.1 U1.IN-
Net-(R3-Pad2) R3.2 TP5.1 U1.OUT-
Net-(R4-Pad2) R4.2 TP4.1 U1.OUT+

Every check that ran, and every one left undecided.

out/checks.txt
3 checks, 0 failed, 0 undecided

What the elaborated graph contains, by entity kind.

out/graph.txt
1 block
11 component
22 connection
3 constraint
1 decision
1 evidence
3 interface
18 pin
18 port
78 total
snapshot sha256:5aa1f1efd122fcca2459562a5230e5e42c8bb7181b321814c1c1548d4abbfebc

All of it, including the KiCad netlist, is in examples/ti_opamp_handbook/differential_input/balanced_output_amplifier/out/. Rebuild it with:

Terminal window
fang build examples/ti_opamp_handbook/differential_input/balanced_output_amplifier/balanced_output_amplifier.py