Examples / TI op amp handbook / Buffers
Balanced output
SBOA092B page 50, Balanced Output: two unity-gain inverters (10 kΩ / 10 kΩ each) in cascade. The first one's output is the terminal labelled EO+, the second one's is EO-.
E_O+ = -E_I, E_O- = +E_I, E_O+ - E_O- = -2 E_IThe 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/balanced_output.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”Every value is drawn, so nothing is chosen. The parameters are the two gains
(a_plus = -1, a_minus = +1), their difference (a_diff = -2) and the p-p
swing of E_O+ measured against E_O-, per volt of input peak
(pp_per_peak = 4). Each is tied to the resistors.
What the simulation found
Section titled “What the simulation found”| Run | Measured | Claimed |
|---|---|---|
dc_gains, E_O+ / E_I | -1 | -1 (a_plus), holds |
dc_gains, E_O- / E_I | 1 | 1 (a_minus), holds |
dc_gains, (E_O+ - E_O-) / E_I | -2 | -2 (a_diff), holds |
sine_1v_peak, p-p of E_O+ - E_O- | 4 V | 4 (pp_per_peak), holds |
sine_1v_peak, p-p of E_O+ against ground | 2 V | 2 V, holds |
sine_10v_peak, p-p of E_O+ - E_O- | 40 V | 40 V, holds |
sine_10v_peak, p-p of E_O+ against ground | 20 V | 20 V, holds |
The p-p of the difference is measured through a unit-gain VCVS card, because
ngspice’s meas reads a single vector.
What the text’s “4E_I” means
Section titled “What the text’s “4E_I” means”The text says that using E_O- as the reference, a p-p swing of 4E_I is obtainable at E_O+. That holds with E_I read as the input’s peak: a sine of peak E_I takes E_O+ - E_O- from -2E_I to +2E_I. Against ground, E_O+ swings only 2E_I p-p, the same as the input. The “swing greater than the rails” also holds: at 10 V peak the difference spans 40 V p-p while each output stays at ±10 V, and with ±13.5 V of swing on each it could span 54 V. The text’s “E_O- terminal at the reference” reads as “as the reference”.
Running it
Section titled “Running it”fang check examples/ti_opamp_handbook/buffers/balanced_output/balanced_output.pypython examples/regenerate.py ti_opamp_handbook/buffers/balanced_output # needs ngspiceThe whole program
Section titled “The whole program”"""The balanced output, SBOA092B page 50.Show 20 more lines
E_O+ = -(R_O / R_I) E_I = -E_I, E_O- = -(R_O / R_I) E_O+ = +E_I
Two unity-gain inverters in cascade. The first's output is the terminal thefigure labels E_O+, and it is the inverted one; the second inverts it againfor E_O-. So E_O+ - E_O- = -2 E_I: a load across the two terminals sees twicethe input, and each output only has to swing as far as the input does.
The text says "by using E_O- terminal as the reference, a p-p swing of 4E_I isobtainable at E_O+". That is true when E_I means the input's peak: a sine ofpeak E_I puts E_O+ at -E_I and E_O- at +E_I, so E_O+ measured against E_O-goes from -2 E_I to +2 E_I, 4 E_I peak to peak. Measured against ground, E_O+swings only 2 E_I p-p, the same as the input. The page's "usable swinggreater than the power supply voltage rails" follows: with +/-13.5 V ofswing on each output the difference can span 54 V p-p.
The resistors are all given, so there is nothing to choose. The bench checksthe DC gains, then drives a 1 kHz sine at 1 V peak and at 10 V peak; at 10 Vthe difference swings 40 V p-p while neither output leaves the rails."""
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, Transient
from handbook import ( Bench, Claim, Ground, OpAmp, Run, Terminal, equals, minus, negative, over, product, ratio,)
class BalancedOutput(System): """An inverter of -1 feeding another; E_O+ between them, E_O- after."""
figure = Cites( "By using E_O- terminal at the reference, a p-p swing of 4E_I is " "obtainable at E_O+, i.e. a usable swing greater than the power " "supply voltage rails.", document="SBOA092B, Handbook of Operational Amplifier Applications", locator="page 50, Balanced Output", )
a_plus = Parameter("1", default=-1 * ratio, description="E_O+ / E_I") a_minus = Parameter("1", default=1 * ratio, description="E_O- / E_I") a_diff = Parameter("1", default=-2 * ratio, description="(E_O+ - E_O-) / E_I") pp_per_peak = Parameter( "1", default=4 * ratio, description="p-p swing of E_O+ against E_O-, over the input's peak", )
e_in = Terminal() e_in_return = Terminal() e_out_plus = Terminal() e_out_minus = Terminal() e_out_return = Terminal() r_in_1 = Resistor(resistance=10 * kOhm) r_out_1 = Resistor(resistance=10 * kOhm) r_in_2 = Resistor(resistance=10 * kOhm) r_out_2 = Resistor(resistance=10 * kOhm) amp_1 = OpAmp() amp_2 = OpAmp() ground = Ground()
def architecture(self): # The first inverter; its output is E_O+. self.e_in.probe >> self.r_in_1.p1 self.r_in_1.p2 >> self.amp_1.inverting.signal self.amp_1.inverting.signal >> self.r_out_1.p1 self.r_out_1.p2 >> self.amp_1.output.signal self.amp_1.output.signal >> self.e_out_plus.probe # The second, fed from E_O+; its output is E_O-. self.amp_1.output.signal >> self.r_in_2.p1 self.r_in_2.p2 >> self.amp_2.inverting.signal self.amp_2.inverting.signal >> self.r_out_2.p1 self.r_out_2.p2 >> self.amp_2.output.signal self.amp_2.output.signal >> self.e_out_minus.probe # The bottom wire. self.amp_1.non_inverting.signal >> self.ground.node self.amp_2.non_inverting.signal >> self.ground.node self.e_in_return.probe >> self.ground.node self.e_out_return.probe >> self.ground.node
def constraints(self): require(equals(self.a_plus, negative(over(self.r_out_1.resistance, self.r_in_1.resistance)))) require( equals( self.a_minus, product(negative(over(self.r_out_2.resistance, self.r_in_2.resistance)), self.a_plus), ) ) require(equals(self.a_diff, minus(self.a_plus, self.a_minus))) # A sine of peak E_I takes the difference from -|a_diff| E_I to +|a_diff| E_I. require(equals(self.pp_per_peak, product(2 * ratio, minus(self.a_minus, self.a_plus))))
def _sine(peak: str, note: str, expected: float | str) -> Run: half = 2 * float(peak) return Run( f"sine_{peak}v_peak", Transient(stop="3m", step="1u"), drive={"e_in": f"SIN(0 {peak} 1k)"}, measure={ "pp_diff": "pp v(diff_probe) from=1m to=3m", "pp_plus": "pp v({e_out_plus.1}) from=1m to=3m", "max_plus": "max v({e_out_plus.1}) from=1m to=3m", "max_minus": "max v({e_out_minus.1}) from=1m to=3m", }, claims=[ Claim("pp_diff", expected, within=0.002, unit="V", note=note), Claim( "pp_plus", half, within=0.002, unit="V", note="Against ground, E_O+ swings 2 E_I p-p, no more than the input.", ), ], units={"max_plus": "V", "max_minus": "V"}, # ngspice's meas reads one vector, so a unit-gain VCVS writes the # difference E_O+ - E_O- onto a node of its own. It loads nothing. cards=["EDIFF_PROBE diff_probe 0 {e_out_plus.1} {e_out_minus.1} 1"], )
BENCH = Bench( page=50, title="Balanced Output", runs=[ Run( "dc_gains", OperatingPoint(), drive={"e_in": "DC 1"}, measure={ "gain_plus": "v({e_out_plus.1}) / v({e_in.1})", "gain_minus": "v({e_out_minus.1}) / v({e_in.1})", "gain_diff": "(v({e_out_plus.1}) - v({e_out_minus.1})) / v({e_in.1})", }, claims=[ Claim("gain_plus", "a_plus", within=0.001), Claim("gain_minus", "a_minus", within=0.001), Claim("gain_diff", "a_diff", within=0.001), ], ), _sine( "1", "4 E_I p-p for E_I = 1 V peak, so the volts read as pp_per_peak: the " "handbook's claim, with E_I read as the peak. Held to 0.2% for the " "sampled peaks.", "pp_per_peak", ), _sine( "10", "40 V p-p across the two outputs while each stays inside +/-13.5 V: " "more than the 27 V p-p one output can give. Held to 0.2%.", 40.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 10 kOhm -R3 10 kOhm -R4 10 kOhm -TP1 Terminal -TP2 Terminal -TP3 Terminal -TP4 Terminal -TP5 Terminal -U1 OpAmp -U2 OpAmp -Net-(GND1-Pad1) GND1.1 TP2.1 TP5.1 U1.IN+ U2.IN+Net-(R1-Pad1) R1.1 TP1.1Net-(R1-Pad2) R1.2 R3.1 U1.IN-Net-(R2-Pad1) R2.1 R3.2 TP4.1 U1.OUTNet-(R2-Pad2) R2.2 R4.1 U2.IN-Net-(R4-Pad2) R4.2 TP3.1 U2.OUTEvery check that ran, and every one left undecided.
4 checks, 0 failed, 0 undecidedWhat the elaborated graph contains, by entity kind.
1 block 12 component 28 connection 4 constraint 1 evidence 3 interface 20 pin 20 port 89 totalsnapshot sha256:7c476fbdfc9a37117bf9c1afaedcb129a5cf2faeeaa4ac7609be6414b8873244All of it, including the KiCad netlist, is in
examples/ti_opamp_handbook/buffers/balanced_output/out/. Rebuild it with:
fang build examples/ti_opamp_handbook/buffers/balanced_output/balanced_output.py