Examples / TI op amp handbook / DC amplifiers
Linear gain control
SBOA092B page 71, Linear Gain Control: an inverting amplifier with RI 10 kΩ in and a 100 kΩ potentiometer wired as a rheostat across it, its wiper tied to the end at the output.
E_O = 0 to -10 E_IZ_in = R_I = 10 kΩ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/linear_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 rheostat puts the setting times 100 kΩ in the loop, so the gain is -10
times the setting, a straight line. The figure gives no setting, so the
program records one decision (rheostat): the setting counts from the
summing-point end, and the claims are held at full travel, the printed -10.
Two constraints tie the claims to the parts:
require(equals(self.a_v, negative(over(product(self.r_out.setting, self.r_out.resistance), self.r_in.resistance))))require(equals(self.z_in, self.r_in.resistance))What the simulation found
Section titled “What the simulation found”out/simulation.txt, from the decks under
out/spice/, each an operating point with E_I = 1 V:
| Setting | Gain | Z_in | Claimed |
|---|---|---|---|
| 1 (full travel) | -10 | 10 kΩ | -10 (a_v), 10 kΩ (z_in), hold |
| 0.75 | -7.5 | -7.5, holds | |
| 0.5 | -5 | -5, holds | |
| 0.25 | -2.5 | -2.5, holds | |
| 0 | E_O = -100 nV | 10 kΩ | 0 V, 10 kΩ, hold |
The gain is linear in the setting and Z_in does not move, which is the difference from the simple gain control before it on page 70. At the bottom of the range only the model’s 1 mΩ wiper contact is in the loop.
Running it
Section titled “Running it”fang check examples/ti_opamp_handbook/dc_amplifiers/linear_gain_control/linear_gain_control.pypython examples/regenerate.py ti_opamp_handbook/dc_amplifiers/linear_gain_control # needs ngspiceThe whole program
Section titled “The whole program”"""Linear gain control, SBOA092B page 71.Show 15 more lines
E_O = 0 to -10 E_I Z_in = R_I = 10 kOhm
An inverting amplifier whose feedback resistor is a 100 kOhm potentiometerwired as a rheostat: the wiper is tied to the end at the output, so theresistance in the loop is the setting times 100 kOhm and the gain is-10 times the setting, a straight line from 0 to -10. The input resistor isfixed, so Z_in stays 10 kOhm whatever the setting.
The figure draws the wiper joined to the right-hand end, which is the output.The program had to decide which end of the travel the setting counts from(`rheostat`) and which setting the claims are held at (full travel, theprinted -10). The bench then moves the wiper to show the line is straight."""
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.parts import Resistorfrom fang.rationale import Chooses, Citesfrom fang.simulation import OperatingPoint
from handbook import ( Bench, Claim, Ground, OpAmp, Potentiometer, Run, Terminal, equals, negative, over, product, ratio,)
class LinearGainControl(System): """E_I through R_I into the summing point, a rheostat back from the output."""
figure = Cites( "E_O = 0 to -10 E_I, Z_in = R_I = 10 kOhm; variable from 0 to 10", document="SBOA092B, Handbook of Operational Amplifier Applications", locator="page 71, Linear Gain Control", )
rheostat = Chooses( "Which end of the 100 kOhm pot is in the loop, and at what setting are the claims held?", selected=( "end 1 on the summing point, wiper and end 3 on the output, so the " "loop sees setting x 100 kOhm; claims held at full travel, -10" ), alternatives=[ { "option": "count the setting from the output end", "reason": ( "the gain would still be linear but would fall as the " "setting rises; counting from the summing point makes the " "setting and the gain magnitude move together" ), }, { "option": "hold the claims at mid travel", "reason": "the printed figure is the end of the range, -10; mid travel is checked on the bench", }, ], rationale=( "the figure ties the wiper to the end at the output, a rheostat", "the page prints the range, 0 to -10, and full travel is its end", ), )
a_v = Parameter("1", default=-10 * ratio, description="E_O / E_I at full travel") z_in = Parameter("Ohm", default=10 * kOhm, description="what E_I sees")
e_in = Terminal() e_out = Terminal() r_in = Resistor(resistance=10 * kOhm) r_out = Potentiometer(resistance=100 * kOhm, setting=Decimal("1") * ratio) amp = OpAmp() ground = Ground()
def architecture(self): self.e_in.probe >> self.r_in.p1 self.r_in.p2 >> self.amp.inverting.signal self.amp.inverting.signal >> self.r_out.end_a self.r_out.wiper >> self.amp.output.signal self.r_out.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): # The rheostat puts setting x R_O in the loop, so the gain is linear in it. require( equals( self.a_v, negative( over( product(self.r_out.setting, self.r_out.resistance), self.r_in.resistance, ) ), ) ) # The summing point is a virtual ground, so E_I sees R_I alone. require(equals(self.z_in, self.r_in.resistance))
def _setting(value: float, gain: float) -> Run: return Run( f"setting_{int(value * 100):03d}", OperatingPoint(), drive={"e_in": "DC 1"}, settings={"r_out": {"setting": value}}, measure={"gain": "v({e_out.1}) / v({e_in.1})"}, claims=[ Claim( "gain", gain, within=0.001, note=f"-10 x {value}: the rheostat puts {value * 100:g} kOhm in the loop", ) ], )
BENCH = Bench( page=71, title="Linear Gain Control", runs=[ Run( "full_travel", OperatingPoint(), drive={"e_in": "DC 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), Claim("z_in", "z_in", within=0.001, unit="Ohm"), ], ), _setting(0.75, -7.5), _setting(0.5, -5.0), _setting(0.25, -2.5), Run( "setting_000", OperatingPoint(), drive={"e_in": "DC 1"}, settings={"r_out": {"setting": 0}}, measure={ "e_out": "v({e_out.1})", "z_in": "-v({e_in.1}) / i(vdrive_e_in)", }, claims=[ Claim( "e_out", 0, within=1e-6, absolute=True, unit="V", note="the bottom of the range: only the pot's 1 mOhm contact is in the loop", ), Claim("z_in", "z_in", within=0.001, unit="Ohm"), ], note="Z_in does not move with the setting, unlike the simple gain control just before it in the handbook.", ), ],)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 -RV1 Potentiometer -TP1 Terminal -TP2 Terminal -U1 OpAmp -Net-(GND1-Pad1) GND1.1 U1.IN+Net-(R1-Pad1) R1.1 TP1.1Net-(R1-Pad2) R1.2 RV1.1 U1.IN-Net-(RV1-Pad2) RV1.2 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 6 component 14 connection 2 constraint 1 decision 1 evidence 3 interface 11 pin 11 port 50 totalsnapshot sha256:ed1f9631f3b3995525ac922d05ef3700909f90d820d0f69fd14751e072647997All of it, including the KiCad netlist, is in
examples/ti_opamp_handbook/dc_amplifiers/linear_gain_control/out/. Rebuild it with:
fang build examples/ti_opamp_handbook/dc_amplifiers/linear_gain_control/linear_gain_control.py