A sensor does not hand you a continuous function. It hands you numbers at intervals. A microphone gives a stream of samples. A controller updates once per cycle. Once time is sampled, the mathematics changes shape: derivatives become recurrences, spectra pick up aliasing, and filters become algorithms.
Upper-year engineering is full of systems that are modelled in continuous time but built in discrete time. A motor may obey a differential equation. The controller that stabilises it still runs on a clock. A seismic wave may be continuous in the ground. The data still arrives as a sampled trace.
Sampling turns a continuous object into a sequence, and once you have a sequence, the natural questions become delay, recurrence, convolution, spectrum, and stability. That structural shift — not signal processing vocabulary — is what this chapter is about.
65.1 What this chapter helps you do
Symbols to keep handy
These are the bits of notation you'll see a lot. If a line of symbols feels like a fence, read it out loud once, then keep going.
y[n] = _{k=0}^{M} b_k x[n-k]: y of n equals the weighted sum of present and past input samples
f_s = 1/T_s: f sub s equals one over T sub s — the sampling frequency
T_s: T sub s — the sampling period
Definitions to keep handy
These are the words we keep coming back to. If one feels slippery, come back here and steady it before you push on.
sampling: Turning a continuous-time signal into a sequence of numbers taken at regular time intervals.
aliasing: When you sample too slowly and high-frequency content masquerades as a lower frequency.
LTI system: Linear time-invariant: superposition holds, and the rule does not change over time.
FIR filter: A filter where the output uses only a finite number of past input samples.
frequency response: How a system amplifies or attenuates each frequency component.
Here is the main move this chapter is making, in plain terms. You do not need to be fast. You just need to keep the thread.
Coming in: Real sensors, controllers, and computers do not live in continuous time.
Leaving with: Sampling turns continuous mathematics into sequences, recurrences, spectra, and filters with their own stability and aliasing laws.
65.2 The discrete-time view
A continuous-time signal is written x(t). A discrete-time signal is written x[n], read “x of n.” The variable n is not time itself. It is an index. The physical time attached to sample n is
t_n = nT_s
How to read bracket notation
x[n] means “the value of x at step n” (a sample).
x[n+1] means “the next sample.”
x[n-3] means “three samples ago.”
The brackets are not multiplication, and they are not an exponent. They are just an index into a sequence.
where T_s is the sampling period. The sampling frequency is
f_s = \frac{1}{T_s}
The same physical system can therefore be described in two ways:
continuous-time: what the signal is doing between samples
discrete-time: what the sampled sequence records at the clock instants
This sounds like a harmless change of notation. It is not. Once the signal is a sequence, operations that looked infinitesimal in continuous time become difference equations and delay operators.
If a discrete-time system takes an input sequence x[n] and produces an output sequence y[n], the simplest and most important class is the linear time-invariant (LTI) discrete system. Linear means the system obeys superposition. Time-invariant means the rule does not change between steps — the coefficients are fixed constants, not functions of n. For those systems, present output depends on present and past samples in a fixed way.
One common form is a finite impulse response (FIR) filter:
FIR filter, in words
The rule
y[n] = \sum_{k=0}^{M} b_k x[n-k]
says: to compute the next output, take a small window of recent input samples, weight them, and add them up.
It is a sliding weighted average (not necessarily with positive weights). The weights b_k are the filter you are designing.
y[n] = \sum_{k=0}^{M} b_k x[n-k]
“Finite” here means the output depends on only a bounded window of M+1 past inputs, so the filter’s memory is bounded. The output at time step n is a weighted combination of current and previous input samples.
Filters that also feed back past outputs — so that y[n] depends on earlier y values as well as earlier x values — are called infinite impulse response (IIR) filters. Because the output feeds back into itself, a single impulse at the input can in principle affect every future output, giving the filter an unbounded memory. In stable IIR filters the impulse response decays geometrically, so the influence of distant past inputs becomes negligible in practice. IIR filters appear in the z-transform and state-space frameworks; the stability rules derived later in this chapter apply to them.
Code
{const width =700, height =230;functionsvgEl(tag){returndocument.createElementNS("http://www.w3.org/2000/svg", tag);}const container =document.createElement("div"); container.style.cssText="max-width: 740px; margin: 1rem 0 1.25rem 0; font-family: inherit;";const controls =document.createElement("div"); controls.style.cssText="display:flex; gap:0.75rem; align-items:end; margin-bottom:0.75rem;";const stepWrap =document.createElement("label"); stepWrap.style.cssText="display:grid; gap:0.25rem; font-size:0.9rem; color:#374151;"; stepWrap.appendChild(Object.assign(document.createElement("span"), { textContent:"Window position n" }));const step =document.createElement("input"); step.type="range"; step.min="2"; step.max="10"; step.step="1"; step.value="5";const out =document.createElement("span"); out.style.cssText="font-variant-numeric: tabular-nums; color:#6b7280;"; stepWrap.append(step, out); controls.appendChild(stepWrap);const svg =svgEl("svg"); svg.setAttribute("viewBox",`0 0 ${width}${height}`); svg.setAttribute("width","100%"); svg.style.cssText="display:block; background:#fff; border:1px solid #e5e7eb; border-radius:10px;";const caption =document.createElement("p"); caption.style.cssText="font-size:0.9rem; color:#4b5563; margin:0.55rem 0 0 0;";const xVals = [0,1,2,4,3,5,2,1,0,2,1,0];const hVals = [0.2,0.3,0.5];functionrect(x,y,w,h,fill,stroke="#cbd5e1"){const r=svgEl("rect"); r.setAttribute("x",x); r.setAttribute("y",y); r.setAttribute("width",w); r.setAttribute("height",h); r.setAttribute("fill",fill); r.setAttribute("stroke",stroke); svg.appendChild(r);}functiontext(x,y,t,fill="#374151",size=12,anchor="middle",weight="400"){const n=svgEl("text"); n.setAttribute("x",x); n.setAttribute("y",y); n.setAttribute("fill",fill); n.setAttribute("font-size",size); n.setAttribute("text-anchor",anchor); n.setAttribute("font-weight",weight); n.textContent=t; svg.appendChild(n);}functionredraw(){while(svg.firstChild) svg.removeChild(svg.firstChild);const n =parseInt(step.value,10); out.textContent=`${n}`;text(100,28,"input sequence x[n]","#374151",12,"middle","700");text(100,122,"filter weights h[k]","#374151",12,"middle","700");text(100,202,"output sample y[n]","#374151",12,"middle","700");for(let i=0;i<xVals.length;i++){const x =150+ i*42;const active = i >= n-2&& i <= n;rect(x,40,34,34, active ?"#dbeafe":"#f8fafc", active ?"#2563eb":"#cbd5e1");text(x+17,62,`${xVals[i]}`, active ?"#1d4ed8":"#374151",12);text(x+17,87,`${i}`,"#6b7280",10); }for(let k=0;k<hVals.length;k++){const x =150+ (n-2+k)*42;rect(x,105,34,28,"#ecfdf5","#059669");text(x+17,123,`${hVals[k].toFixed(1)}`,"#047857",11);const l=svgEl("line"); l.setAttribute("x1",x+17); l.setAttribute("x2",x+17); l.setAttribute("y1","74"); l.setAttribute("y2","105"); l.setAttribute("stroke","#059669"); l.setAttribute("stroke-dasharray","4 3"); svg.appendChild(l); }const y = hVals[0]*xVals[n-2] + hVals[1]*xVals[n-1] + hVals[2]*xVals[n];rect(150+ n*42-10,182,54,28,"#fef3c7","#f59e0b");text(150+ n*42+17,200,`${y.toFixed(1)}`,"#b45309",12,"middle","700");text(430,164,`y[${n}] = 0.2x[${n-2}] + 0.3x[${n-1}] + 0.5x[${n}]`,"#374151",12,"middle"); caption.textContent="An FIR filter computes each output sample from a finite sliding window of past inputs. Moving the window makes the convolution rule visible as local weighted averaging."; } step.addEventListener("input", redraw); container.append(controls, svg, caption);redraw();return container;}
Use the controls to compare the real continuous oscillation with what the sampler can actually preserve.
and you sample it every T_s seconds. Then the discrete sequence is
x[n] = \sin(2\pi f nT_s) = \sin\left(2\pi \frac{f}{f_s} n\right)
The discrete sequence only sees the ratio f/f_s. Two different continuous frequencies can therefore produce sampled sequences that are indistinguishable, if the sampling frequency is too low. The samples no longer carry enough information to tell you which continuous oscillation was actually present. This means aliasing is not a small numerical error. It is a structural loss of information created at the moment of sampling.
Nyquist limit, in words
Sampling does not “record everything.” It can only resolve changes up to about half the sampling rate.
So if you sample at f_s, frequencies above f_s/2 do not disappear — they show up disguised as lower frequencies.
The Nyquist–Shannon sampling theorem states the safe condition:
to represent frequency content up to f_{\max}, sample faster than 2f_{\max}
That does not solve every practical problem, but it tells you where obvious failure begins. Sampling below that rate invites aliasing: high-frequency content reappears as a false lower-frequency pattern in the data.
This is one of the reasons discrete-time systems need their own mathematics. The sampled sequence is not a transparent window into the continuous signal. It is a transformed object with its own constraints.
65.4 Difference equations and recurrence
Continuous-time linear systems are often described by differential equations. Discrete-time linear systems are naturally described by difference equations.
A first-order example is
y[n+1] = ay[n] + bu[n]
This says: the next output is built from the current output and the current input. The constant a carries memory. The constant b tells you how strongly the input influences the update.
The stability rule for the homogeneous system
y[n+1] = ay[n]
is very simple:
if |a| < 1, the sequence decays to zero
if |a| > 1, the sequence grows in magnitude
if |a| = 1, the sequence neither decays nor grows and small modelling changes can matter a great deal
This is the discrete-time analogue of looking at pole location in continuous time. In one case you care about left-half-plane location. In the other you care about whether the relevant quantity sits inside the unit circle.
NoteWhy this works
In continuous time, a mode behaves like e^{st}, so the sign of \Re(s) decides whether it decays or grows. In discrete time, a mode behaves like a^n, so the magnitude of a decides whether repeated updates shrink or blow up. Exponentials become powers, and left-half-plane stability becomes unit-circle stability.
Code
{const width =700, height =280;functionsvgEl(tag){returndocument.createElementNS("http://www.w3.org/2000/svg", tag);}const container =document.createElement("div"); container.style.cssText="max-width: 740px; margin: 1rem 0 1.25rem 0; font-family: inherit;";const controls =document.createElement("div"); controls.style.cssText="display:flex; gap:0.75rem; align-items:end; margin-bottom:0.75rem;";const wrap =document.createElement("label"); wrap.style.cssText="display:grid; gap:0.25rem; font-size:0.9rem; color:#374151;"; wrap.appendChild(Object.assign(document.createElement("span"), { textContent:"Mode location s" }));const slider =document.createElement("input"); slider.type="range"; slider.min="-1.4"; slider.max="1.4"; slider.step="0.02"; slider.value="0.6";const out =document.createElement("span"); out.style.cssText="font-variant-numeric: tabular-nums; color:#6b7280;"; wrap.append(slider, out); controls.appendChild(wrap);const svg =svgEl("svg"); svg.setAttribute("viewBox",`0 0 ${width}${height}`); svg.setAttribute("width","100%"); svg.style.cssText="display:block; background:#fff; border:1px solid #e5e7eb; border-radius:10px;";const caption =document.createElement("p"); caption.style.cssText="font-size:0.9rem; color:#4b5563; margin:0.55rem 0 0 0;";functionredraw(){while(svg.firstChild) svg.removeChild(svg.firstChild);const a =parseFloat(slider.value); out.textContent= a.toFixed(2);const left = { x:40,y:30,w:260,h:220 };const right = { x:380,y:30,w:260,h:220 };const mapSx = x => left.x+ left.w* (x +2) /4;const mapSy = y => left.y+ left.h* (1- (y +2) /4);const mapZx = x => right.x+ right.w* (x +1.6) /3.2;const mapZy = y => right.y+ right.h* (1- (y +1.6) /3.2);const text=(x,y,t,fill="#374151",size=12,anchor="middle",weight="400")=>{const n=svgEl("text"); n.setAttribute("x",x); n.setAttribute("y",y); n.setAttribute("fill",fill); n.setAttribute("font-size",size); n.setAttribute("text-anchor",anchor); n.setAttribute("font-weight",weight); n.textContent=t; svg.appendChild(n);};const line=(x1,y1,x2,y2,stroke,w=1.5,dash="")=>{const l=svgEl("line"); l.setAttribute("x1",x1); l.setAttribute("y1",y1); l.setAttribute("x2",x2); l.setAttribute("y2",y2); l.setAttribute("stroke",stroke); l.setAttribute("stroke-width",w);if(dash) l.setAttribute("stroke-dasharray",dash); svg.appendChild(l);};text(left.x+ left.w/2,20,"continuous-time s-plane","#374151",12,"middle","700");line(mapSx(0),mapSy(-2),mapSx(0),mapSy(2),"#9ca3af",1.8,"5 4");line(mapSx(-2),mapSy(0),mapSx(1),mapSy(0),"#6b7280",1.5);const stableShade =svgEl("rect"); stableShade.setAttribute("x",mapSx(-2)); stableShade.setAttribute("y",mapSy(2)); stableShade.setAttribute("width",mapSx(0)-mapSx(-2)); stableShade.setAttribute("height",mapSy(-2)-mapSy(2)); stableShade.setAttribute("fill","rgba(5,150,105,0.12)"); svg.appendChild(stableShade);const pole =svgEl("circle"); pole.setAttribute("cx",mapSx(a)); pole.setAttribute("cy",mapSy(0)); pole.setAttribute("r","6"); pole.setAttribute("fill","#2563eb"); svg.appendChild(pole);text(mapSx(-1), left.y+ left.h+18,"stable: Re(s) < 0","#059669",12);text(mapSx(a),mapSy(0) -10,`s = ${a.toFixed(2)}`,"#2563eb",11);text(right.x+ right.w/2,20,"discrete-time z-plane","#374151",12,"middle","700");line(mapZx(-1.6),mapZy(0),mapZx(1.6),mapZy(0),"#6b7280",1.5);line(mapZx(0),mapZy(-1.6),mapZx(0),mapZy(1.6),"#6b7280",1.5);const unit =svgEl("circle"); unit.setAttribute("cx",mapZx(0)); unit.setAttribute("cy",mapZy(0)); unit.setAttribute("r", right.w/3.2); unit.setAttribute("fill","rgba(5,150,105,0.08)"); unit.setAttribute("stroke","#059669"); unit.setAttribute("stroke-width","2"); svg.appendChild(unit);const z =Math.exp(a);const mode =svgEl("circle"); mode.setAttribute("cx",mapZx(z)); mode.setAttribute("cy",mapZy(0)); mode.setAttribute("r","6"); mode.setAttribute("fill","#f59e0b"); svg.appendChild(mode);text(right.x+ right.w/2, right.y+ right.h+18,"stable: |z| < 1","#059669",12);text(mapZx(z),mapZy(0) -10,`z ≈ ${z.toFixed(2)}`,"#b45309",11); caption.textContent="In continuous time, decay means the mode sits left of the imaginary axis. After sampling, the corresponding discrete-time mode must sit inside the unit circle. The region changes, but the stability idea does not."; } slider.addEventListener("input", redraw); container.append(controls, svg, caption);redraw();return container;}
65.5 The core method
A first pass through a discrete-time systems problem usually goes like this:
Identify the sampling period T_s and therefore the sampling frequency f_s.
Decide whether the data is a sampled version of a continuous signal, a genuinely discrete process, or a digital controller state.
Write the update rule or filter equation.
Interpret every term as memory, delay, or input contribution.
Check the relevant stability test: for the first-order case, the coefficient magnitude must be less than 1. For higher-order systems, stability requires all poles — the roots of the system’s characteristic equation — to lie inside the unit circle in the complex plane. This is the discrete-time counterpart of the left-half-plane test from continuous-time control.
Connect the algebra back to behaviour: smoothing, lag, oscillation, alias, or amplification.
The habit is the same one from the control chapter: do not leave the recurrence as pure symbol manipulation. Ask what it means for the sequence the machine is actually computing.
65.6 Worked example 1: a moving-average sensor filter
It smooths rapid sample-to-sample variation by replacing the present sample with the average of the present and previous two samples. If one sample spikes upward because of noise, the output spike is diluted by the neighbouring samples.
The price is delay. A filter that averages over time cannot react instantly to a real rapid change. This is the first recurring engineering tradeoff in discrete-time signal processing:
more smoothing usually means more lag
less lag usually means less smoothing
In embedded control, that tradeoff matters because a laggy filter can make an otherwise good controller feel slow or even unstable.
65.7 Worked example 2: discrete-time stability of a sampled update
Consider the update rule
y[n+1] = 0.6\,y[n] + u[n]
If the input is zero, then
y[n+1] = 0.6\,y[n]
Starting from y[0], repeated substitution gives
y[n] = 0.6^n y[0]
Because |0.6| < 1, the homogeneous response decays to zero. So the system’s memory fades over time.
Now compare with
y[n+1] = 1.1\,y[n] + u[n]
With zero input, the response is
y[n] = 1.1^n y[0]
and because |1.1| > 1, the sequence grows. A digital implementation with this update law is unstable.
In first-order discrete-time systems, the entire stability story is often carried by one number.
65.8 Worked example 3: aliasing in a seismic record
A geophysicist samples ground motion at f_s = 100 Hz. A strong sinusoidal component is present at f = 70 Hz.
This is above the Nyquist limit of
\frac{f_s}{2} = 50 \text{ Hz}
so the signal will alias. The discrete record cannot faithfully represent a 70 Hz oscillation at that sampling rate. Because the sampler runs at 100 Hz, a frequency f that exceeds the Nyquist limit folds back by the amount it overshoots the full sampling rate: the alias frequency is |f - f_s| = |70 - 100| = 30 Hz. In the sampled record, the 70 Hz component appears as a spurious 30 Hz oscillation.
The engineering lesson is not “compute a formula and move on.” It is that data collection itself is part of the mathematics. Sampling choices determine what questions the later analysis can answer honestly.
This is why signal processing belongs naturally beside estimation and inverse problems in Volume 8. If the measurement process already lost information, no later inference method can magically restore it.
65.9 Where this goes
The most direct continuation is Estimation, inverse problems, and filtering. Once a system is sampled, state estimation, filtering, and inference are usually carried out in discrete time. Kalman filtering, recursive estimation, and streaming data assimilation all inherit the language of recurrences, sampling, and noise that this chapter sets up.
This chapter sits beside control deliberately. Upper-year engineers constantly move between the continuous-time model of the plant and the discrete-time implementation of the controller. The control chapter is about shaping behaviour in continuous time. This one is about what happens when that shaping must be done on a clock.
TipApplications
digital filters for noisy sensors
sampled-data control in motors and robotics
audio and vibration signal analysis
seismic and geophysical trace processing
preprocessing streaming time-series data
telemetry, embedded systems, and real-time monitoring
65.10 Exercises
These are project-style exercises. Explain the behaviour as well as the calculation.
65.10.1 Exercise 1
A sensor is sampled every 0.01 s.
Find the sampling frequency.
Find the Nyquist frequency.
Decide whether a 30 Hz sinusoid can be represented without aliasing.
Decide whether a 70 Hz sinusoid can be represented without aliasing.
65.10.2 Exercise 2
Consider the discrete-time system
y[n+1] = 0.8\,y[n] + 2u[n]
Determine whether the homogeneous system is stable.
Starting from y[0] = 5 with u[n] = 0, compute y[1], y[2], and y[3].