Examples / TI op amp handbook / AC amplifiers
AC preamplifier
SBOA092B page 78, AC Preamplifier: the double-rolloff stage built for high gain. EI through C2 1 µF onto the + input; R2 100 kΩ from there to the junction at the foot of C1 1000 µF, which runs up to the - input; R1 200 Ω from the junction to ground, and beside it R3 2.2 kΩ in series with R4, a 10 kΩ rheostat; R0 100 kΩ feedback; C3 10 µF output coupling.
E_O / E_I = (R_0 + R_1) / R_1 = 500 R4 - Fine gain adjustf_-3dB = 1 / (2 pi R_1 C_1) = 1.6 Hz R_1 C_1 = R_2 C_2The 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/ac_preamplifier.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”In the midband the - input sees R_1 in parallel with R_3 + R_4, so the gain
is 1 + R_0 / (R_1 || (R_3 + R_4)). trim sets R_4 to full travel, 509, the
nearest the trim comes to 500 (a_v); a_v_max is the other end, 546.
load gives C_3 a 100 kΩ load, since the figure draws none. f_low holds
the computed 1/(2 pi R_1 C_1) = 0.80 Hz, and t_1 and t_2 hold
R_1 C_1 = 0.2 s and R_2 C_2 = 0.1 s.
What the simulation found
Section titled “What the simulation found”| Run | Measured | Claimed |
|---|---|---|
response, gain at 100 Hz, R_4 = 10 kΩ | 509 | 509.2 (a_v), holds |
response, -3 dB point at E_O | 0.81 Hz | 0.80 Hz (f_low) ±2%, holds |
response, peak gain | 756.9 (+3.4 dB) | 757.7, holds |
response, slope 0.1 to 0.3 Hz at the op amp | 40.9 dB/decade | 40 ±1.5, holds |
response, input impedance at 100 Hz | 32.9 MΩ | not a claim |
response, upper -3 dB point | 19.7 kHz | not a claim |
trim_at_zero, gain at 100 Hz, R_4 = 0 | 546.2 | 546.5 (a_v_max), holds |
dc, op amp output with E_I = 1 V d.c. | 0 V | 0 V, holds |
The bootstrap works: R_2 carries so little signal that E_I sees 33 MΩ, the “high Z_in”. The upper corner is the op amp’s 10 MHz over a noise gain of 509.
Where the handbook is off
Section titled “Where the handbook is off”- The gain: (R_0 + R_1) / R_1 is 501, and the printed 500 leaves out R_3 and R_4. With them beside R_1 the gain runs from 509 to 546 and never reaches 500; to trim to 500, R_1 would have to be larger than 200 Ω.
- The corner: 1/(2 pi 200 Ω 1000 µF) is 0.80 Hz, not 1.6 Hz. 1.6 Hz is 1/(2 pi R_2 C_2). The measured -3 dB point is 0.81 Hz.
- The rule R_1 C_1 = R_2 C_2 is not met: 0.2 s against 0.1 s. The response peaks 3.4 dB above the midband near 1.3 Hz, where the rule would give about 1.2 dB.
Running it
Section titled “Running it”fang check examples/ti_opamp_handbook/ac_amplifiers/ac_preamplifier/ac_preamplifier.pypython examples/regenerate.py ti_opamp_handbook/ac_amplifiers/ac_preamplifier # needs ngspiceThe whole program
Section titled “The whole program”"""The a.c. preamplifier, SBOA092B page 78.Show 26 more lines
E_O / E_I = (R_0 + R_1) / R_1 = 500 f_-3dB = 1 / (2 pi R_1 C_1) = 1.6 Hz, R_1 C_1 = R_2 C_2
The double-rolloff amplifier of page 77 built for a large gain. E_I reachesthe + input through C_2 (1 uF); R_2 (100 kOhm) returns it to the junction atthe foot of C_1 (1000 uF), which runs up to the - input. From that junctionR_1 (200 Ohm) goes to ground, and beside it R_3 (2.2 kOhm) in series withR_4, a 10 kOhm rheostat (its wiper tied to its grounded end): the "fine gainadjust". R_0 (100 kOhm) closes the loop and C_3 (10 uF) couples the outputout. In the midband C_1 is a short, and what the - input sees to ground isR_1 in parallel with R_3 + R_4, so
E_O / E_I = 1 + R_0 / (R_1 || (R_3 + R_4))
which runs from 509 (R_4 at its full 10 kOhm) to 546 (R_4 at zero). Theprinted 500 is (R_0 + R_1) / R_1 = 501 with R_3 and R_4 left out; no settingof R_4 reaches it. The printed corner is off too: 1/(2 pi 200 1000u) is0.80 Hz, not 1.6, and 1.6 Hz is 1/(2 pi R_2 C_2) instead. The rule R_1 C_1 =R_2 C_2 is not met: 0.2 s against 0.1 s, so the response peaks 3.4 dB abovethe midband near 1.3 Hz before it falls away at 40 dB a decade.
The program keeps the drawn values, sets R_4 to full travel (`trim`), andgives C_3 a load to drive (`load`), because the figure draws none and anoutput coupling capacitor into nothing has no d.c. path."""
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, Ohm, Parameter, System, kOhm, require, s, uFfrom fang.parts import Capacitor, Resistorfrom fang.rationale import Chooses, Citesfrom fang.simulation import ACSweep, OperatingPoint
from handbook import ( Bench, Claim, Ground, OpAmp, Potentiometer, Run, Terminal, at_least, corner, equals, over, parallel, product, ratio, total, within,)
class AcPreamplifier(System): """A bootstrapped double-rolloff stage at a gain of 500, with a trim and an output capacitor."""
figure = Cites( "Completely developed AC amplifier with high Z_in and double rolloff " "rate and gain trim. E_O / E_I = (R_0 + R_1) / R_1 = 500. R4 - Fine " "gain adjust. Low Frequency rolloff begins f_-3dB = 1 / (2 pi R_1 C_1) " "= 1.6 Hz. R_1 C_1 = R_2 C_2", document="SBOA092B, Handbook of Operational Amplifier Applications", locator="page 78, AC Preamplifier", )
trim = Chooses( "Where is R_4 set?", selected="full travel, 10 kOhm in series with R_3, for a gain of 509, the nearest the trim comes to the printed 500", alternatives=[ { "option": "the setting that gives 500", "reason": "there is none: R_3 + R_4 in parallel with R_1 can only lower 200 Ohm, and raise the gain above 501", }, { "option": "leave R_3 and R_4 out, as the printed formula does", "reason": "the figure draws them, and a trim that is not there trims nothing", }, ], rationale=( "R_4 is drawn as a rheostat: its wiper runs to the grounded end", "the page gives no setting", ), )
load = Chooses( "What does C_3 drive?", selected="100 kOhm to ground, the input of a following stage; its corner with C_3 is 0.16 Hz", alternatives=[ { "option": "nothing", "reason": "the output node would have no d.c. path, and the simulator cannot solve it", }, { "option": "10 kOhm", "reason": "its 1.6 Hz corner with C_3 would sit on top of the stage's own and be mistaken for it", }, ], rationale=("the figure draws no load",), )
a_v = Parameter("1", default=Decimal("509.2") * ratio, description="E_O / E_I in the midband, R_4 at full travel") a_v_max = Parameter("1", default=Decimal("546.5") * ratio, description="the same with R_4 at zero") f_low = Parameter("Hz", default=Decimal("0.8") * Hz, description="1 / (2 pi R_1 C_1), computed") t_1 = Parameter("s", default=Decimal("0.2") * s, description="R_1 C_1") t_2 = Parameter("s", default=Decimal("0.1") * s, description="R_2 C_2")
e_in = Terminal() e_out = Terminal() c_2 = Capacitor(capacitance=1 * uF) r_2 = Resistor(resistance=100 * kOhm) c_1 = Capacitor(capacitance=1000 * uF) r_1 = Resistor(resistance=200 * Ohm) r_3 = Resistor(resistance=Decimal("2.2") * kOhm) r_4 = Potentiometer(resistance=10 * kOhm, setting=Decimal("1") * ratio) r_0 = Resistor(resistance=100 * kOhm) c_3 = Capacitor(capacitance=10 * uF) r_load = Resistor(resistance=100 * kOhm) amp = OpAmp() ground = Ground()
def architecture(self): self.e_in.probe >> self.c_2.p1 self.c_2.p2 >> self.amp.non_inverting.signal self.amp.non_inverting.signal >> self.r_2.p1
self.amp.inverting.signal >> self.c_1.p1 self.amp.inverting.signal >> self.r_0.p1 self.r_0.p2 >> self.amp.output.signal
# The junction at the foot of C_1: R_2's return, R_1, and the trim. self.c_1.p2 >> self.r_1.p1 self.r_2.p2 >> self.r_1.p1 self.r_1.p1 >> self.r_3.p1 self.r_3.p2 >> self.r_4.end_a self.r_4.wiper >> self.ground.node self.r_4.end_b >> self.ground.node self.r_1.p2 >> self.ground.node
# The output, through C_3. self.amp.output.signal >> self.c_3.p1 self.c_3.p2 >> self.e_out.probe self.e_out.probe >> self.r_load.p1 self.r_load.p2 >> self.ground.node
def constraints(self): trim = product(self.r_4.setting, self.r_4.resistance) foot = parallel(self.r_1.resistance, total(self.r_3.resistance, trim)) require(within(self.a_v, total(1 * ratio, over(self.r_0.resistance, foot)), 0.001)) # R_4 at zero leaves R_3 alone beside R_1. require( within( self.a_v_max, total(1 * ratio, over(self.r_0.resistance, parallel(self.r_1.resistance, self.r_3.resistance))), 0.001, ) ) # The printed 1.6 Hz is twice this. require(within(self.f_low, corner(self.r_1.resistance, self.c_1.capacitance), 0.01)) # And the printed rule does not hold: R_1 C_1 is twice R_2 C_2. require(equals(self.t_1, product(self.r_1.resistance, self.c_1.capacitance))) require(equals(self.t_2, product(self.r_2.resistance, self.c_2.capacitance))) require(at_least(self.t_1, product(self.t_2, 2 * ratio)))
BENCH = Bench( page=78, title="AC Preamplifier", runs=[ Run( "response", ACSweep(points=200, start="0.001", stop="10meg"), drive={"e_in": "DC 0 AC 1"}, measure={ "gain_100": "find vm({e_out.1}) at=100", "f_3db": "when vdb({e_out.1})=51.1087 cross=1", "peak": "max vm({e_out.1}) from=0.01 to=10", "g_01": "find vm({amp.OUT}) at=0.1", "g_03": "find vm({amp.OUT}) at=0.3", "slope": "20 * log10(g_03 / g_01) / log10(3)", "p_re": "find vr({amp.IN+}) at=100", "p_im": "find vi({amp.IN+}) at=100", "j_re": "find vr({r_1.1}) at=100", "j_im": "find vi({r_1.1}) at=100", "z_in_100": "100k / sqrt((p_re - j_re)^2 + (p_im - j_im)^2)", "f_upper": "when vdb({e_out.1})=51.1087 cross=2", }, claims=[ Claim( "gain_100", "a_v", within=0.001, note=( "1 + 100k / (200 || 12.2k), read at 100 Hz, clear of " "the low corners and of the op amp's 20 kHz. The " "handbook prints 500; (R_0 + R_1) / R_1 is 501, and " "with the trim beside R_1 the gain is 509 to 546" ), ), Claim( "f_3db", "f_low", within=0.02, unit="Hz", note=( "the handbook prints 1.6 Hz; 1/(2 pi R_1 C_1) is 0.80 Hz. " "The circuit, with its double pole and C_3 into 100 k, " "is 3 dB down at 0.81 Hz, hence 2%" ), ), Claim( "peak", 757.7, within=0.005, note=( "3.45 dB above the midband near 1.3 Hz, from the transfer " "function with R_1 C_1 = 0.2 s and R_2 C_2 = 0.1 s; the " "page's rule R_1 C_1 = R_2 C_2 would bring it down to 1.2 dB" ), ), Claim( "slope", 40, within=1.5, absolute=True, note="dB per decade at the op amp's output between 0.1 Hz and 0.3 Hz: the double rolloff", ), ], units={"f_upper": "Hz", "z_in_100": "Ohm"}, note=( "E_O is 3 dB down from 509.2 at 51.11 dB. z_in_100 is E_I over " "the current in R_2 at 100 Hz, the bootstrapped input " "impedance the page calls high Z_in; it is not a numeric claim of " "the handbook." ), ), Run( "trim_at_zero", ACSweep(points=10, start="10", stop="1k"), drive={"e_in": "DC 0 AC 1"}, settings={"r_4": {"setting": 0}}, measure={"gain_100": "find vm({e_out.1}) at=100"}, claims=[Claim("gain_100", "a_v_max", within=0.001, note="1 + 100k / (200 || 2.2k)")], ), Run( "dc", OperatingPoint(), drive={"e_in": "DC 1"}, measure={"e_amp": "v({amp.OUT})", "e_out": "v({e_out.1})"}, claims=[ Claim( "e_amp", 0, within=1e-6, absolute=True, unit="V", note="C_2 blocks the 1 V, and C_1 leaves the stage a follower of its grounded + input", ) ], units={"e_out": "V"}, ), ],)The files it writes
Section titled “The files it writes”The parts, then the nets and the pads on them.
C1 1000 uF -C2 1 uF -C3 10 uF -GND1 Ground -R1 100 kOhm -R2 200 Ohm -R3 100 kOhm -R4 2.2 kOhm -R5 100 kOhm -RV1 Potentiometer -TP1 Terminal -TP2 Terminal -U1 OpAmp -Net-(C1-Pad1) C1.1 R1.1 U1.IN-Net-(C1-Pad2) C1.2 R2.1 R3.2 R4.1Net-(C2-Pad1) C2.1 TP1.1Net-(C2-Pad2) C2.2 R3.1 U1.IN+Net-(C3-Pad1) C3.1 R1.2 U1.OUTNet-(C3-Pad2) C3.2 R5.1 TP2.1Net-(GND1-Pad1) GND1.1 R2.2 R5.2 RV1.2 RV1.3Net-(R4-Pad2) R4.2 RV1.1Every check that ran, and every one left undecided.
6 checks, 0 failed, 0 undecidedWhat the elaborated graph contains, by entity kind.
1 block 13 component 34 connection 6 constraint 2 decision 1 evidence 3 interface 25 pin 25 port 110 totalsnapshot sha256:4ff23d07c39ec07f82599a1ba528ba52c2d60d25acc127680711382a68edec2fAll of it, including the KiCad netlist, is in
examples/ti_opamp_handbook/ac_amplifiers/ac_preamplifier/out/. Rebuild it with:
fang build examples/ti_opamp_handbook/ac_amplifiers/ac_preamplifier/ac_preamplifier.py