Examples / TI op amp handbook / Oscillators
Simple oscillator
SBOA092B page 83, Simple Oscillator: two T networks in parallel from the output back to the - input, R, R with 2C to ground between them, and C, C with R/2 to ground between them. The op amp runs open loop through them.
f = 1 / (2 pi R C)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/simple_oscillator.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 two T’s are a twin-T notch. At d.c. the R-R path feeds the output back to the - input, negative feedback, and the circuit is still. At 1 / (2 pi R C) a balanced twin-T passes nothing; trim R/2 a little low and what it passes there turns negative, so the inverting op amp gets its own output back in phase and oscillates. Two decisions are recorded:
values: R = 10 kΩ and C = 15.9 nF (2C = 31.8 nF), for 1.001 kHz.trim: R/2 is a 10 kΩ rheostat (so mid-travel is R/2) set to 0.49, 2% low.
The constraints tie f_o to R and C, the two R’s and two C’s to each other, 2C to twice C, and the trim to the low side of R/2.
What the simulation found
Section titled “What the simulation found”out/simulation.txt, 200 ms from a 1 V kick on the 2C
capacitor, measured over the last 50 ms:
| Measured | Value | Claimed |
|---|---|---|
| frequency, 40 cycles | 1.006 kHz | 1 kHz (f_o) ±1%, holds |
| output high | 13.51 V | 13.5 V ±1%, holds |
| output low | -13.51 V | -13.5 V ±1%, holds |
| peak at the - input | 223 mV | not a claim |
Nothing in the loop limits the amplitude, so the output grows until it clips at the op amp’s swing and is close to a square wave; the - input, filtered by the network, carries a far cleaner wave of about 0.2 V. Trimming R/2 2% low moves the frequency up 0.5%. Nearer balance the frequency moves less but the oscillation takes much longer to build.
Running it
Section titled “Running it”fang check examples/ti_opamp_handbook/oscillators/simple_oscillator/simple_oscillator.pypython examples/regenerate.py ti_opamp_handbook/oscillators/simple_oscillator # needs ngspiceThe whole program
Section titled “The whole program”"""The simple oscillator, SBOA092B page 83.Show 18 more lines
f = 1 / (2 pi R C)
Two T networks run in parallel from the output back to the - input: R, R inseries with 2C from their junction to ground, and C, C in series with R/2from theirs. Together they are a twin-T notch, and the op amp runs open loopthrough it. At d.c. the R-R path feeds the output straight back, which isnegative feedback and holds the circuit still. At 1 / (2 pi R C) a balancedtwin-T passes nothing. Trim the R/2 leg a little low and the transmission atthat frequency turns negative, so the inverting op amp sees its own outputcome back in phase: regenerative feedback, and it oscillates there.
The figure gives R and C no values and R/2 no setting, so `values` and`trim` record what was taken: 10 kOhm and 15.9 nF for 1 kHz, and the R/2 legas a 10 kOhm rheostat set to 0.49 of its travel, 4.9 kOhm. Nothing in theloop limits the amplitude but the op amp's swing, so the output grows untilit clips at the rails and is reported as that."""
import sysfrom 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 Parameter, System, kHz, kOhm, nF, requirefrom fang.parts import Capacitor, Resistorfrom fang.rationale import Chooses, Citesfrom fang.simulation import Transient
from handbook import ( Bench, Claim, Ground, OpAmp, Potentiometer, Run, Terminal, at_most, corner, equals, product, ratio, within,)
class SimpleOscillator(System): """An op amp run open loop through a twin-T, trimmed just past balance."""
figure = Cites( "f = 1 / (2 pi R C). Double integrator circuit with regenerative " "feedback. Components R, C, and 2C should be very low tolerance. Trim " "R/2 until oscillation is barely sustained.", document="SBOA092B, Handbook of Operational Amplifier Applications", locator="page 83, Simple Oscillator", )
values = Chooses( "What are R and C?", selected="10 kOhm and 15.9 nF, so 1 / (2 pi R C) is 1.001 kHz; 2C is 31.8 nF", alternatives=[ { "option": "leave them symbolic", "reason": "a frequency nobody can compute is not a claim a run can check", }, ], rationale=( "the figure names R, C and 2C and gives no values", "1 kHz is a round frequency and 10 kOhm keeps the network well " "above the op amp's output resistance", ), )
trim = Chooses( "Where is R/2 set?", selected="a 10 kOhm rheostat at 0.49 of its travel: 4.9 kOhm, 2% below R/2", alternatives=[ { "option": "exactly R/2", "reason": "a balanced twin-T passes nothing at f, so nothing " "comes back to sustain an oscillation", }, { "option": "R/2 set high", "reason": "the transmission at f is then positive, the feedback " "negative, and the circuit is still", }, { "option": "0.499 of the travel, nearer barely sustained", "reason": "the growth is then so slow that a run long enough to " "see it settle is mostly waiting; 2% low moves f by 0.5%", }, ], rationale=( "the page says to trim R/2 until the oscillation is barely " "sustained; below R/2 is the side that sustains it", "a 10 kOhm pot is R, so its mid-travel is R/2 and 0.49 is just below", ), )
f_o = Parameter("Hz", default=1 * kHz, description="1 / (2 pi R C)")
e_out = Terminal() r_first = Resistor(resistance=10 * kOhm) r_second = Resistor(resistance=10 * kOhm) c_shunt = Capacitor(capacitance=Decimal("31.8") * nF) c_first = Capacitor(capacitance=Decimal("15.9") * nF) c_second = Capacitor(capacitance=Decimal("15.9") * nF) r_half = Potentiometer(resistance=10 * kOhm, setting=Decimal("0.49") * ratio) amp = OpAmp() ground = Ground()
def architecture(self): # R, R from the output to the - input, 2C to ground between them. self.amp.output.signal >> self.r_first.p1 self.r_first.p2 >> self.r_second.p1 self.r_first.p2 >> self.c_shunt.p1 self.r_second.p2 >> self.amp.inverting.signal
# C, C beside them, R/2 to ground between them. self.amp.output.signal >> self.c_first.p1 self.c_first.p2 >> self.c_second.p1 self.c_first.p2 >> self.r_half.end_a self.c_second.p2 >> self.amp.inverting.signal
# The R/2 rheostat: the wiper tied to the grounded end. self.r_half.wiper >> self.r_half.end_b self.r_half.end_b >> self.ground.node self.c_shunt.p2 >> self.ground.node self.amp.non_inverting.signal >> self.ground.node self.amp.output.signal >> self.e_out.probe
def constraints(self): require(within(self.f_o, corner(self.r_first.resistance, self.c_first.capacitance), 0.002)) require(equals(self.r_second.resistance, self.r_first.resistance)) require(equals(self.c_second.capacitance, self.c_first.capacitance)) require(equals(self.c_shunt.capacitance, product(2 * ratio, self.c_first.capacitance))) # The pot is R, so mid-travel is R/2, and it is set below that. require(equals(self.r_half.resistance, self.r_first.resistance)) require(at_most(self.r_half.setting, Decimal("0.5") * ratio))
BENCH = Bench( page=83, title="Simple Oscillator", runs=[ Run( "oscillation", Transient(stop="200m", step="2u"), cards=[".ic v({c_shunt.1})=1"], measure={ "t_first": "when v({e_out.1})=0 rise=1 td=150m", "t_last": "when v({e_out.1})=0 rise=41 td=150m", "f_o": "40 / (t_last - t_first)", "e_high": "max v({e_out.1}) from=150m to=200m", "e_low": "min v({e_out.1}) from=150m to=200m", "early_high": "max v({e_out.1}) from=100m to=110m", "summing_peak": "max v({amp.IN-}) from=150m to=200m", }, claims=[ Claim( "f_o", "f_o", within=0.01, unit="Hz", note="1% for the trim: R/2 set 2% low moves the frequency " "at which the twin-T turns negative up by about 0.5%", ), Claim( "e_high", 13.5, within=0.01, unit="V", note="nothing in the loop limits the amplitude; the output " "clips at the op amp's swing", ), Claim("e_low", -13.5, within=0.01, unit="V"), ], units={"t_first": "s", "t_last": "s", "early_high": "V", "summing_peak": "V"}, note="A 1 V kick on the 2C capacitor at the start, released at once, " "and 150 ms for the oscillation to grow into the rails; the " "frequency is timed over the forty rising zero crossings after.", ), ],)The files it writes
Section titled “The files it writes”The parts, then the nets and the pads on them.
C1 15.9 nF -C2 15.9 nF -C3 31.8 nF -GND1 Ground -R1 10 kOhm -R2 10 kOhm -RV1 Potentiometer -TP1 Terminal -U1 OpAmp -Net-(C1-Pad1) C1.1 R1.1 TP1.1 U1.OUTNet-(C1-Pad2) C1.2 C2.1 RV1.1Net-(C2-Pad2) C2.2 R2.2 U1.IN-Net-(C3-Pad1) C3.1 R1.2 R2.1Net-(C3-Pad2) C3.2 GND1.1 RV1.2 RV1.3 U1.IN+Every check that ran, and every one left undecided.
6 checks, 0 failed, 0 undecidedWhat the elaborated graph contains, by entity kind.
1 block 9 component 26 connection 6 constraint 2 decision 1 evidence 3 interface 18 pin 18 port 84 totalsnapshot sha256:74bb0b9883944fdc28a6f5c5fc1dcb9700858563a981400a16ec047350be02edAll of it, including the KiCad netlist, is in
examples/ti_opamp_handbook/oscillators/simple_oscillator/out/. Rebuild it with:
fang build examples/ti_opamp_handbook/oscillators/simple_oscillator/simple_oscillator.py