Skip to content
copperhead.sh
Get started

Examples / TI op amp handbook / Integrators

Augmenting integrator

SBOA092B pages 59 and 60, Augmenting Integrator: EI through RI (10 kΩ) into the summing point, with RO (100 kΩ) in series with CO (10 µF) as the feedback. "Sums the input signal and its time integral."

E_O = -(R_O/R_I) E_I - 1/(C_O R_I) ∫ E_I dt
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/augmenting_integrator.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.

Two parameters, gain = -10 (-R_O/R_I) and rate = -10 /s (-1/(C_O R_I)), each held to the parts. The bench uses a 0.1 V step at 10 ms, starting from rest (bench). The proportional term shows up as a jump and the integral as the ramp after it, so one waveform carries both.

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

RunMeasuredClaimed
step, the jump (ramp extrapolated back to the step) over E_I-10-10 (gain), holds
step, the slope after it over E_I-10 /s-10 /s (rate), holds

The output is -1.1 V at 0.11 s and -2.0 V at 1.01 s: a jump to -1 V, then a fall of 1 V/s.

The handbook evaluates the formula as “-10 E_I - ∫ E_I dt”. The first term is right. The second is not: C_O R_I is 10 µF × 10 kΩ = 0.1 s, so the integral has a coefficient of 10, not 1. To get 1 it would need C_O = 100 µF. The program claims the rate the parts give, and the simulation measures that rate.

Terminal window
fang check examples/ti_opamp_handbook/integrators/augmenting_integrator/augmenting_integrator.py
python examples/regenerate.py ti_opamp_handbook/integrators/augmenting_integrator # needs ngspice
examples/ti_opamp_handbook/integrators/augmenting_integrator/augmenting_integrator.py
"""The augmenting integrator, SBOA092B pages 59 and 60.
Show 17 more lines
E_O = -(R_O/R_I) E_I - 1/(C_O R_I) integral E_I dt
R_I is 10 kOhm; the feedback is R_O (100 kOhm) in series with C_O (10 uF).
The current E_I/R_I flows through both, so the output is the drop across R_O,
-(R_O/R_I) E_I, plus the charge on C_O, -1/(C_O R_I) integral E_I dt: the input
and its integral, summed.
The handbook evaluates this as "-10 E_I - integral E_I dt". The first term is
right, R_O/R_I = 10. The second is not: C_O R_I is 10 uF times 10 kOhm, 0.1 s,
so the integral is multiplied by 10, not 1. The program holds the rate to the
parts, -10 per second, and the simulation agrees with it.
The bench applies a 0.1 V step at 10 ms to an integrator at rest, so both
terms can be read off one waveform: the jump is the proportional part, the
slope after it the integral.
"""
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, UnitLiteral, kOhm, require, uF
from fang.parts import Capacitor, Resistor
from fang.rationale import Calculates, Chooses, Cites
from fang.simulation import Transient
from handbook import (
Bench,
Claim,
Ground,
OpAmp,
Run,
Terminal,
equals,
negative,
over,
product,
ratio,
)
#: A rate: volts of output per second, for each volt of input.
per_second = UnitLiteral("1/s")
class AugmentingIntegrator(System):
"""E_I through R_I into the summing point; R_O and C_O in series back from the output."""
figure = Cites(
"E_O = -(R_O E_I)/R_I - 1/(C_O R_I) integral E_I dt = -10 E_I - integral E_I dt. "
"Sums the input signal and its time integral.",
document="SBOA092B, Handbook of Operational Amplifier Applications",
locator="pages 59-60, Augmenting Integrator",
)
integral_term = Calculates(
"1/(C_O R_I) = 1/(10 uF x 10 kOhm)",
inputs=("c_out", "r_in"),
result=(
"10 per second. The handbook's second line prints the integral with "
"a coefficient of 1, which would need C_O R_I = 1 s (100 uF, or R_I "
"= 100 kOhm, which would also change the first term to -1)"
),
)
bench = Chooses(
"What input shows both terms?",
selected="a 0.1 V step at 10 ms, from rest",
alternatives=[
{
"option": "a sine",
"reason": "the two terms add in quadrature and have to be separated again",
},
],
rationale=(
"a step makes the proportional term a jump and the integral a ramp, "
"each read directly",
),
)
gain = Parameter("1", default=-10 * ratio, description="-R_O/R_I, the proportional term")
rate = Parameter("1/s", default=-10 * per_second, description="-1/(C_O R_I), the integral term")
e_in = Terminal()
e_out = Terminal()
r_in = Resistor(resistance=10 * kOhm)
r_out = Resistor(resistance=100 * kOhm)
c_out = Capacitor(capacitance=10 * uF)
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.inverting.signal >> self.r_out.p1
self.r_out.p2 >> self.c_out.p1
self.c_out.p2 >> self.amp.output.signal
self.amp.output.signal >> self.e_out.probe
self.amp.non_inverting.signal >> self.ground.node
def constraints(self):
require(equals(self.gain, negative(over(self.r_out.resistance, self.r_in.resistance))))
require(
equals(
self.rate,
negative(over(1 * ratio, product(self.c_out.capacitance, self.r_in.resistance))),
)
)
BENCH = Bench(
page=60,
title="Augmenting Integrator",
runs=[
Run(
"step",
Transient(stop="1.01", step="1m"),
drive={"e_in": "PWL(0 0 10m 0 10.001m 0.1)"},
measure={
"e_early": "find v({e_out.1}) at=0.11",
"e_late": "find v({e_out.1}) at=1.01",
"rate_per_volt": "(e_late - e_early) / 0.9 / 0.1",
"gain_step": "(e_early - rate_per_volt * 0.1 * 0.1) / 0.1",
},
claims=[
Claim("gain_step", "gain", within=0.001,
note="the jump at the step: the ramp extrapolated back to 10 ms, over E_I"),
Claim("rate_per_volt", "rate", within=0.001, unit="/s",
note=(
"the handbook prints the integral term as -integral E_I dt, a "
"rate of -1/s; 1/(C_O R_I) = 1/(10 uF x 10 kOhm) is 10/s, and "
"that is what the circuit does"
)),
],
units={"e_early": "V", "e_late": "V"},
note=(
"E_I steps from 0 to 0.1 V at 10 ms. The output should jump to "
"-1 V and fall 1 V/s after it: -10 E_I - 10 integral E_I dt."
),
),
],
)

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

out/netlist.txt
C1 10 uF -
GND1 Ground -
R1 10 kOhm -
R2 100 kOhm -
TP1 Terminal -
TP2 Terminal -
U1 OpAmp -
Net-(C1-Pad1) C1.1 R2.2
Net-(C1-Pad2) C1.2 TP2.1 U1.OUT
Net-(GND1-Pad1) GND1.1 U1.IN+
Net-(R1-Pad1) R1.1 TP1.1
Net-(R1-Pad2) R1.2 R2.1 U1.IN-

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
1 calculation
7 component
14 connection
2 constraint
1 decision
1 evidence
3 interface
12 pin
12 port
54 total
snapshot sha256:772cb918c912ddea56586ad60ed8897da6e1ff6795cbd6983bb6f04bbec8eca5

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

Terminal window
fang build examples/ti_opamp_handbook/integrators/augmenting_integrator/augmenting_integrator.py