Skip to content
copperhead.sh
Get started

Examples / TI op amp handbook / Current output

Meter amplifier

SBOA092B page 80, Meter Amplifier, "fully developed average reading meter": a follower whose feedback current runs through a bridge of two diodes and two 10 µF capacitors, with the meter across it.

Meter reading = 0.9 E_I / (R4 + R5) (E_I rms)
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/meter_amplifier.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.

E_I comes in through a 1 µF capacitor onto the + input (220 kΩ to ground). The - input follows it, so E_I / (R4 + R5) flows through R4 (47 Ω) and the R5 rheostat to ground, and the output supplies it through the bridge. The output is the bridge’s top corner; one diode leads from it to the right corner and another from the left corner up to it; the capacitors join both corners to the bottom corner, which is the - input. R0 (220 kΩ) closes the loop at d.c. The meter, with R8 (68 kΩ) across it, sits between the right and left corners through R7 and R6.

The positive half of the current leaves through the right diode, the negative half comes back through the left one, and the capacitors pass both. What reaches the meter is the d.c. circulating through the two diodes in series, which is one diode’s average: half a cycle of the current. For a sine that is 0.45 E_I rms / (R4 + R5). The constraint says so:

require(equals(self.reading_per_volt, over(0.45, total(self.r4.resistance, in_circuit))))

Decisions recorded: names (the figure labels two capacitors C1), movement (a 100 Ω, 1 mA meter) and calibration (R5 at 53 Ω, so R4 + R5 = 100 Ω).

out/simulation.txt, 100 mV rms at 1 kHz:

RunMeter average / E_I rmsClaimed
sine, R4 + R5 = 100 Ω4.479 mA/V4.5 mA/V (reading_per_volt) ±1%, holds
calibration, R4 + R5 = 147 Ω3.043 mA/V3.061 mA/V ±1%, holds

The 0.5% shortfall is R0 and R8, which take a little of the current.

The page prints 0.9 E_I / (R4 + R5), the full-wave average of a sine over its rms. The drawn bridge has two diodes and two capacitors, not four diodes, and its meter carries half that: 0.45 E_I / (R4 + R5), which is what ngspice measures. A four-diode bridge would read 0.9.

Terminal window
fang check examples/ti_opamp_handbook/current_output/meter_amplifier/meter_amplifier.py
python examples/regenerate.py ti_opamp_handbook/current_output/meter_amplifier # needs ngspice
examples/ti_opamp_handbook/current_output/meter_amplifier/meter_amplifier.py
"""The meter amplifier, SBOA092B page 80: a fully developed average-reading meter.
Show 25 more lines
Meter reading = 0.9 E_I / (R4 + R5) (E_I rms)
E_I comes in through C1 (1 uF) onto the + input, with R1 (220 kOhm) to
ground. The - input follows it, so the current through R4 and the R5
rheostat to ground is E_I / (R4 + R5), and the output has to supply it
through the bridge: the output drives the top corner, a diode leads from
there to the right corner and another from the left corner up to it, the two
10 uF capacitors join the left and right corners to the bottom corner, and
the bottom corner is the - input. R0 (220 kOhm) closes the loop at d.c.,
which the capacitors cannot. The meter, with R8 (68 kOhm) across it, sits
between the right and left corners through R7 and R6 (100 Ohm each).
The positive half of the feedback current goes out through the right diode
and the negative half back through the left one, and the capacitors pass
both. The meter carries the d.c. that circulates right corner, meter, left
corner, left diode, right diode: what one diode carries on average, the
average of one half-cycle of the current. For a sine that is 0.45 E_I rms /
(R4 + R5), half of what the page prints. The program claims what the drawn
circuit does and records the handbook's 0.9 in the citation.
The figure gives the meter no resistance and R5 no setting, and it names two
different capacitors C1; `movement`, `calibration` and `names` record what
was taken.
"""
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 decimal import Decimal
from fang.lang import Ohm, Parameter, System, UnitLiteral, kOhm, require, uF
from fang.parts import Capacitor, Resistor
from fang.rationale import Chooses, Cites
from fang.simulation import Transient
from handbook import (
Bench,
Claim,
Ground,
Meter,
OpAmp,
Potentiometer,
Run,
SignalDiode,
Terminal,
equals,
minus,
over,
product,
ratio,
total,
)
#: The meter's average current per volt rms in.
mA_per_V = UnitLiteral("mA/V")
class MeterAmplifier(System):
"""A follower whose feedback current runs through a two-diode, two-capacitor bridge."""
figure = Cites(
"Meter reading = 0.9 E_I / (R4 + R5) (rms). R5: gain control "
"(calibration). Fully developed average reading meter.",
document="SBOA092B, Handbook of Operational Amplifier Applications",
locator="page 80, Meter Amplifier",
)
names = Chooses(
"The figure labels two capacitors C1, one 1 uF and one 10 uF. Which is which?",
selected=(
"the 1 uF at the input is `c_in`; the two 10 uF in the bridge are "
"`c_left` (drawn C1) and `c_right` (drawn C2)"
),
alternatives=[
{
"reading": "the input capacitor is 10 uF",
"reason": "the value printed beside the input capacitor is 1 uF; "
"only its name repeats",
},
],
rationale=("the values are unambiguous where they are drawn; only the names collide",),
)
movement = Chooses(
"What is the meter's resistance?",
selected="100 Ohm, a 1 mA movement",
alternatives=[
{
"option": "a 50 uA, 2 kOhm movement",
"reason": "R8 (68 kOhm) across it would then take 3% of the "
"current the meter should, where across 100 Ohm it takes 0.15%",
},
],
rationale=(
"the figure draws a meter and gives it no rating",
"a 100 mV rms input into 100 Ohm puts the average near 0.45 mA, "
"half of a 1 mA scale",
),
)
calibration = Chooses(
"Where is R5 set?",
selected="0.47 of its travel, 53 Ohm in circuit, so R4 + R5 is 100 Ohm",
alternatives=[
{
"option": "the whole 100 Ohm",
"reason": "used as a second run, to show the reading follows "
"R4 + R5 as the calibration control moves",
},
],
rationale=(
"R5 is drawn as a rheostat: its wiper is tied to the end at R4, so "
"what is in circuit is the part from the wiper to ground",
"a round 100 Ohm makes the reading a round number",
),
)
reading_per_volt = Parameter(
"mA/V",
default=Decimal("4.5") * mA_per_V,
description="average meter current per volt rms of E_I: 0.45 / (R4 + R5)",
)
e_in = Terminal()
c_in = Capacitor(capacitance=1 * uF)
r1 = Resistor(resistance=220 * kOhm)
amp = OpAmp()
ground = Ground()
r0 = Resistor(resistance=220 * kOhm)
r4 = Resistor(resistance=47 * Ohm)
r5 = Potentiometer(resistance=100 * Ohm, setting=Decimal("0.47") * ratio)
d_left = SignalDiode()
d_right = SignalDiode()
c_left = Capacitor(capacitance=10 * uF)
c_right = Capacitor(capacitance=10 * uF)
r6 = Resistor(resistance=100 * Ohm)
r7 = Resistor(resistance=100 * Ohm)
r8 = Resistor(resistance=68 * kOhm)
meter = Meter(resistance=100 * Ohm)
def architecture(self):
# The input: C1 onto the + input, R1 from there to ground.
self.e_in.probe >> self.c_in.p1
self.c_in.p2 >> self.amp.non_inverting.signal
self.amp.non_inverting.signal >> self.r1.p1
self.r1.p2 >> self.ground.node
# The - input: R0 from the output, R4 and the R5 rheostat to ground.
self.amp.output.signal >> self.r0.p1
self.r0.p2 >> self.amp.inverting.signal
self.amp.inverting.signal >> self.r4.p1
self.r4.p2 >> self.r5.end_a
self.r5.end_a >> self.r5.wiper
self.r5.end_b >> self.ground.node
# The bridge. The output is the top corner; the left diode points up
# into it and the right diode down out of it.
self.amp.output.signal >> self.d_left.p2
self.d_left.p2 >> self.d_right.p1
self.d_left.p1 >> self.c_left.p1
self.d_right.p2 >> self.c_right.p1
# The bottom corner is the - input.
self.c_left.p2 >> self.c_right.p2
self.c_right.p2 >> self.amp.inverting.signal
# The meter, R8 across it, R6 and R7 to the left and right corners.
self.d_left.p1 >> self.r6.p1
self.d_right.p2 >> self.r7.p1
self.r6.p2 >> self.r8.p1
self.r7.p2 >> self.r8.p2
self.r7.p2 >> self.meter.p1
self.meter.p2 >> self.r6.p2
def constraints(self):
in_circuit = product(self.r5.resistance, minus(1 * ratio, self.r5.setting))
require(
equals(
self.reading_per_volt,
over(Decimal("0.45") * ratio, total(self.r4.resistance, in_circuit)),
)
)
#: The meter's average current over the input's rms, over whole periods.
MEASURE = {
"i_avg": "avg i(vm1_sense) from=100m to=200m",
"e_rms": "rms v({e_in.1}) from=100m to=200m",
"reading_per_volt": "i_avg / e_rms",
}
HALF = (
"The handbook prints 0.9 E_I / (R4 + R5), the full-wave average of a sine "
"over its rms. The drawn bridge has two diodes and two capacitors, and its "
"meter carries one diode's average: 0.45. The 1% allowance covers R0 and R8, "
"which between them take 0.5% of the current."
)
BENCH = Bench(
page=80,
title="Meter Amplifier",
runs=[
Run(
"sine",
Transient(stop="200m", step="2u"),
drive={"e_in": "SIN(0 0.141421356 1k)"},
measure=MEASURE,
claims=[Claim("reading_per_volt", "reading_per_volt", within=0.01,
unit="A/V", note=HALF)],
units={"i_avg": "A", "e_rms": "V"},
note="100 mV rms at 1 kHz. The first 100 ms let the 10 uF bridge "
"capacitors reach their level; the next 100 are averaged.",
),
Run(
"calibration",
Transient(stop="200m", step="2u"),
drive={"e_in": "SIN(0 0.141421356 1k)"},
settings={"r5": {"setting": 0}},
measure=MEASURE,
claims=[Claim("reading_per_volt", 0.45 / 147, within=0.01, unit="A/V",
note="0.45 / (47 + 100) Ohm, with the whole of R5 in circuit")],
units={"i_avg": "A", "e_rms": "V"},
note="R5 turned to its full 100 Ohm: the reading follows R4 + R5.",
),
],
)

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

out/netlist.txt
C1 1 uF -
C2 10 uF -
C3 10 uF -
D1 SignalDiode -
D2 SignalDiode -
GND1 Ground -
M1 Meter -
R1 220 kOhm -
R2 220 kOhm -
R3 47 Ohm -
R4 100 Ohm -
R5 100 Ohm -
R6 68 kOhm -
RV1 Potentiometer -
TP1 Terminal -
U1 OpAmp -
Net-(C1-Pad1) C1.1 TP1.1
Net-(C1-Pad2) C1.2 R2.1 U1.IN+
Net-(C2-Pad1) C2.1 D1.A R4.1
Net-(C2-Pad2) C2.2 C3.2 R1.2 R3.1 U1.IN-
Show 6 more lines
Net-(C3-Pad1) C3.1 D2.K R5.1
Net-(D1-PadK) D1.K D2.A R1.1 U1.OUT
Net-(GND1-Pad1) GND1.1 R2.2 RV1.3
Net-(M1-Pad1) M1.1 R5.2 R6.2
Net-(M1-Pad2) M1.2 R4.2 R6.1
Net-(R3-Pad2) R3.2 RV1.1 RV1.2

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
16 component
44 connection
1 constraint
3 decision
1 evidence
3 interface
32 pin
32 port
133 total
snapshot sha256:e7303b8c6d06066da08676b15d67bccb4c1cac79da6c5cb799190d5c54c13b36

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

Terminal window
fang build examples/ti_opamp_handbook/current_output/meter_amplifier/meter_amplifier.py