Examples / TI op amp handbook / DC amplifiers
Simple gain control
SBOA092B pages 70 and 71, Simple Gain Control: one 10 kΩ potentiometer with one end at EI, the other at EO, and its wiper on the inverting input; the non-inverting input on ground. The page says: "Wide range gain or attenuation. Unity gain with R centered. The gain is not linear with potentiometer setting. Zin drops as gain is increased."
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/simple_gain_control.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 page prints no formula, so the program derives one from the figure. With the wiper a fraction k of the travel from the E_I end, k R is the input resistor and (1 - k) R the feedback:
E_O / E_I = -(1 - k) / k Z_in = k RBoth are constraints on the pot’s own parameters, so a_v and z_in follow
the setting:
r_input = product(self.pot.resistance, self.pot.setting)r_feedback = product(self.pot.resistance, minus(1 * ratio, self.pot.setting))require(equals(self.a_v, negative(over(r_feedback, r_input))))require(equals(self.z_in, r_input))The default setting is the centre (setting), where the gain is -1 and Z_in
5 kΩ, and the bench moves the wiper to four other positions. The ends are left
out: at the E_I end the gain is unbounded, and at the E_O end the input is
wired straight to the summing point.
What the simulation found
Section titled “What the simulation found”out/simulation.txt, from the decks under
out/spice/, each with E_I = 0.1 V:
| Wiper k | Gain | Z_in | Claimed |
|---|---|---|---|
| 0.10 | -9 | 1 kΩ | -9, 1 kΩ, hold |
| 0.25 | -3 | 2.5 kΩ | -3, 2.5 kΩ, hold |
| 0.50 | -1 | 5 kΩ | a_v, z_in, hold |
| 0.75 | -0.3333 | 7.5 kΩ | -1/3, 7.5 kΩ, hold |
| 0.90 | -0.1111 | 9 kΩ | -1/9, 9 kΩ, hold |
All three of the page’s sentences are in the table. Moving the wiper by the same 0.15 either side of 0.25 and 0.75 changes the gain by very different amounts, so the gain is not linear with the setting. The gain rises as k falls, and Z_in = k R falls with it. The centre gives unity.
The pot is labelled “10 kW” in the figure. That is 10 kΩ with the ohm sign lost to a font.
Running it
Section titled “Running it”fang check examples/ti_opamp_handbook/dc_amplifiers/simple_gain_control/simple_gain_control.pypython examples/regenerate.py ti_opamp_handbook/dc_amplifiers/simple_gain_control # needs ngspiceThe whole program
Section titled “The whole program”"""Simple gain control, SBOA092B pages 70 and 71.Show 21 more lines
"Wide range gain or attenuation." "Unity gain with R centered. The gain is not linear with potentiometer setting. Z_in drops as gain is increased."
One 10 kOhm potentiometer does both resistors' work: one end at E_I, theother at E_O, and the wiper on the inverting input, with the non-invertinginput on ground. With the wiper a fraction k of the travel from the E_I end,k R is the input resistor and (1 - k) R the feedback, so
E_O / E_I = -(1 - k) / k Z_in = k R
which is -1 at k = 1/2, runs towards minus infinity as k falls and towards 0as k rises, and is not linear in k. The page prints no formula; the programderives this one from the figure and holds the page's three sentences againstit. The pot is labelled "10 kW" on the page, which is 10 kOhm with the ohmsign lost to a font.
The setting is the run's: `setting` records the centre as the program'sdefault, and the bench moves the wiper through five positions."""
import sysfrom decimal import Decimalfrom 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.rationale import Chooses, Citesfrom fang.simulation import OperatingPoint
from handbook import ( Bench, Claim, Ground, OpAmp, Potentiometer, Run, Terminal, equals, minus, negative, over, product, ratio,)
class SimpleGainControl(System): """A potentiometer from E_I to E_O with its wiper on the summing point."""
figure = Cites( "Wide range gain or attenuation. Unity gain with R centered. The gain is " "not linear with potentiometer setting. Zin drops as gain is increased. " "(Pot drawn as 10 kW)", document="SBOA092B, Handbook of Operational Amplifier Applications", locator="pages 70 and 71, Simple Gain Control", )
setting = Chooses( "Where is the wiper?", selected=( "at the centre by default, where the page says the gain is unity; " "the bench moves it to 0.1, 0.25, 0.75 and 0.9 of the travel from " "the E_I end" ), alternatives=[ { "option": "one fixed setting only", "reason": ( "the page's claims are about how gain and Z_in move with the " "setting, which one setting cannot show" ), }, { "option": "the wiper at either end", "reason": ( "at the E_I end the gain is unbounded and the op amp saturates; " "at the E_O end the input is shorted to the summing point and " "the gain is 0 with a 10 kOhm load on the output" ), }, ], rationale=( "the pot's travel is its only adjustment, and the page names none", ), )
a_v = Parameter("1", default=-1 * ratio, description="E_O / E_I at the default setting") z_in = Parameter("Ohm", default=5 * kOhm, description="what E_I sees at the default setting")
e_in = Terminal() e_out = Terminal() pot = Potentiometer(resistance=10 * kOhm, setting=Decimal("0.5") * ratio) amp = OpAmp() ground = Ground()
def architecture(self): self.e_in.probe >> self.pot.end_a self.pot.wiper >> self.amp.inverting.signal self.pot.end_b >> self.amp.output.signal self.amp.output.signal >> self.e_out.probe self.amp.non_inverting.signal >> self.ground.node
def constraints(self): r_input = product(self.pot.resistance, self.pot.setting) r_feedback = product(self.pot.resistance, minus(1 * ratio, self.pot.setting)) require(equals(self.a_v, negative(over(r_feedback, r_input)))) require(equals(self.z_in, r_input))
def _at(k: float, gain: float, z_in: float, name: str) -> Run: return Run( name, OperatingPoint(), drive={"e_in": "DC 0.1"}, settings={"pot": {"setting": k}}, measure={ "gain": "v({e_out.1}) / v({e_in.1})", "z_in": "-v({e_in.1}) / i(vdrive_e_in)", }, claims=[ Claim("gain", gain, within=0.001, note=f"-(1 - {k:g}) / {k:g}"), Claim("z_in", z_in, within=0.001, unit="Ohm", note=f"{k:g} x 10 kOhm"), ], )
BENCH = Bench( page=70, title="Simple Gain Control", runs=[ _at(0.1, -9.0, 1000.0, "wiper_0_10"), _at(0.25, -3.0, 2500.0, "wiper_0_25"), Run( "wiper_centre", OperatingPoint(), drive={"e_in": "DC 0.1"}, measure={ "gain": "v({e_out.1}) / v({e_in.1})", "z_in": "-v({e_in.1}) / i(vdrive_e_in)", }, claims=[ Claim("gain", "a_v", within=0.001, note="unity gain with R centred"), Claim("z_in", "z_in", within=0.001, unit="Ohm"), ], ), _at(0.75, -1 / 3, 7500.0, "wiper_0_75"), _at(0.9, -1 / 9, 9000.0, "wiper_0_90"), ],)The files it writes
Section titled “The files it writes”The parts, then the nets and the pads on them.
GND1 Ground -RV1 Potentiometer -TP1 Terminal -TP2 Terminal -U1 OpAmp -Net-(GND1-Pad1) GND1.1 U1.IN+Net-(RV1-Pad1) RV1.1 TP1.1Net-(RV1-Pad2) RV1.2 U1.IN-Net-(RV1-Pad3) RV1.3 TP2.1 U1.OUTEvery check that ran, and every one left undecided.
2 checks, 0 failed, 0 undecidedWhat the elaborated graph contains, by entity kind.
1 block 5 component 10 connection 2 constraint 1 decision 1 evidence 3 interface 9 pin 9 port 41 totalsnapshot sha256:a5835a1fe695db61fbfaeb4e1a748fcbb74ce527e3666089b21d6ff72ea120c6All of it, including the KiCad netlist, is in
examples/ti_opamp_handbook/dc_amplifiers/simple_gain_control/out/. Rebuild it with:
fang build examples/ti_opamp_handbook/dc_amplifiers/simple_gain_control/simple_gain_control.py