Examples / TI op amp handbook / Lead and lag
Linear lag
SBOA092B page 85, Lag value linear with R setting: a voltage follower whose + input sits on an RC low-pass, EI through a 10 kΩ pot wired as a rheostat and C = 10 µF to ground.
E_O = E_I / (1 + D R C P) = 10 E_I / (10 + P)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_lag.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 wiper is tied to the end at E_I, so the path is D R and the time constant
D R C, linear in the setting. The program counts D from the + input end
(rheostat), so full travel is all 10 kΩ, the page’s 10/(10 + P). The claims
are the unity DC gain a_v and the corners at D = 1, 1/2 and 1/4 (f_c,
f_half, f_quarter). The printed formula checks out.
What the simulation found
Section titled “What the simulation found”| Run | Measured | Claimed |
|---|---|---|
dc_gain | 1 | 1 (a_v), holds |
full_travel, D = 1, -3 dB point | 1.592 Hz | 1.592 Hz (f_c), holds |
half_travel, D = 1/2 | 3.183 Hz | 3.183 Hz (f_half), holds |
quarter_travel, D = 1/4 | 6.366 Hz | 6.366 Hz (f_quarter), holds |
Running it
Section titled “Running it”fang check examples/ti_opamp_handbook/lead_lag/linear_lag/linear_lag.pypython examples/regenerate.py ti_opamp_handbook/lead_lag/linear_lag # needs ngspiceThe whole program
Section titled “The whole program”"""Lag value linear with R setting, SBOA092B page 85.Show 14 more lines
E_O = E_I / (1 + D R C P) = 10 E_I / (10 + P)
A voltage follower whose + input sits on an RC low-pass: E_I through a10 kOhm potentiometer wired as a rheostat, and C = 10 uF to ground. The wiperis tied to the end at E_I, so the resistance in the path is D R, and the timeconstant is D R C, a straight line in the setting. The page's 10/(10 + P) isD = 1: R C = 0.1 s, a corner at 10 rad/s.
The program had to decide which end of the pot the setting counts from(`rheostat`), and holds the claims at full travel, where the page's numbersare. The bench moves the wiper to 1/2 and 1/4 to show the time constantscale with it. The follower passes DC at unity whatever the setting."""
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 Hz, Parameter, System, kOhm, require, uFfrom fang.parts import Capacitorfrom fang.rationale import Chooses, Citesfrom fang.simulation import ACSweep, OperatingPoint
from handbook import ( Bench, Claim, Ground, OpAmp, Potentiometer, Run, Terminal, corner, equals, product, ratio, within,)
class LinearLag(System): """E_I through a rheostat onto the + input, C to ground there, the output back to the - input."""
figure = Cites( "E_O = E_I / (1 + D R C P) = 10 E_I / (10 + P); non-inverting low distortion", document="SBOA092B, Handbook of Operational Amplifier Applications", locator="page 85, Lag value linear with R setting", )
rheostat = Chooses( "Which end of the pot does the setting count from, and where are the claims held?", selected=( "end 1 on the + input, wiper and end 3 on E_I, so the path is " "setting x 10 kOhm; the claims are held at full travel, the page's 10/(10 + P)" ), alternatives=[ { "option": "count the setting from the E_I end", "reason": "the lag would still be linear but would shrink as D rises, the reverse of D R C", }, ], rationale=( "the figure ties the wiper to the end at E_I, a rheostat", "10/(10 + P) is R C = 0.1 s, all of the pot in the path", ), )
a_v = Parameter("1", default=1 * ratio, description="E_O / E_I at DC") f_c = Parameter("Hz", default=Decimal("1.59155") * Hz, description="the corner at full travel, 1/(2 pi R C)") f_half = Parameter("Hz", default=3.1831 * Hz, description="the corner at D = 1/2") f_quarter = Parameter("Hz", default=6.3662 * Hz, description="the corner at D = 1/4")
e_in = Terminal() e_out = Terminal() pot = Potentiometer(resistance=10 * kOhm, setting=Decimal("1") * ratio) c = Capacitor(capacitance=10 * uF) amp = OpAmp() ground = Ground()
def architecture(self): self.e_in.probe >> self.pot.end_b self.pot.wiper >> self.pot.end_b self.pot.end_a >> self.amp.non_inverting.signal self.amp.non_inverting.signal >> self.c.p1 self.c.p2 >> self.ground.node self.amp.inverting.signal >> self.amp.output.signal self.amp.output.signal >> self.e_out.probe
def constraints(self): # A follower: the + input's voltage is the output's, at DC all of E_I. require(equals(self.a_v, 1 * ratio)) # The lag: D R into C. path = product(self.pot.setting, self.pot.resistance) require(within(self.f_c, corner(path, self.c.capacitance), 0.0001)) # Linear in D: half the path, twice the corner. half = product(Decimal("0.5") * ratio, self.pot.resistance) quarter = product(Decimal("0.25") * ratio, self.pot.resistance) require(within(self.f_half, corner(half, self.c.capacitance), 0.0001)) require(within(self.f_quarter, corner(quarter, self.c.capacitance), 0.0001))
def _sweep(name, setting, corner_claim, note): return Run( name, ACSweep(points=200, start="10m", stop="1k"), drive={"e_in": "DC 0 AC 1"}, settings={"pot": {"setting": setting}}, measure={"f_3db": "when vdb({e_out.1})=-3.0103 fall=1"}, claims=[Claim("f_3db", corner_claim, within=0.005, unit="Hz")], note=note, )
BENCH = Bench( page=85, title="Lag value linear with R setting", runs=[ Run( "dc_gain", OperatingPoint(), drive={"e_in": "DC 1"}, measure={"gain": "v({e_out.1}) / v({e_in.1})"}, claims=[Claim("gain", "a_v", within=0.001)], ), _sweep("full_travel", 1, "f_c", "D = 1: the page's 10/(10 + P), a corner at 10 rad/s."), _sweep("half_travel", 0.5, "f_half", "D = 1/2: half the resistance, twice the corner."), _sweep("quarter_travel", 0.25, "f_quarter", "D = 1/4: a quarter of it, four times the corner."), ],)The files it writes
Section titled “The files it writes”The parts, then the nets and the pads on them.
C1 10 uF -GND1 Ground -RV1 Potentiometer -TP1 Terminal -TP2 Terminal -U1 OpAmp -Net-(C1-Pad1) C1.1 RV1.1 U1.IN+Net-(C1-Pad2) C1.2 GND1.1Net-(RV1-Pad2) RV1.2 RV1.3 TP1.1Net-(TP2-Pad1) TP2.1 U1.IN- U1.OUTEvery check that ran, and every one left undecided.
4 checks, 0 failed, 0 undecidedWhat the elaborated graph contains, by entity kind.
1 block 6 component 14 connection 4 constraint 1 decision 1 evidence 3 interface 11 pin 11 port 52 totalsnapshot sha256:728046891ba220bceecfc254ec7550752dfb0db90979cb3bed184e0590cde745All of it, including the KiCad netlist, is in
examples/ti_opamp_handbook/lead_lag/linear_lag/out/. Rebuild it with:
fang build examples/ti_opamp_handbook/lead_lag/linear_lag/linear_lag.py