Designing a Filter for a Signal in a Control Loop

Your controller is a noise amplifier. A speed loop that looks clean on paper turns encoder hash into actuator chatter, heat, and whine. The fix is a filter — but inside a feedback loop a filter is never free: every degree of lag it adds at your crossover comes straight out of your phase margin. This page builds the skill of placing that trade deliberately: the one-pole filter first, then the Butterworth/Bessel/Chebyshev families, then analog and digital realizations, ending with a seven-step recipe and a live loop you can push into oscillation.

1. The motor that buzzes

A PI loop holds a DC motor at 10 rad/s. The speed signal comes from an optical encoder: count the clicks, difference them every millisecond. On the scope, that "speed" is a fuzzy band — the true value plus junk of about ±0.3 rad/s. Differentiated quantization (every click is a tiny step), PWM ripple coupling in at 20 kHz, ground noise. Nothing is wrong with the control law. The problem is what the controller does to that junk: it multiplies whatever sits in the error by its gain and commands the result onto the motor. The 300 Hz slice of the hash alone, times the proportional gain of 0.31 V/(rad/s), is about 0.09 V of continuous chatter on a drive that should be sitting still.

00.050.10.150.20.250.3024681012time (s)speed (rad/s)the information (changes over ~10 ms)the junk (300 Hz – 20 kHz)
Figure 1. The measured speed of the motor. The information (the smooth ramp) and the noise (the fuzz) share the same amplitude scale, so no static gain can separate them — but they live at different frequencies.

Notice what the noise is not: it is not something you know, so you cannot subtract it, and it is not small, so you cannot ignore it. But you know its time scale. The real speed changes over tens of milliseconds; the junk wiggles hundreds to thousands of times per second. Separation by time scale is separation by frequency — and that is an opening you can exploit with a linear circuit or a few lines of code.

Intuition: a controller does not know signal from noise; it only sees the error. If the error jitters at 300 Hz, the plant receives 300 Hz. The only place to break that path is before the controller sees the junk.
Takeaway: signal and noise overlap in amplitude but not in frequency — that overlap gap is exactly what a filter exploits.

2. A filter is a gain that depends on frequency

You already think of signals as sums of sinusoids, so here is the whole idea in one sentence: a linear filter treats each sinusoid separately, multiplying it by \(|H(j\omega)|\) and shifting it by \(\angle H(j\omega)\). "Removing the noise" therefore has an exact meaning: choose \(|H| \approx 1\) where the information lives (below about 20 Hz here), \(|H| \approx 0\) where the junk lives (above 300 Hz), and transition as fast as convenient in between.

1101001k10k100k00.250.50.751frequency (Hz)gainsignal bandnoise bandthe filter we wantcorner
Figure 2. The design problem in one picture: pass the band where the signal lives, kill the band where the noise lives. The gold curve is the magnitude \(|H(j\omega)|\) we want; everything that follows is about realizing it and paying for it.

Everything on this page is about that gold curve and its hidden partner, the phase. You choose the corner frequency (where \(|H|\) starts falling), the steepness (how fast it falls), and the shape of the knee. The phase lag that comes along with them is not optional — it is the price, and §4 is about who collects it.

Takeaway: designing a filter means shaping \(|H(j\omega)|\); the corner and the steepness are your two free knobs, and the phase is the bill.

3. The one-pole filter: where should the corner go?

The simplest useful filter is one pole — the classic RC low-pass:

\[ H(s) \;=\; \frac{\omega_c}{s+\omega_c} \]

with the pole at \(-\omega_c\). Below the corner \(f_c = \omega_c/2\pi\), \(|H|\approx 1\). Above it, \(|H|\) falls at \(-20\) dB per decade: ten times the frequency, ten times less signal. The phase swings from 0° to \(-90°\), passing \(-45°\) exactly at the corner. One pole, three consequences, all visible in Figure 3: at 2× the corner you have already lost 7 dB of noise; at 5×, 14 dB; and the phase lag is already \(-5.7°\) at one tenth of the corner — remember that number for §4.

1101001k10k100k100-20-40-60|H| (dB)1101001k10k100k0-45-90frequency (Hz)∠H (deg)loop bandnoise band−20 dB/decade−3 dB−45° at the corner
Figure 3. One pole at 100 Hz: magnitude (top) and phase (bottom). The knee is a dial — move it and every number on this plot moves with it. The shaded bands are the loop band (blue, §4) and the noise band (red).

Drag the corner yourself and watch two things happen at the same time:

Demo 1 — Filter lab: drag the corner

The fuzz dies and the gold curve starts trailing the truth. The lag you see in the time domain is the phase of Figure 3 — same phenomenon, other picture. At DC the delay is \(1/\omega_c\): 1.6 ms at 100 Hz, 32 ms at 5 Hz. So far this is a mere nuisance. The next section is where it gets expensive.

Takeaway: the corner frequency is a budget allocation — noise rejection on one side, lag on the other — and you have not yet met the party that collects the lag.

4. The catch: this filter lives inside your loop

Up to now the filter was a signal-processing box sitting on a bench. In the loop it becomes part of the dynamics. The loop transmission — the gain-around-the-loop that decides stability — changes from \(L = CP\) to

\[ L(s) \;=\; \underbrace{C(s)\,P(s)}_{\text{controller, plant}}\;\cdot\; \underbrace{H(s)}_{\text{your filter}} \]

Whatever the filter does to the signal, it now also does to the loop.

Loop facts in one breath. \(|L|\) is the gain around the loop; the crossover is the frequency where \(|L|=1\) (0 dB); the loop's phase at crossover decides everything: \( \mathrm{PM} = 180° + \angle L(\text{crossover}) \). PM is how many extra degrees of lag flip the loop into oscillation. Below crossover the loop tracks and corrects; near crossover it is on the edge; above it, too slow to react. Healthy designs keep PM around 45–60°.

Now put the filter in. Its magnitude at crossover is \(\approx 1\) — with the corner 5× above crossover, \(|H|\) there is 0.999+, and the crossover barely moves (20.00 → 19.98 Hz). But its phase at crossover is pure loss:

1101001k10k6040200-20-40-60-80|L| (dB)1101001k10k0-90-180-270frequency (Hz)∠L (deg)PM 87.7°PM 71.3°PM 23.4°no filter+ 100 Hz filter+ 25 Hz filtercrossover: |L| = 1
Figure 4. The loop transmission without (gray) and with a 100 Hz two-pole filter (gold), and with a 25 Hz filter (dashed). The magnitude panel barely changes; the phase panel tells the story: at the 20 Hz crossover the 100 Hz filter quietly spends 16.4° of margin (87.7° → 71.3°), and a 25 Hz filter spends 64°.

Here is the whole design problem in one sentence: noise wants the corner low, stability wants the corner high, and the space between your crossover and your noise band is the room you have to work in. The rules of thumb fall straight out of the geometry: keep the corner at 5–10× the crossover, which costs a one-pole filter \(57.3/\text{ratio}\) degrees and a 2-pole Butterworth about \(81/\text{ratio}\) degrees at crossover — 16.4° for the 100 Hz design. Spend only what you have: with a 60° margin, a 10–15° spend is tolerable; 30° is asking for ringing.

Demo 2 — The margin budget (this loop: crossover 20 Hz, PM 87.7°)

Slide the ratio down to 1.5 and watch the verdict flip to oscillates: at a 1× ratio the filter alone contributes \(-90°\) at crossover — the margin is gone before the loop even starts. This is why "just filter the measurement" is the most classic way to destabilize an otherwise healthy loop.

Takeaway: every dB of noise rejection is bought with degrees of phase margin, and the exchange rate is set by the corner-to-crossover ratio.

5. Sharper knees: more poles, and the families

One pole buys \(-20\) dB/decade. The 100 Hz one-pole design leaves the 300 Hz hash only 14 dB down — often not enough. Cascade \(n\) poles and the stopband steepens to \(-n\cdot 20\) dB/decade:

|H| at \(n\,\times\) the cornern = 1n = 2n = 3n = 4
2×−7.0 dB−12.3 dB−18.1 dB−24.1 dB
5×−14.1 dB−28.0 dB−41.9 dB−55.9 dB
10×−20.0 dB−40.0 dB−60.0 dB−80.0 dB

The bill grows with the order (each pole adds lag near and below the corner), and one free choice remains: where exactly the \(n\) poles sit in the s-plane. The classical answers have names and personalities — same corner, same order, different placements:

1101001k10k0-20-40-60-80|H| (dB)1101001k10k0-90-180-270frequency (Hz)∠H (deg)ButterworthChebyshev 1 dBBesselsame corner, same order — different pole placement
Figure 5. Three ways to place three poles, all with \(-3\) dB at 100 Hz. Butterworth: poles equally spaced on a circle — flattest passband, the sane default. Bessel: placed for flattest delay — cleanest step, laziest skirt. Chebyshev: passband ripple (visible) buys the steepest skirt.
051015202530354000.250.50.7511.25time (ms)step responseButterworth +8.2%Chebyshev +6.4%, longest tailBessel +0.8% — flat delay, clean step
Figure 6. The same three filters, step response. The magnitude plot's skirt steepness is paid in the time domain: Bessel barely overshoots, Chebyshev rings the longest despite overshooting less than Butterworth.

For design work you need one family by heart — Butterworth. Its poles sit on a circle, \( s_k = \omega_c\, e^{\,j(2k+n-1)\pi/(2n)} \), which produces the factored forms:

orderButterworth \(H(s)\), corner \(\omega_c\)
1\(\dfrac{\omega_c}{s+\omega_c}\)
2\(\dfrac{\omega_c^2}{s^2+\sqrt{2}\,\omega_c s+\omega_c^2}\)
3\(\dfrac{\omega_c^3}{(s+\omega_c)(s^2+\omega_c s+\omega_c^2)}\)
4\(\dfrac{\omega_c^4}{(s^2+0.765\,\omega_c s+\omega_c^2)(s^2+1.848\,\omega_c s+\omega_c^2)}\)

And to turn an attenuation spec into an order \(n\) (Butterworth): \( n \ge \log_{10}(10^{A/10}-1)\,\big/\,2\log_{10}(\omega_s/\omega_c) \). For "\(-20\) dB at 3× the corner": \(n \ge \log_{10}(99)/(2\times 0.477) = 2.1\), so \(n=3\). (Check the table: \(n=2\) would deliver only \(-19.1\) dB — almost.)

Takeaway: order sets the stopband slope, the family sets the shape, and the \(n=2\) Butterworth row of the table is the one to memorize.

6. Building it: op-amps or code

The poles are the design; the schematic or the code is just a skin over them. For one 2nd-order analog section the workhorse is the Sallen–Key filter. With equal resistors and capacitors its corner is \(\omega_c = 1/RC\), and the op-amp's gain \(K\) sets the damping: \(Q = 1/(3-K)\). Butterworth needs \(Q = 1/\sqrt{2}\), so \(K = 3-\sqrt{2} = 1.586\). With \(C = 100\) nF and \(R = 15.92\) kΩ: corner = 100.1 Hz. Higher orders cascade sections; filter tables and tools fill in the values.

VinR = 15.92 kΩR = 15.92 kΩC = 100 nF+−VoutC = 100 nF5.86 kΩ10 kΩK = 1 + 5.86/10 = 1.586  (sets Q = 0.707)
Figure 7. A Sallen–Key low-pass realizing the 2nd-order Butterworth section: \(R_1=R_2=R\), \(C_1=C_2=C\), gain \(K=1.586\) from the \(R_a/R_b\) divider. The two resistors and two capacitors place the same pole pair the table specifies.

If it runs as code, something sampled first — and sampling folds

A digital filter sees the world only at sample times, and every frequency above \(f_s/2\) folds back below it. A 600 Hz sinusoid sampled at 1 kHz is indistinguishable from a 400 Hz one. The nasty party trick: 20 kHz PWM ripple sampled at 1 kHz folds to exactly 0 Hz — a phantom DC "speed error" that your integrator will faithfully chase forever.

012345-101time (ms)amplitude600 Hz as the ADC sees it,sampled at 1 kHzwhat those samples say: 400 Hz
Figure 8. Aliasing. The gray 600 Hz sinusoid and the red 400 Hz sinusoid produce identical samples at 1 kHz. Whatever you want the digital filter to remove must be removed before sampling.

That is why real products have both an analog and a digital filter: a cheap RC before the ADC — here \(\tau = 200\,\mu s\), corner 796 Hz — takes \(-28\) dB out of the 20 kHz ripple (killing the fold-back junk) while barely touching 300 Hz (\(-0.6\) dB, leaving it for the digital stage). The analog RC is the bodyguard; the digital filter is the surgeon.

Recipe A: the one-pole workhorse, derived in three lines

Discretize \(\tau\dot{y} = x-y\) over one sample time \(T\):

\[ y[k] \;\approx\; y[k-1] + \tfrac{T}{\tau}\big(x[k]-y[k-1]\big) \;\;\;\Longrightarrow\;\;\; y[k] \;=\; \alpha\, y[k-1] + (1-\alpha)\, x[k], \qquad \alpha = e^{-T/\tau} \]

With \(\tau = 1.59\) ms (100 Hz) and \(T = 1\) ms: \(\alpha = 0.5335\). Three lines of C; the realized \(-3\) dB corner is 103.5 Hz, within 3.5% of target.

y[k] = alpha*y[k-1] + (1-alpha)*x[k];   // alpha = exp(-2*pi*f_c*T)

Recipe B: any s-domain design → code, mechanically

For higher orders, substitute the bilinear transform into your s-domain \(H(s)\) and simplify:

\[ s \;\longleftarrow\; \frac{2}{T}\,\frac{z-1}{z+1} \]

It maps the whole left half-plane into the unit circle, so stability survives, and every design becomes a difference equation. Its one quirk: frequencies compress, so a 100 Hz design lands at 96.9 Hz when \(f_s = 1\) kHz. The fix is to prewarp — design the s-domain corner at \((2/T)\tan(\omega T/2) = 103.43\) Hz, and the realized filter hits 100.00 Hz exactly. (Below \(f_s/10\), nobody bothers.) Doing that to the 2nd-order Butterworth section gives, verified:

\[ H(z) \;=\; \frac{0.067455 + 0.134911\,z^{-1} + 0.067455\,z^{-2}} {1 \;-\; 1.142981\,z^{-1} \;+\; 0.412802\,z^{-2}} \]
y[k] = 0.067455*x[k] + 0.134911*x[k-1] + 0.067455*x[k-2]
     + 1.142981*y[k-1] - 0.412802*y[k-2];
Demo 3 — Analog design → digital code (corner fixed at 100 Hz)

Gold is the continuous design; blue dots are what the microcontroller actually produces. Even at 800 Hz sampling — eight samples per corner period — the dots sit on the curve. The recipe is mechanical, and it works.

Takeaway: same poles, two skins — an RC and an op-amp, or six multiply-adds per sample; the s-domain design is the asset, and the anti-alias RC in front of the ADC is not optional.

7. The whole design, end to end

Everything so far, as a recipe you can run on any noisy measurement feeding a controller:

  1. Know the noise. Scope + FFT, or subtract a heavily filtered copy and look at the difference. Where in frequency, how big? (Here: hash ≥ 300 Hz, ±0.3 rad/s, PWM line at 20 kHz.)
  2. Know the loop. Crossover and margin, measured or designed. (20 Hz, PM 87.7°.)
  3. Place the corner at 5–10× crossover. (100 Hz → 16.4° spent, 71.3° left.)
  4. Pick the order from the attenuation spec. (\(-19\) dB at 300 Hz from \(n=2\); the actuator ripple drops 86 mV → 7.3 mV — verified both analytically and in simulation.)
  5. Pick the family. Butterworth, always, until you have a reason not to.
  6. Realize it: Sallen–Key (15.92 kΩ, 100 nF, \(K=1.586\)) or the bilinear biquad at 1 kHz — plus the 200 µs anti-alias RC before the ADC.
  7. Re-verify the loop. Step the reference and the load; watch overshoot (1.6% → 2.2%, the lag tax) and actuator ripple (86 → 7.3 mV RMS). The design is finished when the loop passes, not when the magnitude plot looks right.
Demo 4 — The loop, end to end: try to kill it

Slide the corner down from 100 Hz and watch the loop pay: at 50 Hz the step overshoots 16%; at 30 Hz, 47%; at 25 Hz it rings on every step; below about 17 Hz the margin is gone entirely and the loop howls — with the same controller gains. The filter did it, through phase alone. Then slide back up and watch the actuator panel go quiet: that is the 86 mV → 7 mV payoff.

Takeaway: the seven steps are a loop — step 7 feeds back into step 3 — and the loop you must not skip is the one with your filter inside it.

8. Where this connects

Resources

The whole skill in one line: put the corner between the crossover and the noise, pay for steepness with order and for placement with margin, and verify the loop — not the plot.