Examples / TI op amp handbook / Additional circuits
Peak follower
SBOA092B page 87, Peak Follower: EI through a diode onto a 1 µF capacitor, a switch across the capacitor to clear it, and a voltage follower reading it.
E_O = E_I maximumThe 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/peak_follower.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 diode lets the capacitor charge to the highest E_I it has seen and stops
it discharging. It is outside the loop, so its forward drop at the moment
charging stops is subtracted from the peak. The program claims
e_held = e_peak - v_drop, a 4 V peak less 0.5 V, and records the drop as a
decision (drop) because the figure names no diode and the drop depends on
how the input approaches its peak. The claims are held to 0.1 V.
Where the handbook is off
Section titled “Where the handbook is off”E_O is not E_I’s maximum: it is a diode drop below it, 0.44 V to 0.48 V here with a 1N4148 model.
What the simulation found
Section titled “What the simulation found”| Run | Measured | Claimed |
|---|---|---|
pulse, 3 ms after a 4 V, 1 ms pulse | 3.556 V | 3.5 V (e_held) ± 0.1 V, holds |
pulse, 1.9 ms later | 3.556 V | 3.5 V (e_held) ± 0.1 V, holds |
pulse, after the reset closes | 0 V | 0 V ± 1 mV, holds |
sine, after ten cycles of 4 V at 1 kHz | 3.517 V | 3.5 V (e_held) ± 0.1 V, holds |
The pulse leaves 0.44 V of drop, the diode’s voltage at the 45 µA the capacitor still takes when the pulse ends. The sine tops the capacitor up in short bursts at each crest and leaves 0.48 V. The model’s capacitor does not leak, so the held value does not droop; the page’s advice to use a low-leakage capacitor is about the part this does not model.
Running it
Section titled “Running it”fang check examples/ti_opamp_handbook/additional/peak_follower/peak_follower.pypython examples/regenerate.py ti_opamp_handbook/additional/peak_follower # needs ngspiceThe whole program
Section titled “The whole program”"""The peak follower, SBOA092B page 87.Show 17 more lines
E_O = E_I maximum
E_I charges a 1 uF capacitor through a diode, and a voltage follower readsthe capacitor without loading it. The diode lets the capacitor charge up tothe highest E_I it has seen and stops it discharging when E_I falls, so thecapacitor, and the output, remember the peak. A switch across the capacitorclears it.
The page says the output is E_I's maximum. It is one diode drop below it: thediode is outside the loop, and its forward voltage at the current that lastcharged the capacitor is subtracted from the peak and never given back. Thatdrop is not a fixed number. It is the voltage at which the diode's currenthas fallen to what the capacitor still asks for, so a slow input leaves lessof it than a fast one. The program says so in `drop`, and the bench measuresit for a 1 ms pulse and for a sine."""
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 fang.lang import Parameter, System, V, require, uFfrom fang.parts import Capacitorfrom fang.rationale import Chooses, Citesfrom fang.simulation import Transient
from handbook import ( Bench, Claim, Ground, OpAmp, Run, SignalDiode, Switch, Terminal, equals, minus,)
class PeakFollower(System): """E_I through a diode onto a capacitor, a follower reading it, a switch to clear it."""
figure = Cites( "Peak value memory. Use low leakage capacitor. E_O = E_I maximum. " "Common mode input voltage must be observed", document="SBOA092B, Handbook of Operational Amplifier Applications", locator="page 87, Peak Follower", )
drop = Chooses( "What forward drop does the held value lose to the diode?", selected="0.5 V, the 1N4148's drop at the tens of microamps still charging the capacitor when the peak ends, claimed to 0.1 V", alternatives=[ { "option": "none, as the page's E_O = E_I maximum says", "reason": "the diode is outside the loop; its drop is in the held value, and the bench measures it", }, { "option": "0.7 V, the textbook silicon drop", "reason": "that is the drop at milliamps; the charging current has died to tens of microamps by the time the peak passes", }, ], rationale=( "the handbook draws a plain diode and names no part; the bench uses the 1N4148 it names on page 47", "the drop depends on how the input approaches its peak, so the claim is a band, not a number", ), )
e_peak = Parameter("V", default=4 * V, description="the peak the bench applies") v_drop = Parameter("V", default=0.5 * V, description="the diode's forward drop when charging stops") e_held = Parameter("V", default=3.5 * V, description="what the output holds: the peak less the drop")
e_in = Terminal() e_out = Terminal() diode = SignalDiode() c_hold = Capacitor(capacitance=1 * uF) reset = Switch() amp = OpAmp() ground = Ground()
def architecture(self): self.e_in.probe >> self.diode.p1 self.diode.p2 >> self.c_hold.p1 self.c_hold.p1 >> self.reset.p1 self.c_hold.p1 >> self.amp.non_inverting.signal self.c_hold.p2 >> self.ground.node self.reset.p2 >> self.ground.node self.amp.inverting.signal >> self.amp.output.signal self.amp.output.signal >> self.e_out.probe
def constraints(self): require(equals(self.e_held, minus(self.e_peak, self.v_drop)))
BENCH = Bench( page=87, title="Peak Follower", runs=[ Run( "pulse", Transient(stop="10m", step="1u"), drive={"e_in": "PULSE(0 4 1m 1u 1u 1m 20m)"}, switches={"reset": "PWL(0 0 7m 0 7.001m 1)"}, measure={ "held": "find v({e_out.1}) at=5m", "held_later": "find v({e_out.1}) at=6.9m", "cleared": "find v({e_out.1}) at=9m", }, claims=[ Claim( "held", "e_held", within=0.1, absolute=True, unit="V", note=( "3 ms after a 4 V, 1 ms pulse: 0.44 V under the peak, the drop " "at the 45 uA the capacitor still took when the pulse ended. " "The page's E_O = E_I maximum would be 4 V" ), ), Claim( "held_later", "e_held", within=0.1, absolute=True, unit="V", note="still there 1.9 ms later: the model's capacitor does not leak", ), Claim( "cleared", 0, within=0.001, absolute=True, unit="V", note="the reset switch closed at 7 ms", ), ], note=( "E_I pulses to 4 V from 1 ms to 2 ms and falls back to 0. The " "reset switch closes at 7 ms." ), ), Run( "sine", Transient(stop="10m", step="1u"), drive={"e_in": "SIN(0 4 1k)"}, measure={ "held": "find v({e_out.1}) at=9.5m", "input_peak": "max v({e_in.1}) from=0 to=10m", }, claims=[ Claim( "held", "e_held", within=0.1, absolute=True, unit="V", note=( "after ten cycles of a 4 V, 1 kHz sine: 0.48 V under the peak. " "Each cycle tops the capacitor up in a short burst near the " "crest, which leaves a little more drop than the pulse did" ), ), ], units={"input_peak": "V"}, ), ],)The files it writes
Section titled “The files it writes”The parts, then the nets and the pads on them.
C1 1 uF -D1 SignalDiode -GND1 Ground -SW1 Switch -TP1 Terminal -TP2 Terminal -U1 OpAmp -Net-(C1-Pad1) C1.1 D1.K SW1.1 U1.IN+Net-(C1-Pad2) C1.2 GND1.1 SW1.2Net-(D1-PadA) D1.A TP1.1Net-(TP2-Pad1) TP2.1 U1.IN- U1.OUTEvery check that ran, and every one left undecided.
1 checks, 0 failed, 0 undecidedWhat the elaborated graph contains, by entity kind.
1 block 7 component 16 connection 1 constraint 1 decision 1 evidence 3 interface 12 pin 12 port 54 totalsnapshot sha256:c8d966792e147c15d7778c3fa26d5dd7bcc7f5f3a90213c0da7bb1e1e9353516All of it, including the KiCad netlist, is in
examples/ti_opamp_handbook/additional/peak_follower/out/. Rebuild it with:
fang build examples/ti_opamp_handbook/additional/peak_follower/peak_follower.py