Examples / TI op amp handbook / DC amplifiers
Gain control
SBOA092B page 73, Gain Control: EI on the + input, a 10 kΩ potentiometer from the output to ground, and its wiper on the - input.
E_O / E_I = 1 / k, k = the fraction of the pot below the wiperThe page prints no formula, only that this is the non-inverting amplifier with both resistors replaced by the pot; 1/k is that formula with R_O = (1 - k) R and R_I = k R.
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/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”setting records that the pot’s setting counts from the grounded end, so it
is k itself, and that the claim is held at k = 0.1, a gain of 10. One
constraint:
require(equals(self.a_v, over(1 * ratio, self.pot.setting)))What the simulation found
Section titled “What the simulation found”out/simulation.txt, operating points:
| k | E_I | Gain | Claimed |
|---|---|---|---|
| 0.1 | 1 V | 10 | 10 (a_v), holds |
| 1 | 1 V | 1 | 1, holds |
| 0.5 | 1 V | 2 | 2, holds |
| 0.25 | 1 V | 4 | 4, holds |
| 0.02 | 0.2 V | 50 | 50, holds |
At k = 0.02 the drive is cut to 0.2 V so that E_O, 10 V, stays inside the swing. The gain is not linear in the setting, and it heads for infinity as the wiper nears ground.
Running it
Section titled “Running it”fang check examples/ti_opamp_handbook/dc_amplifiers/gain_control/gain_control.pypython examples/regenerate.py ti_opamp_handbook/dc_amplifiers/gain_control # needs ngspiceThe whole program
Section titled “The whole program”"""Gain control, SBOA092B page 73.Show 16 more lines
E_O / E_I = 1 / k, k = the fraction of the pot below the wiper
A non-inverting amplifier whose two resistors are the two halves of one10 kOhm potentiometer: it runs from the output to ground, and the wiper feedsthe - input. The wiper sits at k E_O, the loop holds it at E_I, so the gainis 1/k, from 1 with the wiper at the output towards infinity as it nearsground. The page prints no formula; it says the circuit is equivalent toreplacing both resistors of the non-inverting amplifier, and the formula hereis that one with R_O = (1 - k) R and R_I = k R.
The program had to decide which end the pot's setting counts from and wherethe claim is held (`setting`). The bench moves the wiper through foursettings, and keeps E_I small enough at the top one that the output staysinside its swing."""
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, over, ratio,)
class GainControl(System): """E_I on the + input, the pot from output to ground, its wiper on the - input."""
figure = Cites( "Equivalent to replacing both resistors in the non-inverting amplifier. " "Observe common mode voltage limit.", document="SBOA092B, Handbook of Operational Amplifier Applications", locator="page 73, Gain Control", )
setting = Chooses( "Which end does the pot's setting count from, and where is the claim held?", selected=( "end 1 on ground and end 3 on the output, so the setting is k, the " "fraction below the wiper; the claim is held at k = 0.1, a gain of 10" ), alternatives=[ { "option": "end 1 on the output", "reason": "the setting would be 1 - k, and the gain 1 / (1 - setting) reads less directly", }, { "option": "hold the claim at mid travel, a gain of 2", "reason": "the page's other amplifiers are shown at a gain of 10; mid travel is checked on the bench", }, ], rationale=( "the figure gives the pot's value and no setting", "counting from ground makes the setting the feedback fraction itself", ), )
a_v = Parameter("1", default=10 * ratio, description="E_O / E_I at the chosen setting")
e_in = Terminal() e_out = Terminal() pot = Potentiometer(resistance=10 * kOhm, setting=Decimal("0.1") * ratio) amp = OpAmp() ground = Ground()
def architecture(self): self.e_in.probe >> self.amp.non_inverting.signal self.amp.output.signal >> self.pot.end_b self.pot.wiper >> self.amp.inverting.signal self.pot.end_a >> self.ground.node self.amp.output.signal >> self.e_out.probe
def constraints(self): # The wiper divides E_O by k and the loop holds it at E_I. require(equals(self.a_v, over(1 * ratio, self.pot.setting)))
def _setting(k: float, drive: float) -> Run: return Run( f"k_{int(k * 100):03d}", OperatingPoint(), drive={"e_in": f"DC {drive:g}"}, settings={"pot": {"setting": k}}, measure={"gain": "v({e_out.1}) / v({e_in.1})"}, claims=[Claim("gain", 1 / k, within=0.001, note=f"1 / k = 1 / {k:g}")], )
BENCH = Bench( page=73, title="Gain Control", runs=[ Run( "chosen", OperatingPoint(), drive={"e_in": "DC 1"}, measure={"gain": "v({e_out.1}) / v({e_in.1})", "e_out": "v({e_out.1})"}, claims=[Claim("gain", "a_v", within=0.001)], units={"e_out": "V"}, ), _setting(1.0, 1.0), _setting(0.5, 1.0), _setting(0.25, 1.0), Run( "k_002", OperatingPoint(), drive={"e_in": "DC 0.2"}, settings={"pot": {"setting": 0.02}}, measure={"gain": "v({e_out.1}) / v({e_in.1})"}, claims=[ Claim( "gain", 50, within=0.001, note="1 / 0.02; E_I is 0.2 V here so that E_O, 10 V, stays inside the swing", ) ], ), ],)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 RV1.1Net-(RV1-Pad2) RV1.2 U1.IN-Net-(RV1-Pad3) RV1.3 TP2.1 U1.OUTNet-(TP1-Pad1) TP1.1 U1.IN+Every check that ran, and every one left undecided.
1 checks, 0 failed, 0 undecidedWhat the elaborated graph contains, by entity kind.
1 block 5 component 10 connection 1 constraint 1 decision 1 evidence 3 interface 9 pin 9 port 40 totalsnapshot sha256:8114e0693bd3e59d6f593eeaee1515a859d5b1635ba27f6ca76f7af4b0d5e196All of it, including the KiCad netlist, is in
examples/ti_opamp_handbook/dc_amplifiers/gain_control/out/. Rebuild it with:
fang build examples/ti_opamp_handbook/dc_amplifiers/gain_control/gain_control.py