Examples / TI op amp handbook / Integrators
Regenerative integrator
SBOA092B page 57, the integrator page 56 introduces with "Regeneration may be used to increase open loop DC gain to infinity". There is no formula, only a four-step procedure for trimming the zero control R8 and the regeneration control R5.
The 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/regenerative_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 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 program’s reading of the drawing is recorded as a decision (reading):
- the integrator: E_I through R1 (100 kΩ) to the - input, C_O (1 µF) with a reset switch to the output;
- the zero control: R9 and R10 (10 kΩ) from the + and - terminals (read as ±15 V) to the ends of R8 (10 kΩ). The wiper feeds R6 (1 MΩ) to the - input and R7 (1 MΩ) to the + input;
- the regeneration: R3 (10 MΩ) from the output to a node X, R2 (100 kΩ) from X to the + input, and R4 (1 kΩ) plus the rheostat R5 (2 kΩ) from X to ground.
That is positive feedback of a fraction k ≈ (R4 + R5)/R3 × R7/(R2 + R7) of the output. With the input open, the - input sits at E_O (k - 1/A), and R6 leaks C_O’s charge away with a time constant near R6 C_O / (1/A - k). Regeneration makes that longer until k reaches 1/A. Past that point the output grows, which is the handbook’s step 3.
k runs from 0.9e-4 to 2.7e-4 over R5’s travel, so the network suits an op
amp with a gain of 4000 to 11000. With the bench’s default of 1e6 the output
would run away at every setting. The program gives the op amp a gain of 5000
(op_amp), which puts the balance at 60% of R5’s travel, near the centre the
procedure starts from. R8 is centred and R5 is at a quarter (settings).
The claims: rate = -10 /s, hold_open = A R6 C_O = 5000 s with no
regeneration, and hold_quarter = 15,710 s with R5 at a quarter.
What the simulation found
Section titled “What the simulation found”out/simulation.txt, from the decks under out/spice/.
The hold runs follow step 2: 1 V through a switch the bench adds takes the
output to about -5 V, the input is opened at 0.51 s, and the output is read
100 s apart.
| Run | Measured | Claimed |
|---|---|---|
rate, slope over E_I | -9.995 /s | -10 /s (rate) ± 0.1%, holds |
hold_without_regeneration, node X grounded by a card | 5015 s | 5000 s (hold_open) ± 3%, holds |
hold_with_regeneration, R5 at 25% | 15,350 s | 15,710 s (hold_quarter) ± 3%, holds |
too_much_regeneration, R5 at 100% | -14,530 s (grows) | not a claim |
Regeneration triples the hold. Turned too far, it makes the output grow instead of decay. The 3% tolerance covers what the estimate leaves out: the zero control’s 7.5 kΩ source resistance and R7’s path to the + input. The program does not claim an infinite hold. Getting one needs an exact balance, and that is not a number anyone can claim.
Running it
Section titled “Running it”fang check examples/ti_opamp_handbook/integrators/regenerative_integrator/regenerative_integrator.pypython examples/regenerate.py ti_opamp_handbook/integrators/regenerative_integrator # needs ngspiceThe whole program
Section titled “The whole program”"""The integrator with zero control and regeneration, SBOA092B page 57.Show 36 more lines
E_O = -1/(R1 C_O) integral E_I dt = -10 integral E_I dt, held without decay
The page follows "Regeneration may be used to increase open loop DC gain toinfinity" on page 56, and prints no formula, only a procedure. The drawing,read wire by wire (`reading`):
the integrator E_I, R1 100 kOhm, into the - input; C_O 1 uF from there to the output, with the reset switch across it zero control the + terminal through R9 10 kOhm and the - terminal through R10 10 kOhm to the two ends of R8 (10 kOhm); the wiper feeds R6 1 MOhm to the - input and R7 1 MOhm to the + input regeneration R3 10 MOhm from the output to a node X; R2 100 kOhm from X to the + input; R4 1 kOhm and the rheostat R5 (2 kOhm, wiper tied to its grounded end) from X to ground
So a fraction k of the output, about (R4 + R5)/R3 times R7/(R2 + R7), is fedback to the + input: positive feedback. An op amp with open-loop gain A holdsits - input at E_O (k - 1/A), and with the input open that voltage leakscharge off C_O through R6, so the output decays with a time constant nearR6 C_O / (1/A - k). Without regeneration it is A R6 C_O; regeneration shrinksthe denominator and the hold lengthens, until k passes 1/A and the outputgrows instead: the handbook's step 3.
The feedback fraction runs from 0.9e-4 to 2.7e-4 over R5's travel, so thenetwork is sized for an op amp of 70 to 80 dB, not the bench's default 120 dB(`op_amp`). The program gives the op amp an open-loop gain of 5000, for whichthe balance falls at 60% of R5's travel, near the centre the procedure startsfrom.
What the program claims: the integration rate, -10 V/s per volt; the holdtime constant with regeneration taken out, A R6 C_O; and the longer one withR5 at a quarter of its travel. It does not claim an infinite hold: that is aknife-edge setting, and the runs show the two sides of it instead."""
import sysfrom decimal import Decimalfrom 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 MOhm, Parameter, System, UnitLiteral, kOhm, require, s, uFfrom fang.parts import Capacitor, Resistorfrom fang.rationale import Calculates, Chooses, Citesfrom fang.simulation import Transient
from handbook import ( Bench, Claim, Ground, OpAmp, Potentiometer, Run, Switch, Terminal, equals, minus, negative, over, product, ratio, total, within,)
#: A rate: volts of output per second, for each volt of input.per_second = UnitLiteral("1/s")
#: The reset: closed at t = 0, open from 10 ms.RESET = "PWL(0 1 10m 1 10.001m 0)"#: The + and - terminals of the zero control, read as the +/-15 V rails.RAILS = {"rail_plus": "DC 15", "rail_minus": "DC -15"}
#: Step 2 of the procedure: apply an input, let the integrator run up, then#: open-circuit the input. A 1 V source reaches E_I through a switch the bench#: adds, closed until 0.51 s: 0.5 s at -10 V/s leaves the output near -5 V.#: HB_SWITCH is the reset switch's model, already in the deck.RUN_UP_AND_OPEN = [ "VSIG sig 0 DC 1", "SSIG sig {e_in.1} sig_ctl 0 HB_SWITCH", "VSIG_CTL sig_ctl 0 PWL(0 1 510m 1 510.1m 0)",]
#: Read the output 100 s apart once the input is open; the decay (or growth)#: is exponential, so its time constant is -(t2 - t1)/ln(E2/E1).HOLD_MEASURE = { "e_1s": "find v({e_out.1}) at=1", "e_101s": "find v({e_out.1}) at=101", "tau": "-100 / ln(e_101s / e_1s)",}
class RegenerativeIntegrator(System): """An integrator with a zero control on both inputs and positive feedback to the + input."""
figure = Cites( "Regeneration may be used to increase open loop DC gain to infinity. " "If integrator output decays toward zero, increase regeneration by " "increasing R5. If output continues to grow, decrease regeneration.", document="SBOA092B, Handbook of Operational Amplifier Applications", locator="pages 56-57, Simple Integrators (regeneration)", )
reading = Chooses( "Where do R2, R3, R4, R5, R6 and R7 connect?", selected=( "R6 from the R8 wiper to the - input and R7 from the wiper to the + " "input; R3 from the output to node X, R2 from X to the + input, and " "R4 plus the rheostat R5 from X to ground" ), alternatives=[ { "reading": "R2 and R3 in series from the - input to the output", "reason": ( "the vertical wire at the left end of R2 comes down from the + " "input's junction dot, and a resistive path across C_O would " "make the regeneration negative feedback that shortens the hold" ), }, { "reading": "R6 and R7 in series, the wiper feeding only their junction", "reason": "the two resistors end on separate dots, one on each op amp input", }, ], rationale=( "the handbook's procedure only makes sense with positive feedback: " "more R5 must mean more regeneration, and (R4 + R5)/R3 rises with R5", ), )
op_amp = Chooses( "What open-loop gain does the op amp have?", selected="5000 (74 dB)", alternatives=[ { "option": "the bench default, 1e6", "reason": ( "the smallest feedback fraction the network can set, with R5 at " "zero, is 0.9e-4, ninety times 1/A: the output runs away at every " "setting and the regeneration control does nothing useful" ), }, { "option": "1e4", "reason": "the balance would fall at 10% of R5's travel, not near the centre the procedure starts from", }, ], rationale=( "the network brackets 1/A for A between about 3700 and 11000, so it was " "sized for an op amp of that era's gain", "with 5000, k = 1/A at R5 = 1.2 kOhm, 60% of its travel", ), )
settings = Chooses( "Where are R8 and R5 set?", selected="R8 centred (the op amp is given no offset, so zero is the centre); R5 at a quarter of its travel", alternatives=[ { "option": "R5 at the balance, 60%", "reason": "the hold there is as long as the setting is exact, which is no number to claim", }, ], rationale=("a quarter of the travel is regeneration short of balance: a longer hold, still decaying",), )
hold_estimate = Calculates( "tau = R6 C_O (1 - k) / (1/A - k), k = (R4 + x R5)/(R3 + R4 + x R5) R7/(R2 + R7)", inputs=("r2", "r3", "r4", "r5", "r6", "r7", "c_o", "amp"), result=( "k = 0 without regeneration: A R6 C_O = 5000 s. With R5 at a quarter " "(500 Ohm), k = 1.364e-4 and tau = 15,700 s, about three times longer. " "The zero control's 7.5 kOhm source resistance and R7's pull on the " "wiper are left out; the simulation puts tau 2.3% lower" ), )
rate = Parameter("1/s", default=-10 * per_second, description="-1/(R1 C_O)") hold_open = Parameter( "s", default=5000 * s, description="the output's decay time constant with the input open and no regeneration: A R6 C_O", ) k_quarter = Parameter( "1", default=Decimal("1.3635e-4") * ratio, description="the fraction of E_O fed back to the + input with R5 at a quarter", ) hold_quarter = Parameter( "s", default=15710 * s, description="the decay time constant with R5 at a quarter of its travel", )
e_in = Terminal() e_out = Terminal() rail_plus = Terminal() rail_minus = Terminal() r1 = Resistor(resistance=100 * kOhm) c_o = Capacitor(capacitance=1 * uF) reset = Switch() # Zero control. r9 = Resistor(resistance=10 * kOhm) r8 = Potentiometer(resistance=10 * kOhm, setting=Decimal("0.5") * ratio) r10 = Resistor(resistance=10 * kOhm) r6 = Resistor(resistance=1 * MOhm) r7 = Resistor(resistance=1 * MOhm) # Regeneration. r2 = Resistor(resistance=100 * kOhm) r3 = Resistor(resistance=10 * MOhm) r4 = Resistor(resistance=1 * kOhm) r5 = Potentiometer(resistance=2 * kOhm, setting=Decimal("0.25") * ratio) amp = OpAmp(open_loop_gain=5000 * ratio) ground = Ground()
def architecture(self): # The integrator and its reset. self.e_in.probe >> self.r1.p1 self.r1.p2 >> self.amp.inverting.signal self.amp.inverting.signal >> self.c_o.p1 self.c_o.p1 >> self.reset.p1 self.c_o.p2 >> self.amp.output.signal self.reset.p2 >> self.amp.output.signal self.amp.output.signal >> self.e_out.probe # Zero control: rail, R9, R8, R10, rail; the wiper to both inputs. self.rail_plus.probe >> self.r9.p1 self.r9.p2 >> self.r8.end_a self.r8.end_b >> self.r10.p2 self.r10.p1 >> self.rail_minus.probe self.r8.wiper >> self.r6.p1 self.r8.wiper >> self.r7.p1 self.r6.p2 >> self.amp.inverting.signal self.r7.p2 >> self.amp.non_inverting.signal # Regeneration: output, R3, node X, R2 to the + input; R4 and R5 to ground. self.amp.output.signal >> self.r3.p2 self.r3.p1 >> self.r2.p2 self.r2.p1 >> self.amp.non_inverting.signal self.r3.p1 >> self.r4.p1 self.r4.p2 >> self.r5.end_a self.r5.wiper >> self.r5.end_b self.r5.end_b >> self.ground.node
def constraints(self): require( equals(self.rate, negative(over(1 * ratio, product(self.r1.resistance, self.c_o.capacitance)))) ) gain = self.amp.open_loop_gain require(equals(self.hold_open, product(gain, self.r6.resistance, self.c_o.capacitance)))
shunt = total(self.r4.resistance, product(self.r5.resistance, self.r5.setting)) k = product( over(shunt, total(self.r3.resistance, shunt)), over(self.r7.resistance, total(self.r2.resistance, self.r7.resistance)), ) require(within(self.k_quarter, k, 0.0005)) require( within( self.hold_quarter, over( product(self.r6.resistance, self.c_o.capacitance, minus(1 * ratio, self.k_quarter)), minus(over(1 * ratio, gain), self.k_quarter), ), 0.001, ) )
BENCH = Bench( page=57, title="Integrator with zero control and regeneration", runs=[ Run( "rate", Transient(stop="1.1", step="1m"), drive={"e_in": "DC 0.1", **RAILS}, switches={"reset": RESET}, measure={ "e_early": "find v({e_out.1}) at=0.1", "e_late": "find v({e_out.1}) at=1.1", "rate_per_volt": "(e_late - e_early) / 1.0 / 0.1", }, claims=[Claim("rate_per_volt", "rate", within=0.001, unit="/s")], units={"e_early": "V", "e_late": "V"}, note="0.1 V on E_I with R5 at a quarter; the reset switch opens at 10 ms.", ), Run( "hold_without_regeneration", Transient(stop="101", step="10m"), drive=RAILS, switches={"reset": RESET}, cards=RUN_UP_AND_OPEN + ["RNOREGEN {r3.1} 0 1m"], measure=HOLD_MEASURE, claims=[ Claim("tau", "hold_open", within=0.03, unit="s", note=( "3%: A R6 C_O leaves out the zero control's source resistance " "and the path through R7 and R2 to the + input" )), ], units={"e_1s": "V", "e_101s": "V"}, note=( "Regeneration taken out: node X tied to ground by a card the bench " "adds, so the + input sees none of the output. The input runs the " "output up to about -5 V and is opened at 0.51 s; the output then " "leaks through R6 with the time constant A R6 C_O." ), ), Run( "hold_with_regeneration", Transient(stop="101", step="10m"), drive=RAILS, switches={"reset": RESET}, cards=RUN_UP_AND_OPEN, measure=HOLD_MEASURE, claims=[ Claim("tau", "hold_quarter", within=0.03, unit="s", note="3%, for the same simplifications as the run above"), ], units={"e_1s": "V", "e_101s": "V"}, note=( "The circuit as drawn, R5 at a quarter of its travel: the same run " "up and the same open input, and a hold three times longer." ), ), Run( "too_much_regeneration", Transient(stop="101", step="10m"), drive=RAILS, switches={"reset": RESET}, settings={"r5": {"setting": 1.0}}, cards=RUN_UP_AND_OPEN, measure=HOLD_MEASURE, units={"e_1s": "V", "e_101s": "V", "tau": "s"}, note=( "R5 at the top of its travel: k = 2.7e-4 is past 1/A = 2e-4, so the " "output grows (a negative time constant), the handbook's cue to " "decrease regeneration. Not a claim: it shows the other side of the balance." ), ), ],)The files it writes
Section titled “The files it writes”The parts, then the nets and the pads on them.
C1 1 uF -GND1 Ground -R1 100 kOhm -R2 10 kOhm -R3 100 kOhm -R4 10 MOhm -R5 1 kOhm -R6 1 MOhm -R7 1 MOhm -R8 10 kOhm -RV1 Potentiometer -RV2 Potentiometer -SW1 Switch -TP1 Terminal -TP2 Terminal -TP3 Terminal -TP4 Terminal -U1 OpAmp -Net-(C1-Pad1) C1.1 R1.2 R6.2 SW1.1 U1.IN-Net-(C1-Pad2) C1.2 R4.2 SW1.2 TP2.1 U1.OUTShow 10 more lines
Net-(GND1-Pad1) GND1.1 RV1.2 RV1.3Net-(R1-Pad1) R1.1 TP1.1Net-(R2-Pad1) R2.1 TP3.1Net-(R2-Pad2) R2.2 RV2.3Net-(R3-Pad1) R3.1 R7.2 U1.IN+Net-(R3-Pad2) R3.2 R4.1 R5.1Net-(R5-Pad2) R5.2 RV1.1Net-(R6-Pad1) R6.1 R7.1 RV2.2Net-(R8-Pad1) R8.1 TP4.1Net-(R8-Pad2) R8.2 RV2.1Every check that ran, and every one left undecided.
4 checks, 0 failed, 0 undecidedWhat the elaborated graph contains, by entity kind.
1 block 1 calculation 18 component 44 connection 4 constraint 3 decision 1 evidence 3 interface 34 pin 34 port 143 totalsnapshot sha256:6b3be0a9f8b0acc62886ec5d32bef0c814d5c03cbdffe2a78eeb23ebc727f452All of it, including the KiCad netlist, is in
examples/ti_opamp_handbook/integrators/regenerative_integrator/out/. Rebuild it with:
fang build examples/ti_opamp_handbook/integrators/regenerative_integrator/regenerative_integrator.py