Skip to content
copperhead.sh
Get started

Examples / TI op amp handbook / Additional circuits

Precision rectifier

SBOA092B page 88, Precision Rectifier: an inverting amplifier with RI (2 kΩ) in and two feedback paths, each an RO (10 kΩ) behind a diode. EO is taken from the upper path, between its RO and its diode.

E_O peak = -(R_O / R_I) E_I peak = -5 E_I peak
the schematic, drawn by copperhead from the circuit's netlist
The schematic, drawn by copperhead from the circuit's netlist

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/precision_rectifier.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, fang's own projection
The interconnect view, fang's own projection

The interconnect view is fang’s own projection. It names the parts as the program does, so it reads against the code below.

The diodes are the circuit, so the program records its reading of them (reading): both point up the page, the upper from the op amp output to E_O, the lower from the lower R_O into the output. A positive E_I drives the output low, the lower path closes the loop, and E_O rests on the virtual ground at zero. A negative E_I closes the loop through the upper path and E_O = -5 E_I: the positive hump the page sketches. The claim is a_v = -5, held to the parts by

require(equals(self.a_v, negative(over(self.r_out.resistance, self.r_in.resistance))))

out/simulation.txt, from the decks under out/spice/:

RunMeasuredClaimed
transfer, E_O / E_I at E_I = -1 V-5-5 (a_v), holds
transfer, E_O at E_I = +1 V-19.7 µV0 ± 1 mV, holds
half_wave, peak of E_O, 1 V sine in5 V5 V, holds
half_wave, E_O at the positive peak of E_I-19.8 µV0 ± 1 mV, holds
half_wave, lowest E_O-8.8 mVnot a claim

Neither half shows a diode drop: both diodes sit inside the loop. The one excursion below zero is a spike of a few microseconds at the zero crossing going positive, while the op amp output swings across two diode drops and the upper diode recovers.

Terminal window
fang check examples/ti_opamp_handbook/additional/precision_rectifier/precision_rectifier.py
python examples/regenerate.py ti_opamp_handbook/additional/precision_rectifier # needs ngspice
examples/ti_opamp_handbook/additional/precision_rectifier/precision_rectifier.py
"""The precision rectifier, SBOA092B page 88 (top).
Show 18 more lines
E_O peak = -(R_O / R_I) E_I peak = -5 E_I peak
An inverting amplifier with R_I 2 kOhm in and two feedback paths, each an
R_O of 10 kOhm behind a diode, so the op amp closes its loop through one path
on each half of the input. The output terminal is taken from the upper path
only, between its R_O and its diode.
The figure is small and the diodes are the whole circuit, so the program
records its reading as a decision (`reading`): the upper diode points from the
op amp output up to E_O, the lower one from the lower R_O up to the op amp
output. Read that way, a positive E_I drives the output low, the lower diode
closes the loop, and E_O sits on the virtual ground through its R_O at zero.
A negative E_I drives the output high, the upper diode closes the loop through
the upper R_O, and E_O = -5 E_I, a positive hump five times the negative half
of the input. That is the half-wave the page sketches, and the diode drops sit
inside the loop, so they do not show at E_O.
"""
import sys
from 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, require
from fang.parts import Resistor
from fang.rationale import Chooses, Cites
from fang.simulation import DCSweep, Transient
from handbook import (
Bench,
Claim,
Ground,
OpAmp,
Run,
SignalDiode,
Terminal,
equals,
negative,
over,
ratio,
)
class PrecisionRectifier(System):
"""E_I through R_I into the summing point, two diode-steered R_O paths back."""
figure = Cites(
"E_O peak = -(R_O/R_I) E_I peak = -5 E_I peak. Half wave with "
"amplification if desired. Placing rectifiers in feedback loop "
"decreases non-linearity to very small value.",
document="SBOA092B, Handbook of Operational Amplifier Applications",
locator="page 88, Precision Rectifier",
)
reading = Chooses(
"Which way do the two diodes point?",
selected=(
"both point up the page: the upper from the op amp output to the E_O "
"node, the lower from the lower R_O to the op amp output"
),
alternatives=[
{
"reading": "both reversed",
"reason": (
"the triangles point up with the bar above them; reversed, E_O "
"would be a negative hump for a positive E_I, not the positive "
"hump the page sketches"
),
},
],
rationale=(
"under this reading only one path conducts at a time, which is what "
"makes the other half of the output zero",
"E_O is a positive hump, -5 times the negative half of E_I, as the "
"sketch under the figure draws it",
),
)
bench = Chooses(
"What drives E_I?",
selected="a DC sweep from -2 V to 2 V, and a 1 V peak, 1 kHz sine",
alternatives=[
{
"option": "a 2 V peak sine",
"reason": "5 x 2 V is 10 V, near enough the swing to muddy the peak",
},
],
rationale=(
"the sweep gives the two slopes exactly; the sine shows the half-wave",
),
)
a_v = Parameter(
"1",
default=-5 * ratio,
description="E_O / E_I on the half that conducts (E_I negative)",
)
e_in = Terminal()
e_out = Terminal()
r_in = Resistor(resistance=2 * kOhm)
r_out = Resistor(resistance=10 * kOhm)
r_out_lower = Resistor(resistance=10 * kOhm)
d_out = SignalDiode()
d_clamp = SignalDiode()
amp = OpAmp()
ground = Ground()
def architecture(self):
self.e_in.probe >> self.r_in.p1
self.r_in.p2 >> self.amp.inverting.signal
self.amp.non_inverting.signal >> self.ground.node
# The upper path: R_O to the E_O node, the diode up to it from the output.
self.amp.inverting.signal >> self.r_out.p1
self.r_out.p2 >> self.e_out.probe
self.d_out.p2 >> self.e_out.probe
self.d_out.p1 >> self.amp.output.signal
# The lower path: R_O, and the diode up from it into the output.
self.amp.inverting.signal >> self.r_out_lower.p1
self.r_out_lower.p2 >> self.d_clamp.p1
self.d_clamp.p2 >> self.amp.output.signal
def constraints(self):
require(equals(self.a_v, negative(over(self.r_out.resistance, self.r_in.resistance))))
BENCH = Bench(
page=88,
title="Precision Rectifier",
runs=[
Run(
"transfer",
DCSweep(source="VDRIVE_e_in", start="-2", stop="2", step="0.01"),
drive={"e_in": "DC 0"},
measure={
"e_at_minus_1": "find v({e_out.1}) at=-1",
"e_at_plus_1": "find v({e_out.1}) at=1",
"gain_negative": "e_at_minus_1 / -1",
},
claims=[
Claim("gain_negative", "a_v", within=0.001),
Claim("e_at_plus_1", 0, within=1e-3, absolute=True, unit="V",
note="the other half: the upper diode is off and E_O rests "
"on the virtual ground through R_O"),
],
units={"e_at_minus_1": "V"},
),
Run(
"half_wave",
Transient(stop="3m", step="1u"),
drive={"e_in": "SIN(0 1 1k)"},
measure={
"e_peak": "max v({e_out.1}) from=1m to=3m",
"e_off": "find v({e_out.1}) at=2.25m",
"e_glitch": "min v({e_out.1}) from=1m to=3m",
},
claims=[
Claim("e_peak", 5, within=0.002, unit="V",
note="-5 times the -1 V peak of E_I"),
Claim("e_off", 0, within=1e-3, absolute=True, unit="V",
note="at the positive peak of E_I: E_O stays at zero, "
"without a diode drop"),
],
units={"e_glitch": "V"},
note=(
"E_I is a 1 V peak, 1 kHz sine; the last two cycles are measured. "
"The one excursion below zero is a spike of a few microseconds as "
"E_I crosses zero going positive, while the op amp output swings "
"across the two diode drops and the upper diode recovers. It is "
"reported, not claimed."
),
),
],
)

The parts, then the nets and the pads on them.

out/netlist.txt
D1 SignalDiode -
D2 SignalDiode -
GND1 Ground -
R1 2 kOhm -
R2 10 kOhm -
R3 10 kOhm -
TP1 Terminal -
TP2 Terminal -
U1 OpAmp -
Net-(D1-PadA) D1.A R3.2
Net-(D1-PadK) D1.K D2.A U1.OUT
Net-(D2-PadK) D2.K R2.2 TP2.1
Net-(GND1-Pad1) GND1.1 U1.IN+
Net-(R1-Pad1) R1.1 TP1.1
Net-(R1-Pad2) R1.2 R2.1 R3.1 U1.IN-

Every check that ran, and every one left undecided.

out/checks.txt
1 checks, 0 failed, 0 undecided

What the elaborated graph contains, by entity kind.

out/graph.txt
1 block
9 component
20 connection
1 constraint
2 decision
1 evidence
3 interface
16 pin
16 port
69 total
snapshot sha256:7216bdcc61dcb01a3b955775df96f902c470fb2497942ba9470adac4db7402ee

All of it, including the KiCad netlist, is in examples/ti_opamp_handbook/additional/precision_rectifier/out/. Rebuild it with:

Terminal window
fang build examples/ti_opamp_handbook/additional/precision_rectifier/precision_rectifier.py