Skip to content
copperhead.sh
Get started

Examples / TI op amp handbook / Additional circuits

Full wave rectifier

SBOA092B page 89, Full Wave Rectifier: a precision half-wave rectifier (R4, R3, R5, all 1 kΩ, and two diodes) followed by a summer that adds EI through R1 (2 kΩ) and the half-wave through R2 (1 kΩ) into RO (2 kΩ). The page prints no formula, only "Precision absolute value circuit."

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/full_wave_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 half-wave counts twice as much as E_I at the summer, which is what turns a half-wave into an absolute value. Its sign depends on the diodes. As drawn (reading), both point up the page, so a negative E_I gives a half-wave of +|E_I| and a positive E_I gives zero, and

E_O = -E_I for E_I > 0
E_O = -E_I - 2|E_I| = E_I for E_I < 0

which is E_O = -|E_I|. Two parameters hold the two slopes to the parts: a_positive = -R_O/R_1 = -1 and a_negative = -R_O/R_1 + R_O R_3 / (R_2 R_4) = 1.

out/simulation.txt:

RunMeasuredClaimed
transfer, slope for E_I > 0-1-1 (a_positive), holds
transfer, slope for E_I < 011 (a_negative), holds
transfer, E_O at E_I = +10 mV-9.998 mV-10 mV ± 0.1 mV, holds
transfer, E_O at E_I = -10 mV-9.993 mV-10 mV ± 0.1 mV, holds
sine, lowest E_O, 1 V peak in-1 V-1 V, holds
sine, average E_O-636.6 mV-2/π V, holds

Ten millivolts in comes out within 7 µV of ten millivolts: no diode drop shows, which is the point of the circuit against the simple absolute value one.

Not off, but not what a reader expects: the drawn diodes give -|E_I|, not +|E_I|. Turned round, both give the positive absolute value. The program keeps the figure and says which sign it gives.

Terminal window
fang check examples/ti_opamp_handbook/additional/full_wave_rectifier/full_wave_rectifier.py
python examples/regenerate.py ti_opamp_handbook/additional/full_wave_rectifier # needs ngspice
examples/ti_opamp_handbook/additional/full_wave_rectifier/full_wave_rectifier.py
"""The full wave rectifier, SBOA092B page 89 (top): a precision absolute value.
Show 20 more lines
E_O = -(R_O/R_1) E_I - (R_O/R_2) E_H, E_H = half-wave of E_I
The page prints no formula, only "Precision absolute value circuit." The first
stage is a precision half-wave rectifier: E_I through R_4 into the summing
point, R_3 and R_5 back from the output through a diode each, and E_H taken
between R_3 and its diode. The second stage sums E_I through R_1 (2 kOhm) and
E_H through R_2 (1 kOhm) into R_O (2 kOhm), so E_H counts twice as much as E_I.
What the program had to decide is the direction of the diodes, and so the sign
of the answer. As drawn (`reading`), both diodes point up the page: the upper
from the first op amp's output to E_H, the lower from R_5 to that output. A
negative E_I then makes E_H = +|E_I|, a positive E_I leaves E_H at zero, and
E_O = -E_I for E_I > 0
E_O = -E_I - 2|E_I| = E_I for E_I < 0
which is E_O = -|E_I|: an absolute value, inverted. Turn both diodes round and
it is +|E_I|. The program keeps the drawn diodes and says what they give.
"""
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,
product,
ratio,
total,
)
class FullWaveRectifier(System):
"""A precision half-wave, then a summer that adds it twice to E_I."""
figure = Cites(
"Precision absolute value circuit.",
document="SBOA092B, Handbook of Operational Amplifier Applications",
locator="page 89, Full Wave Rectifier",
)
reading = Chooses(
"Which way do the two diodes of the first stage point?",
selected=(
"both up the page, as drawn: the upper from the first op amp's output "
"to the R_3/R_2 junction, the lower from R_5 to that output, which "
"gives E_O = -|E_I|"
),
alternatives=[
{
"reading": "both reversed, the textbook circuit",
"reason": (
"that gives +|E_I|, but the triangles on the page point up with "
"the bar above them; the program does not redraw the figure to "
"fit a sign the page never states"
),
},
],
rationale=(
"the page claims an absolute value and no sign; -|E_I| is one",
"the precision, which is the page's point, is the same either way",
),
)
a_positive = Parameter(
"1",
default=-1 * ratio,
description="E_O / E_I for E_I > 0: the half-wave is zero, only R_1 counts",
)
a_negative = Parameter(
"1",
default=1 * ratio,
description="E_O / E_I for E_I < 0: R_1's -1 plus twice the half-wave's -1",
)
e_in = Terminal()
e_out = Terminal()
r_4 = Resistor(resistance=1 * kOhm)
r_3 = Resistor(resistance=1 * kOhm)
r_5 = Resistor(resistance=1 * kOhm)
d_up = SignalDiode()
d_down = SignalDiode()
amp_1 = OpAmp()
r_1 = Resistor(resistance=2 * kOhm)
r_2 = Resistor(resistance=1 * kOhm)
r_o = Resistor(resistance=2 * kOhm)
amp_2 = OpAmp()
ground = Ground()
def architecture(self):
# The half-wave stage.
self.e_in.probe >> self.r_4.p1
self.r_4.p2 >> self.amp_1.inverting.signal
self.amp_1.non_inverting.signal >> self.ground.node
self.amp_1.inverting.signal >> self.r_3.p1
self.r_3.p2 >> self.d_up.p2
self.d_up.p1 >> self.amp_1.output.signal
self.amp_1.inverting.signal >> self.r_5.p1
self.r_5.p2 >> self.d_down.p1
self.d_down.p2 >> self.amp_1.output.signal
# The summer: E_I through R_1, the half-wave through R_2.
self.e_in.probe >> self.r_1.p1
self.r_1.p2 >> self.amp_2.inverting.signal
self.r_3.p2 >> self.r_2.p1
self.r_2.p2 >> self.amp_2.inverting.signal
self.amp_2.inverting.signal >> self.r_o.p1
self.r_o.p2 >> self.amp_2.output.signal
self.amp_2.output.signal >> self.e_out.probe
self.amp_2.non_inverting.signal >> self.ground.node
def constraints(self):
through_r1 = negative(over(self.r_o.resistance, self.r_1.resistance))
require(equals(self.a_positive, through_r1))
# For E_I < 0 the half-wave is -(R_3/R_4) E_I, and it reaches the output
# through R_2 with a gain of -R_O/R_2.
require(
equals(
self.a_negative,
total(
through_r1,
over(
product(self.r_o.resistance, self.r_3.resistance),
product(self.r_2.resistance, self.r_4.resistance),
),
),
)
)
BENCH = Bench(
page=89,
title="Full Wave Rectifier",
runs=[
Run(
"transfer",
DCSweep(source="VDRIVE_e_in", start="-2", stop="2", step="1m"),
drive={"e_in": "DC 0"},
measure={
"e_plus_1": "find v({e_out.1}) at=1",
"e_minus_1": "find v({e_out.1}) at=-1",
"gain_positive": "e_plus_1 / 1",
"gain_negative": "e_minus_1 / -1",
"e_plus_10m": "find v({e_out.1}) at=10m",
"e_minus_10m": "find v({e_out.1}) at=-10m",
},
claims=[
Claim("gain_positive", "a_positive", within=0.001),
Claim("gain_negative", "a_negative", within=0.001),
Claim("e_plus_10m", -0.01, within=1e-4, absolute=True, unit="V",
note="10 mV in, -10 mV out, to 0.1 mV: no diode drop shows"),
Claim("e_minus_10m", -0.01, within=1e-4, absolute=True, unit="V"),
],
units={"e_plus_1": "V", "e_minus_1": "V"},
),
Run(
"sine",
Transient(stop="3m", step="1u"),
drive={"e_in": "SIN(0 1 1k)"},
measure={
"e_min": "min v({e_out.1}) from=1m to=3m",
"e_average": "avg v({e_out.1}) from=1m to=3m",
},
claims=[
Claim("e_min", -1, within=0.002, unit="V",
note="both peaks of the 1 V sine come out at -1 V"),
Claim("e_average", -0.63662, within=0.002, unit="V",
note="-2/pi of the peak, the mean of a full-wave rectified sine; "
"the 0.2% allows for the few microseconds at each zero "
"crossing while the first op amp swings across its diodes"),
],
note="E_I is a 1 V peak, 1 kHz sine; the last two cycles are measured.",
),
],
)

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

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

Every check that ran, and every one left undecided.

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

What the elaborated graph contains, by entity kind.

out/graph.txt
1 block
13 component
34 connection
2 constraint
1 decision
1 evidence
3 interface
25 pin
25 port
105 total
snapshot sha256:ac52e06d6f5c72ac087377e21280cb9cc8ea37ad9b1f26e2756fa15eec1db4f2

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

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