◀ Back to Theory of operation

Oscilloscope

What it is for

The oscilloscope is the time-domain view - and the one you should look at first. Before any spectral number is worth trusting, the waveform has to look sane: present at all, not clipping, free of gross DC offset or mains hum, at the level and frequency you expect. The scope answers those questions directly, and puts a live readout table beside the trace: peak-to-peak and RMS voltage, DC offset, frequency and period, duty cycle, rise and fall times.

From ring buffer to screen

The scope reads the newest window from the ring buffer on every repaint, conditions it, finds a stable trigger point, and draws it. A separate background pass measures the numbers at a steady 10 Hz so the readout is calm even when the trace updates fast.

ring buffer latest window conditioning HF cleanup · mains trigger edge / glitch resampling Lanczos to pixels trace on screen measurement pass (10 Hz) Vpp · Vrms · Vmean · f · duty · rise/fall captured window · mains comb only

Conditioning

Two optional filters run before display:

Only one of the two also reaches the measurement pass: the mains comb, because rejecting hum is a measurement decision rather than a display one - hum you have deliberately notched out should not be counted into Vpp or Vrms either. The high-frequency cleanup is a property of the trace alone. It is a setting you pick to make a picture readable, and the moment it moves Vpp, Vmean, rise and fall time or duty cycle, those stop being measurements of the signal and become measurements of our rendering of it. It would also err in one direction only: the filter is reset at the start of every pass, so it is measured ringing its way out of a step of up to the full peak-to-peak - which a peak detector reads as signal. On the bench that put a clean sine about 12 % high in Vpp and a quarter long in rise time, which is why the measurement pass no longer sees it.

Triggering

A free-running scope would redraw the waveform at a random phase each frame, so a periodic signal would appear to slide across the screen. Triggering anchors every frame at the same feature - a chosen level crossed in a chosen direction (rising or falling edge). The display is then drawn relative to that point, so a stationary tone stands still.

Phonalyser.web triggers on absolute sample positions in the stream: each captured trigger records the absolute index of the crossing, including a sub-sample fraction from linear interpolation between the two straddling samples. That fraction shifts the rendered trace by less than one pixel so the waveform doesn't jitter by a sample even on a fast time base.

The glitch trigger: firing on damage, not on a level

Some events worth catching are not level crossings at all. A capture hiccup that swallows samples leaves an amplitude step or a slope splice that can sit anywhere on the wave, in either direction - an edge trigger parked at a fixed level almost never lands on it. The glitch trigger fires on any waveform discontinuity instead, wherever it occurs; the trigger level, hysteresis and sub-sample refinement do not apply. (It is available in normal and single modes only: a free-running auto scope would overwrite the caught frame on the very next repaint.)

The detector exploits the fact that a sampled sinusoid - at any frequency - obeys an exact three-term recurrence[1]: each sample is fully predicted by the previous two,

x[i] = 2 cos ω · x[i−1] − x[i−2],   ω = 2π f / fs(1)

so the prediction error

e[i] = x[i] − 2 cos ω · x[i−1] + x[i−2](2)

nulls exactly on a clean tone at any frequency, leaving only what the tone does not explain: the noise floor, residual harmonics, a second tone. When the measured channel is the trigger channel, ω is pinned from the measured fundamental (the millihertz-accurate figure from the measurement pass below); otherwise the coefficient 2 cos ω is fitted to the window itself by least squares, clamped to the range a real sinusoid allows.

A sample flags as discontinuous when |e| exceeds 8× the window's own mean |e| - a threshold that rides the noise floor. On a clean tone that is about 6.4 standard deviations, which broadband noise essentially never reaches, while a genuine splice breaks the prediction by a large fraction of the signal amplitude - decades above it. And because the baseline is measured from the same window, harmonics or a second tone raise it proportionally: the rule scales itself.

Pinning the frequency is what keeps the detector alive up high. The plain second difference - (2) with ω = 0 - does not null the tone: it leaves a residual of amplitude 2(1 − cos ωA, the tone's own curvature. For a 20 kHz tone sampled at 384 kHz that residual pushes the 8×-mean threshold to roughly half the signal amplitude (≈ 0.5 A), so every glitch short of a half-scale break would pass unseen; the tuned recurrence keeps the baseline at the noise floor at any frequency.

A real dropout has two boundaries - the entry into the gap and the recovery after it (observed USB dropouts run 120-160 µs) - and both break the prediction. Above-threshold samples closer together than 1 ms therefore merge into a single glitch event, and the edge-direction buttons choose the anchor: ↑ places the trigger on the glitch's start (the last sample still on the old trend), ↓ on its end (the first sample the local prediction accepts again).

The same detector guards the FFT analyser's coherent averaging: each captured window is screened with it in the time domain beside the spectral gates, so the scope's trigger and the analyser's block rejection agree on what counts as damaged.

How the numbers are measured

The measurement pass works on the captured window in normalised units - mains-rejected if that is switched on, but never high-frequency filtered (see Conditioning) - converting to volts only with the ADC full-scale calibration (a sample of ±1 corresponds to Vfs·√2 peak). Level statistics are direct; frequency takes more care.

Level

Over the window of N samples xi:

Vpp = (max − min) · Vpeak(3)
Vmean = Vpeak · (1/N) Σ xi(4)
Vrms = Vpeak · √( E[x²] − E[x]² )(5)

The RMS in (5) is the AC RMS - the DC bias is removed first (the variance form subtracts the mean), because a total RMS that collapsed to ≈ Vmean on a DC-biased input is far less useful on a scope.

On a periodic signal, (4) and (5) are not evaluated over the raw window. A window holding a fractional number of cycles leaves a dangling partial cycle whose contribution to the mean - up to A/(π n) for amplitude A and n cycles in the window - depends on where the window happens to start. The spread of Vmean then scales with the signal amplitude rather than the noise: millivolts of phase-random wander at full scale, swamping a microvolt noise floor. The scope therefore integrates Vmean and Vrms only between the first and last rising crossings of the half-amplitude midpoint - an integer number of periods. At a midpoint crossing the signal sits at its own mean by definition, so the boundary error turns second-order, and Vmean settles to the noise floor: its standard deviation on a full-scale signal drops from millivolts to under a microvolt - the same ≈ 0.6 µV it shows on a 1 mV signal. Vpp, min and max stay full-window (a peak must not be dropped for landing in the partial cycle), and a signal with no two same-direction crossings - DC, or noise alone - falls back to the full window.

Frequency - crossings, then Goertzel refinement

Reading frequency off a short buffer accurately is the hard part. A three-stage strategy gets it to a few millihertz:

  1. Coarse from crossings. Count rising crossings of the half-amplitude midpoint; the span of the outermost two over the number of intervals gives a first period estimate. Crossings track the true fundamental directly, so a narrow-duty rectangle yields the right answer rather than locking onto a strong harmonic.
  2. Refine with the Goertzel algorithm[2]. The Goertzel algorithm evaluates a single DFT bin at an arbitrary frequency in one O(N) pass - a cheap way to probe magnitude at any trial frequency without a full FFT. Three successive scans, each ~20× finer than the last, home in on the peak, and a parabolic fit across the final three probes gives sub-step precision (typically ≤ 0.005 Hz on a one-second buffer).
  3. Leakage de-bias. The bare (rectangular-window) peak is pulled off the true frequency by leakage from the negative-frequency image - up to a few tenths of a hertz on short buffers, which is what once made the scope read low next to the FFT. Re-refining the peak on a Hann-windowed[3] copy suppresses that image and pins the frequency to < 0.02 Hz regardless of buffer length.

A quality figure (the peak magnitude relative to the AC RMS) gates the result: ≈ 0.707 for a clean sine, far lower for noise. Below the threshold the frequency, period and duty read as undefined rather than latching onto noise. For a genuine dual-tone signal these single-value fields have no meaningful answer and are blanked entirely; the level figures stay valid.

Drawing between the samples: sin x/x reconstruction

At fast time bases only a handful of samples cross the screen. Connecting the sample dots with straight lines draws corners that do not exist in the signal - the ADC's anti-alias filter guarantees the captured signal contains no energy above Nyquist, and the sampling theorem[4] states that such a band-limited signal is completely determined between its samples:

x(t) = Σn x[n] · sinc(tn),   sinc u = sin(πu) / (πu)(6)
ADC samples (4 per period) straight-line join - corners that don't exist sin x/x reconstruction

The scope evaluates (6) in its practical, windowed form - a Lanczos[5] kernel with 16 sinc lobes each side, precomputed into a phase table - producing one true reconstructed value per pixel column when zoomed in. The same reconstruction refines the trigger instant between two samples (a bisection on the reconstructed curve, to about 1/1000 of a sample), which is why the trace stands still instead of jittering by a sample. Reconstruction is switchable per channel (the sin x/x option); with it off, the scope falls back to straight-line interpolation.

Zoomed out, the problem inverts: many samples land on one pixel. The kernel is then widened so it doubles as an anti-alias low-pass at the screen's own sample rate - without it, energy between the "pixel Nyquist" and the capture Nyquist folds into spurious beat patterns on the trace. Beyond ~5 samples per pixel the scope switches to per-column min/max envelope bars instead.

Mains-hum rejection: the tracked comb

Mains interference sits at the line frequency and every integer harmonic of it. A single feedback comb filter[6] places a notch at DC and at every multiple of f₀ at once:

H(z) = (1 − zN) / (1 − α·zN),   N = fs / f(7)

where α (just below 1) sets how sharp each notch is. The trick is that the comb is frequency-tracked: real mains drifts by a few tenths of a hertz, and the k-th harmonic drifts k× as far, so a fixed comb loses depth up high. By measuring the true line fundamental from a reference block - auto-detecting 50 vs 60 Hz - and re-tuning the single value N, every notch repositions to its exact k·f₀ together. N is generally fractional, so the delays interpolate linearly between the two straddling taps.

Why frequency is read off the raw signal. The comb's notches bias any frequency measured through it. So the scope uses the comb only to decide which peak is the tone (it suppresses an often-dominant hum so the tone becomes the spectral peak), then re-measures that tone's true frequency on the raw, un-notched signal in a narrow band around the located peak - narrow enough that no mains harmonic (tens of hertz away) competes.

The amplitude histogram: how often, not when

The trace answers "what does the waveform look like over time". The amplitude histogram answers a different question about the same samples: how much of its time does the signal spend at each voltage. Time is thrown away and only the occupancy is kept, which is what makes the picture worth having - the shape of the distribution identifies the signal's character at a glance, independently of its frequency.

A sine spends most of its time near its turning points, where it is moving slowly, and races through zero. Its probability density is the arcsine distribution, and the bathtub on screen is that curve:

p(v) = 1 / (π √(A² − v²)),  |v| < A(8)

which rises to infinity at ±A and is flattest at zero. Broadband noise draws a Gaussian bell instead; a clipped signal piles the time it spends against the rail into a spike at that voltage; and a waveform clipped on one side only shows as unequal bars about the centre line. Nothing else on the bench separates those three cases as directly.

Counted exactly as captured

Samples are binned before the high-frequency cleanup, before DC removal and before the mains comb - the same reasoning as for the measurement pass. A distribution taken after a display filter would be a distribution of our rendering rather than of the signal, and the low-pass in particular narrows exactly the tails the histogram exists to show.

Resolution follows the signal, not the converter

The obvious construction - divide full scale into a fixed number of bins - ties the resolution to the converter's range instead of to what is being measured. On a card whose full scale is 1.79 VRMS, 2048 divisions is a 2.5 mV bin: six orders of magnitude coarser than the 1.18 nV a 32-bit converter actually resolves, so a millivolt residual would collapse into two or three bars and tell you nothing.

So the range is sized from the signal's own peak, symmetric about zero, and re-established when the signal outgrows it. The peak used is a stable one - aggregated over the scope's measurement-average window - because a single block's peak is a random draw from the signal's tail and ranging on that would restart the distribution on nearly every pass of anything noisy.

1/8 1/8 signal occupies the middle 6/8 0 V −peak − headroom +peak + headroom headroom = tolerance 256 micro-bins accumulated per drawn bar

Two constants carry the rest of it:

Bin width is floored at one converter code - 1/2³¹ in normalised units, the deepest capture format the application supports. Below that a bin could only ever hold the gaps between codes, and a dead-flat signal would produce a range of zero width.

Why a re-range clears the counts

When the signal no longer fits, the range is re-established and the counts are cleared, deliberately. Counts gathered at a materially different level describe a different signal, and merging the two would present two populations as one distribution. A restart is the honest signal that the measurement changed. A signal that shrinks keeps its range - and so simply gets coarser - until you reset, which re-ranges onto whatever is present now. Between restarts collection is unbounded: it survives a V/div change, a range switch and a recalibration, because none of those alters a single count.

Absolute bins, a mean-relative axis

Two layers share the work, and the split is what keeps a DC offset honest. The accumulator is unit-agnostic and absolute: it is fed normalised samples, stores nothing but counts, and its range is centred on zero - so a signal carrying an offset sits off centre by exactly that offset. Nothing subtracts a running mean before binning; doing so would file one physical voltage into different bins while the estimate was still settling, smearing a single distribution into two.

The display then windows those bins symmetrically about the distribution's own mean and labels every tick as an offset from it, so 0 V is always the centre line and the visible span cannot slide off-picture when the signal is offset. Because the axis is only a labelling of bin edges - bin edges multiplied by the channel's peak volts - a recalibration relabels the axis without disturbing a single count. That is why a range switch mid-collection is not a reason to start again.

Not the code-density histogram. The command-line ADC-linearity modes also build a histogram, but of ADC codes, one bin per code, compared against an ideal density to yield DNL and INL - see the algorithm reference. That one characterises the converter; this one characterises the signal, and their bin axes mean different things.

Where it sits

The oscilloscope shares its capture device and ring buffer with the FFT analyser through the common capture session, so both can record together off one open device. It also borrows the generator's commanded waveform as a hint: when a dual-tone signal is playing it knows to blank the single-tone fields rather than guess.

References

  1. 1. The sinusoid recurrence (1) is the order-2 case of linear prediction - Wikipedia: Linear prediction.
  2. 2. Goertzel algorithm (single-bin DFT) - Wikipedia: Goertzel algorithm.
  3. 3. Hann window and spectral leakage - Wikipedia: Hann window.
  4. 4. The sampling theorem and formula (6) - Wikipedia: Whittaker-Shannon interpolation formula.
  5. 5. The windowed-sinc kernel used in practice - Wikipedia: Lanczos resampling.
  6. 6. Comb filter (the feedback form of (7)) - Wikipedia: Comb filter.

◀ Theory of operation · next: FFT analyser ▶