Examples / TI op amp handbook / Differentiators
Differentiator
SBOA092B page 61, Differentiators (also Figure 43 on page 38): EI through CI into the summing point, RO from the output back to it, and the non-inverting input on ground.
E_O = -R_O C_I dE_I/dtThe 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/differentiator.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 figure names C_I and R_O and gives them no values, so the program chooses
them and records the choice as a decision (values): 0.1 µF and 100 kΩ, the
same pair as the “with stop” figure below it, for a 10 ms time constant. That
puts the unity-gain point, f_unity = 1/(2π R_O C_I), at 15.9 Hz, where
Figure 43 draws X_C = R_O.
The page warns that the simple circuit is not usable. Figure 43 shows why: the gain rises at 20 dB per decade and meets the op amp’s falling open-loop gain, and at that crossing the loop has almost no phase margin. The program claims where that happens, which follows from equating the two lines, 2π f R_O C_I = GBW / f:
require(within(product(self.f_peak, self.f_peak), product(self.amp.gain_bandwidth, self.f_unity), 0.0001))so f_peak = √(10 MHz × 15.9 Hz) = 12.6 kHz. Change the op amp’s
gain-bandwidth and the check fails until f_peak moves with it.
What the simulation found
Section titled “What the simulation found”out/simulation.txt, from the two decks under
out/spice/:
| Run | Measured | Claimed |
|---|---|---|
derivative, unity-gain frequency | 15.92 Hz | 15.92 Hz (f_unity), holds |
derivative, gain at 100 Hz | 6.284 | 2π × 100 Hz × 10 ms = 6.283, holds |
derivative, phase at 100 Hz | -1.571 rad | -π/2, holds |
peak, where the output turns real | 12.62 kHz | 12.62 kHz (f_peak), holds |
peak, height | 3.86 × 10^5 (111.7 dB) | not a claim |
Below the peak the circuit is a derivative, gain and phase both. At 12.6 kHz the ideal gain would be 793; the circuit gives about 486 times that. The height is set by how little damping the op amp model’s one pole leaves, which is the model’s and not the handbook’s, so it is reported, not claimed. The point stands either way: any noise near 12.6 kHz comes out enormous, which is what the page means by “susceptible to high frequency noise”.
Running it
Section titled “Running it”fang check examples/ti_opamp_handbook/differentiators/differentiator/differentiator.pypython examples/regenerate.py ti_opamp_handbook/differentiators/differentiator # needs ngspiceThe whole program
Section titled “The whole program”"""The differentiator, SBOA092B page 61 (and Figure 43 on page 38).Show 20 more lines
E_O = -R_O C_I dE_I/dt
C_I carries E_I into the summing point and R_O runs from the output back toit. The current through C_I is C_I dE_I/dt, and R_O turns it into a voltage,so the gain rises with frequency, 2 pi f R_O C_I, passing unity where C_I'sreactance equals R_O.
The figure names the two parts and gives them no values, so `values` recordsthe pair chosen here: 0.1 uF and 100 kOhm, the C_I and R_O of the "with stop"figure below it on the same page, which puts the unity-gain point at 15.9 Hz,where Figure 43 draws X_C = R_O.
The page says the circuit is not usable as drawn, and Figure 43 shows why: therising gain meets the op amp's falling open-loop gain, and there the loop hasnothing left to damp it. The two lines cross where 2 pi f R_O C_I equalsGBW / f, at f = sqrt(GBW f_1) with f_1 = 1/(2 pi R_O C_I). With the bench's10 MHz op amp that is 12.6 kHz, and the program claims the peak there. Howtall the peak is depends on the op amp's own pole and is reported as measured."""
import sysfrom 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 decimal import Decimal
from fang.lang import Hz, Parameter, System, kHz, kOhm, ms, require, uFfrom fang.parts import Capacitor, Resistorfrom fang.rationale import Chooses, Citesfrom fang.simulation import ACSweep
from handbook import ( Bench, Claim, Ground, OpAmp, Run, Terminal, corner, equals, product, within,)
class Differentiator(System): """E_I through C_I into the summing point, R_O back from the output."""
figure = Cites( "E_O = -R_O C_I dE_I/dt. The ideal differentiator circuit is not generally " "usable in its simple form", document="SBOA092B, Handbook of Operational Amplifier Applications", locator="page 61, Differentiators", )
values = Chooses( "What are C_I and R_O?", selected="0.1 uF and 100 kOhm, a 10 ms time constant", alternatives=[ { "option": "leave them unknown", "reason": "a derivative with no time constant is not a claim anything can check", }, { "option": "a shorter time constant, such as 0.1 uF and 10 kOhm", "reason": "it moves the peak up to 40 kHz and does not change what the figure shows", }, ], rationale=( "the figure names C_I and R_O and gives no values", "the same page's differentiator with stop uses 0.1 uF and 100 kOhm", "Figure 43 draws X_C = R_O near 16 Hz, which 1/(2 pi 100k 0.1u) is", ), )
time_constant = Parameter("s", default=10 * ms, description="R_O C_I") f_unity = Parameter( "Hz", default=Decimal("15.9155") * Hz, description="where 2 pi f R_O C_I = 1" ) f_peak = Parameter( "Hz", default=Decimal("12.6157") * kHz, description="sqrt(GBW f_unity): where the rising gain meets the open-loop roll-off", )
e_in = Terminal() common = Terminal() e_out = Terminal() c_in = Capacitor(capacitance=Decimal("0.1") * uF) r_out = Resistor(resistance=100 * kOhm) amp = OpAmp() ground = Ground()
def architecture(self): self.e_in.probe >> self.c_in.p1 self.c_in.p2 >> self.amp.inverting.signal self.amp.inverting.signal >> self.r_out.p1 self.r_out.p2 >> self.amp.output.signal self.amp.output.signal >> self.e_out.probe self.amp.non_inverting.signal >> self.ground.node self.common.probe >> self.ground.node
def constraints(self): r_o, c_i = self.r_out.resistance, self.c_in.capacitance require(equals(self.time_constant, product(r_o, c_i))) require(within(self.f_unity, corner(r_o, c_i), 0.00001)) # Written squared, since the expression tree has no square root: # f_peak^2 = GBW f_unity. require( within( product(self.f_peak, self.f_peak), product(self.amp.gain_bandwidth, self.f_unity), 0.0001, ) )
BENCH = Bench( page=61, title="Differentiators", runs=[ Run( "derivative", ACSweep(points=100, start="1", stop="1k"), drive={"e_in": "DC 0 AC 1"}, measure={ "f_unity": "when vm({e_out.1})=1", "gain_100": "find vm({e_out.1}) at=100", "phase_100": "find vp({e_out.1}) at=100", }, claims=[ Claim("f_unity", "f_unity", within=0.001, unit="Hz"), Claim("gain_100", 6.2832, within=0.001, note="2 pi x 100 Hz x 10 ms: the gain of a derivative rises with f"), Claim("phase_100", -1.5708, within=0.001, note="-pi/2 radians: -j 2 pi f R_O C_I, a derivative inverted"), ], note=( "Well below the peak the circuit is what the page says: a gain of " "2 pi f R_O C_I, 90 degrees behind the inversion." ), ), Run( "peak", ACSweep(variation="lin", points=2401, start="12k", stop="13.2k"), drive={"e_in": "DC 0 AC 1"}, measure={ "f_peak": "when vi({e_out.1})=0", "gain_peak": "max vm({e_out.1})", "gain_peak_db": "max vdb({e_out.1})", }, claims=[ Claim("f_peak", "f_peak", within=0.001, unit="Hz", note=( "Where the output's imaginary part changes sign, which for " "a resonance this narrow is the top of the peak." )), ], units={"gain_peak_db": "dB"}, note=( "The handbook's Figure 43 point. The rising gain meets the 10 MHz " "op amp's roll-off at sqrt(GBW f_unity) and the loop is left with " "almost no damping, so a gain the page calls 2 pi f R_O C_I (793 " "there) peaks hundreds of times higher. The height is the op amp " "model's, and is reported, not claimed." ), ), ],)The files it writes
Section titled “The files it writes”The parts, then the nets and the pads on them.
C1 0.1 uF -GND1 Ground -R1 100 kOhm -TP1 Terminal -TP2 Terminal -TP3 Terminal -U1 OpAmp -Net-(C1-Pad1) C1.1 TP2.1Net-(C1-Pad2) C1.2 R1.1 U1.IN-Net-(GND1-Pad1) GND1.1 TP1.1 U1.IN+Net-(R1-Pad2) R1.2 TP3.1 U1.OUTEvery check that ran, and every one left undecided.
3 checks, 0 failed, 0 undecidedWhat the elaborated graph contains, by entity kind.
1 block 7 component 14 connection 3 constraint 1 decision 1 evidence 3 interface 11 pin 11 port 52 totalsnapshot sha256:ab6b1416c3168398c7c25765b6c8e7be918b89e4c7a38316b7da9eb2ee8ba8a7All of it, including the KiCad netlist, is in
examples/ti_opamp_handbook/differentiators/differentiator/out/. Rebuild it with:
fang build examples/ti_opamp_handbook/differentiators/differentiator/differentiator.py