Skip to content
copperhead.sh
Get started

Examples / TI op amp handbook / Buffers

Inverting buffer adjustable gain

SBOA092B page 50, Inverting Buffer Adjustable Gain: EI through RI (10 kΩ), a 100 Ω potentiometer and RO (10 kΩ) in a row to the output, with the pot's wiper on the inverting input.

The handbook prints no formula. The drawing’s, with s the wiper’s position from the R_I end:

E_O / E_I = -(R_O + (1 - s) R_2) / (R_I + s R_2)
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/inverting_buffer_adjustable_gain.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 leaves the wiper’s position open, so the program records a choice (trim): the centre, where both sides are 10.05 kΩ and the gain is exactly -1 (a_v). Two more parameters hold the ends of the trim: -1.01 with the wiper at the R_I end (a_v_input_end) and -0.990099 at the R_O end (a_v_output_end). All three are tied to the parts by the same function of the pot’s setting, so a different pot or resistor fails the check.

out/simulation.txt:

RunPot settingMeasuredClaimed
centre0.5-1-1 (a_v), holds
wiper_at_input_end0-1.01-1.01 (a_v_input_end), holds
wiper_at_output_end1-0.9901-0.990099 (a_v_output_end), holds

A 100 Ω pot between two 10 kΩ resistors trims the gain by about ±1%, which covers the mismatch of two 0.5% resistors.

Terminal window
fang check examples/ti_opamp_handbook/buffers/inverting_buffer_adjustable_gain/inverting_buffer_adjustable_gain.py
python examples/regenerate.py ti_opamp_handbook/buffers/inverting_buffer_adjustable_gain # needs ngspice
examples/ti_opamp_handbook/buffers/inverting_buffer_adjustable_gain/inverting_buffer_adjustable_gain.py
"""The inverting buffer with adjustable gain, SBOA092B page 50.
Show 16 more lines
E_O / E_I = -(R_O + (1 - s) R_2) / (R_I + s R_2)
An inverter of gain -1, with a 100 Ohm potentiometer between R_I and R_O and
its wiper on the - input. Where the wiper sits decides how much of the pot is
on the input side and how much on the feedback side, so the gain can be
trimmed a little either way of -1, which is what the text says it is for:
making up for the tolerance of two 10 kOhm resistors.
The handbook prints no formula. The one above is the drawing's, with s the
wiper's position from the R_I end. At the centre both sides are 10.05 kOhm
and the gain is exactly -1; at the ends it is -10100/10000 = -1.01 and
-10000/10100 = -0.990, a trim of about +/-1%. The figure leaves the wiper's
position open, so `trim` records the centre as the setting the program
claims, and the bench turns the pot to each end.
"""
import sys
from decimal import Decimal
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 Ohm, 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,
Ground,
OpAmp,
Potentiometer,
Run,
Terminal,
equals,
minus,
negative,
over,
product,
ratio,
total,
within,
)
def gain(r_in, r_out, pot, setting):
"""-(R_O + (1 - s) R_2) / (R_I + s R_2), for a wiper at s from the R_I end."""
return negative(
over(
total(r_out, product(minus(1 * ratio, setting), pot)),
total(r_in, product(setting, pot)),
)
)
class InvertingBufferAdjustableGain(System):
"""R_I, the pot, R_O in a row from E_I to E_O; the wiper on the - input."""
figure = Cites(
"Potentiometer in feedback allows gain trimming to compensate for "
"tolerance in resistor values.",
document="SBOA092B, Handbook of Operational Amplifier Applications",
locator="page 50, Inverting Buffer Adjustable Gain",
)
trim = Chooses(
"Where is the wiper?",
selected="at the centre of its travel, where the gain is exactly -1",
alternatives=[
{
"option": "at either end",
"reason": (
"the ends are the trim's limits, not a setting anyone "
"would leave it at; the bench runs both to show the range"
),
},
],
rationale=(
"the figure draws the wiper and gives no setting",
"with matched 10 kOhm resistors the centre is where the trim lands",
),
)
a_v = Parameter("1", default=-1 * ratio, description="E_O / E_I at the pot's setting")
a_v_input_end = Parameter(
"1", default=Decimal("-1.01") * ratio, description="the wiper at the R_I end"
)
a_v_output_end = Parameter(
"1", default=Decimal("-0.990099") * ratio, description="the wiper at the R_O end"
)
e_in = Terminal()
e_out = Terminal()
e_in_return = Terminal()
e_out_return = Terminal()
r_in = Resistor(resistance=10 * kOhm)
pot = Potentiometer(resistance=100 * Ohm, setting=Decimal("0.5") * ratio)
r_out = Resistor(resistance=10 * kOhm)
amp = OpAmp()
ground = Ground()
def architecture(self):
self.e_in.probe >> self.r_in.p1
self.r_in.p2 >> self.pot.end_a
self.pot.wiper >> self.amp.inverting.signal
self.pot.end_b >> 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.e_in_return.probe >> self.ground.node
self.e_out_return.probe >> self.ground.node
def constraints(self):
r_in, r_out, pot = self.r_in.resistance, self.r_out.resistance, self.pot.resistance
require(equals(self.a_v, gain(r_in, r_out, pot, self.pot.setting)))
require(equals(self.a_v_input_end, gain(r_in, r_out, pot, 0 * ratio)))
# -10000/10100 does not end; six figures of it. `within` takes its
# band as a fraction of the target, so it is written on the magnitudes.
require(
within(
negative(self.a_v_output_end),
negative(gain(r_in, r_out, pot, 1 * ratio)),
1e-6,
)
)
BENCH = Bench(
page=50,
title="Inverting Buffer Adjustable Gain",
runs=[
Run(
"centre",
OperatingPoint(),
drive={"e_in": "DC 1"},
measure={"gain": "v({e_out.1}) / v({e_in.1})"},
claims=[Claim("gain", "a_v", within=0.0001)],
),
Run(
"wiper_at_input_end",
OperatingPoint(),
drive={"e_in": "DC 1"},
settings={"pot": {"setting": 0}},
measure={"gain": "v({e_out.1}) / v({e_in.1})"},
claims=[Claim("gain", "a_v_input_end", within=0.0001)],
note="All 100 Ohm on the feedback side: the most gain the trim gives.",
),
Run(
"wiper_at_output_end",
OperatingPoint(),
drive={"e_in": "DC 1"},
settings={"pot": {"setting": 1}},
measure={"gain": "v({e_out.1}) / v({e_in.1})"},
claims=[Claim("gain", "a_v_output_end", within=0.0001)],
note="All 100 Ohm on the input side: the least.",
),
],
)

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

out/netlist.txt
GND1 Ground -
R1 10 kOhm -
R2 10 kOhm -
RV1 Potentiometer -
TP1 Terminal -
TP2 Terminal -
TP3 Terminal -
TP4 Terminal -
U1 OpAmp -
Net-(GND1-Pad1) GND1.1 TP2.1 TP4.1 U1.IN+
Net-(R1-Pad1) R1.1 TP1.1
Net-(R1-Pad2) R1.2 RV1.1
Net-(R2-Pad1) R2.1 RV1.3
Net-(R2-Pad2) R2.2 TP3.1 U1.OUT
Net-(RV1-Pad2) RV1.2 U1.IN-

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
9 component
18 connection
3 constraint
1 decision
1 evidence
3 interface
15 pin
15 port
66 total
snapshot sha256:e8443e2d26240564d445c4eb95ebd5e1ee415242ce97f4bed60fb11570de7383

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

Terminal window
fang build examples/ti_opamp_handbook/buffers/inverting_buffer_adjustable_gain/inverting_buffer_adjustable_gain.py