Chapter 26: Linear Filtering in Control Systems
Lesson 1: Basics of Linear Filters in the s-Plane
This lesson introduces continuous-time linear filters as linear time-invariant (LTI) systems described by transfer functions in the complex \( s \)-plane. We connect pole–zero locations to basic filtering behaviors (low-pass, high-pass) and show how first-order filters arise from simple differential equations. Finally, we implement a first-order low-pass filter in Python, C++, Java, MATLAB/Simulink, and Wolfram Mathematica, with remarks on their integration in robotic control software.
1. Motivation — Filters inside Feedback Loops
In previous chapters you have seen continuous-time LTI models \( G(s) \) of plants and controllers \( C(s) \). In a real feedback system, measured signals are corrupted by sensor noise and high-frequency dynamics not present in the nominal model. A linear filter is an LTI system \( F(s) \) inserted in the loop to shape the frequency content of signals.
Typical uses in control:
- Sensor noise filtering: attenuate high-frequency noise before feeding the measurement to the controller.
- Derivative filtering: make the derivative part of a PID implementable and noise-robust.
- Anti-aliasing / prefiltering: restrict the bandwidth of continuous-time signals before sampling.
Consider a standard unity-feedback loop with a filter in the measurement path. In the Laplace domain:
\[ U(s) = C(s)\bigl(R(s) - Y_f(s)\bigr), \quad Y(s) = G(s)U(s), \quad Y_f(s) = F(s)Y_m(s) \]
where \( Y_m(s) \) is the noisy measurement, and \( F(s) \) is the filter. The choice of \( F(s) \) is conveniently understood by its poles and zeros in the \( s \)-plane.
flowchart TD
R["Reference r"] --> E["Error e = r - y_f"]
E --> C["Controller C(s)"]
C --> U["Actuator input u"]
U --> P["Plant G(s)"]
P --> Y["True output y"]
Y --> N["Sensor + noise"]
N --> F["Filter F(s)"]
F --> YF["Filtered output y_f"]
YF --> E
In this lesson we focus on \( F(s) \) itself, treating it as a small LTI subsystem whose behavior can be predicted from its poles and zeros in the \( s \)-plane.
2. LTI Filters as Transfer Functions in the s-Plane
A continuous-time linear filter is an LTI system with input \( u(t) \) and output \( y(t) \). Assuming zero initial conditions, its behavior is described in the Laplace domain by a transfer function
\[ F(s) = \frac{Y(s)}{U(s)} = \frac{b_m s^m + b_{m-1}s^{m-1} + \dots + b_0}{ s^n + a_{n-1}s^{n-1} + \dots + a_0}, \quad a_0 \neq 0. \]
The denominator polynomial defines the poles of the filter and the numerator polynomial defines its zeros. We write
\[ F(s) = K \frac{\prod_{i=1}^{m} (s - z_i)}{\prod_{k=1}^{n} (s - p_k)}, \quad K \in \mathbb{R}, \]
where \( z_i \) are zeros and \( p_k \) are poles.
For filters used in feedback control, we almost always require:
- Causality: denominator degree \( n \) is at least numerator degree \( m \).
- Strict properness: \( n > m \), so that \( F(s) \) decays at high frequencies and does not amplify noise unboundedly.
- Internal stability: all poles satisfy \( \Re(p_k) < 0 \).
The \( s \)-plane representation is central: a filter is a point-set of poles and zeros in the complex plane, and its frequency response is obtained by evaluating \( F(s) \) on the imaginary axis \( s = \mathrm{j}\omega \).
\[ F(\mathrm{j}\omega) = \left. F(s) \right|_{s = \mathrm{j}\omega}, \quad \omega \in \mathbb{R}. \]
3. Poles, Zeros, and Basic Filtering Behavior
Consider a first-order strictly proper LTI system with one real pole
\[ F(s) = \frac{K}{s - p}, \quad p \in \mathbb{C}, \quad \Re(p) < 0. \]
Evaluating on the imaginary axis gives
\[ F(\mathrm{j}\omega) = \frac{K}{\mathrm{j}\omega - p} = \frac{K}{-\Re(p) + \mathrm{j}(\omega - \Im(p))}. \]
The magnitude and phase are
\[ \bigl|F(\mathrm{j}\omega)\bigr| = \frac{|K|}{\sqrt{\Re(p)^2 + (\omega - \Im(p))^2}}, \quad \angle F(\mathrm{j}\omega) = \arctan\!\left(\frac{\omega - \Im(p)}{-\Re(p)}\right) + \angle K. \]
If \( p \) is a negative real number, \( p = -\omega_c < 0 \), we obtain
\[ F(s) = \frac{K}{s + \omega_c}, \quad \omega_c > 0. \]
Then,
\[ \bigl|F(\mathrm{j}\omega)\bigr| = \frac{|K|}{\sqrt{\omega^2 + \omega_c^2}}, \quad \angle F(\mathrm{j}\omega) = -\arctan\!\left(\frac{\omega}{\omega_c}\right) + \angle K. \]
For small frequencies \( \omega \ll \omega_c \), the magnitude is approximately constant \( |F(\mathrm{j}\omega)| \approx |K|/\omega_c \), while for large frequencies \( \omega \gg \omega_c \) it decays approximately as \( 1/\omega \). This is the hallmark of a low-pass behavior.
Conversely, placing a zero at the origin and a pole at \( -\omega_c \) leads to a high-pass behavior. The role of the \( s \)-plane is thus geometric:
- Pole near the origin → slow dynamics and high low-frequency gain.
- Pole far left (large negative real part) → fast decay and less impact on low frequencies.
- Zeros near the origin → attenuation or amplification around \( \omega \approx 0 \).
4. Canonical First-Order Filter Prototypes
In control engineering, normalized first-order filter prototypes are widely used. Let \( \omega_c > 0 \) be the desired cutoff (corner) angular frequency and \( \tau = 1/\omega_c \) the corresponding time constant.
4.1 First-order low-pass filter
The canonical first-order low-pass filter is
\[ F_{\mathrm{LP}}(s) = \frac{\omega_c}{s + \omega_c} = \frac{1}{\tau s + 1}. \]
It has a single pole at \( s = -\omega_c \) and no finite zeros. Its step response satisfies the differential equation
\[ \tau \frac{\mathrm{d}y(t)}{\mathrm{d}t} + y(t) = u(t), \]
which you can solve using standard methods for first-order linear ODEs.
4.2 First-order high-pass filter
The canonical first-order high-pass filter with the same cutoff \( \omega_c \) is
\[ F_{\mathrm{HP}}(s) = \frac{s}{s + \omega_c} = \frac{\tau s}{\tau s + 1}. \]
Here we have a zero at the origin \( s = 0 \) and a pole at \( s = -\omega_c \). The high-pass nature is visible from the magnitude response:
\[ \bigl|F_{\mathrm{HP}}(\mathrm{j}\omega)\bigr| = \frac{\omega}{\sqrt{\omega^2 + \omega_c^2}}. \]
Thus \( |F_{\mathrm{HP}}(\mathrm{j}\omega)| \approx 0 \) for \( \omega \ll \omega_c \), and it tends to \( 1 \) for \( \omega \gg \omega_c \).
These prototypes are the building blocks for more complex filters (band-pass, notch) that will be studied in the next lesson.
5. Example — Robot Joint Sensor Low-Pass Filter
Consider a single robot joint with angular position \( \theta(t) \). The encoder measurement is corrupted by additive high-frequency noise \( n(t) \):
\[ y_m(t) = \theta(t) + n(t). \]
Suppose we insert a first-order low-pass filter in the measurement path:
\[ F_{\mathrm{LP}}(s) = \frac{\omega_c}{s + \omega_c}, \quad Y_f(s) = F_{\mathrm{LP}}(s)\,Y_m(s). \]
The filtered measurement is
\[ Y_f(s) = F_{\mathrm{LP}}(s)\Theta(s) + F_{\mathrm{LP}}(s)N(s). \]
Choosing \( \omega_c \) much larger than the dominant bandwidth of \( \theta(t) \) but smaller than the main noise frequencies attenuates noise while preserving relevant motion dynamics.
flowchart TD
S["Specify application (e.g. joint sensor)"] --> B["Estimate signal bandwidth"]
B --> N["Estimate noise bandwidth"]
N --> C["Choose cutoff w_c between signal and noise bands"]
C --> M["Map w_c to pole at s = -w_c"]
M --> V["Verify performance via simulation"]
V --> R["Refine w_c if needed"]
The decision flow above is typical in practice: the qualitative requirement "smooth noisy encoder" becomes a quantitative placement of a pole at \( s = -\omega_c \) in the \( s \)-plane.
6. Programming Lab — Implementing a First-Order Low-Pass Filter
In continuous time, the normalized low-pass filter satisfies
\[ \tau \dot{x}(t) + x(t) = u(t), \quad y(t) = x(t), \]
with \( \tau = 1/\omega_c \). For implementation in digital controllers (e.g., on a robot), we approximate this ODE with a discrete-time update using sampling period \( \Delta t \). A simple (forward Euler) scheme is
\[ x_{k+1} = x_k + \Delta t\left( -\frac{1}{\tau} x_k + \frac{1}{\tau} u_k \right), \quad y_k = x_k. \]
Below we implement this in several languages often used in robotics.
6.1 Python (with control and robotics context)
import numpy as np
class FirstOrderLowPass:
def __init__(self, tau, x0=0.0):
"""
tau: time constant (seconds)
x0: initial state
"""
self.tau = float(tau)
self.x = float(x0)
def update(self, u, dt):
"""
Forward-Euler integration of tau * x_dot + x = u.
u : current input sample
dt: sampling period
"""
a = -1.0 / self.tau
b = 1.0 / self.tau
self.x = self.x + dt * (a * self.x + b * u)
return self.x
# Example: filtering a noisy joint position signal
if __name__ == "__main__":
tau = 0.05 # 50 ms time constant
dt = 0.001 # 1 kHz loop
lp = FirstOrderLowPass(tau)
# Simulate 1 s of data for a robot joint moving with a ramp + noise
t = np.arange(0.0, 1.0, dt)
true_theta = 0.5 * t # rad
noise = 0.02 * np.random.randn(len(t))
y_meas = true_theta + noise
y_filt = np.zeros_like(y_meas)
for k in range(len(t)):
y_filt[k] = lp.update(y_meas[k], dt)
# Inspect Bode magnitude of the underlying analog prototype using python-control
try:
import control # pip install control
s = control.TransferFunction.s
wc = 1.0 / tau
F_lp = wc / (s + wc)
mag, phase, omega = control.bode(F_lp, dB=True, omega_limits=(1.0, 1e3), Plot=False)
# At this point you could integrate with robotics toolboxes such as
# roboticstoolbox-python to validate the filter on full robot models.
except ImportError:
print("python-control not installed; skipping Bode computation.")
In a Python-based robotics stack (e.g., ROS with
rospy plus python-control or
roboticstoolbox-python), this class can be called inside
the main control loop for each joint sensor.
6.2 C++ (ROS-style real-time loop)
#include <cmath>
struct FirstOrderLowPass {
double tau;
double x;
explicit FirstOrderLowPass(double tau_, double x0 = 0.0)
: tau(tau_), x(x0) {}
double update(double u, double dt) {
// tau * x_dot + x = u
const double a = -1.0 / tau;
const double b = 1.0 / tau;
x += dt * (a * x + b * u);
return x;
}
};
// Example usage in a robotics control loop
// (e.g., inside a ROS node using ros::Rate or a real-time timer).
void controlLoopExample() {
FirstOrderLowPass encoder_filter(0.02); // 20 ms time constant
double dt = 0.001; // 1 kHz loop
double encoder_meas = 0.0;
for (int k = 0; k < 10000; ++k) {
// Read noisy encoder measurement from hardware / middleware
// encoder_meas = readEncoderJointPosition();
double y_filt = encoder_filter.update(encoder_meas, dt);
// Use y_filt in the feedback law instead of raw encoder_meas
// tau_c = computeControlTorque(y_filt);
}
}
// In a ROS-based robot, such a filter integrates with ros_control or custom
// controllers by wrapping the update call inside the controller::update() method.
6.3 Java (filter class for robotics frameworks)
public class FirstOrderLowPass {
private final double tau;
private double x;
public FirstOrderLowPass(double tau, double x0) {
this.tau = tau;
this.x = x0;
}
public FirstOrderLowPass(double tau) {
this(tau, 0.0);
}
/**
* Update the filter state using forward-Euler integration.
* @param u current input sample
* @param dt sampling period (seconds)
* @return filtered output y_k
*/
public double update(double u, double dt) {
double a = -1.0 / tau;
double b = 1.0 / tau;
x = x + dt * (a * x + b * u);
return x;
}
}
// Example usage (e.g., in a Java-based robot controller such as WPILib robot code):
// FirstOrderLowPass lp = new FirstOrderLowPass(0.03);
// double yFilt = lp.update(encoderMeasurement, dt);
In Java-based robotics platforms (e.g., WPILib for mobile robots), such a class can be part of the subsystem code that preprocesses sensor signals before they are used in feedback laws.
6.4 MATLAB / Simulink
% Parameters
tau = 0.05; % time constant
wc = 1/tau; % cutoff frequency
s = tf('s');
F_lp = wc / (s + wc); % continuous-time low-pass filter
% Time-domain simulation on a noisy signal
dt = 0.001;
t = 0:dt:1;
theta_true = 0.5 * t;
noise = 0.02 * randn(size(t));
y_meas = theta_true + noise;
y_filt = lsim(F_lp, y_meas, t);
% Plot
figure;
plot(t, y_meas, 'Color', [0.8 0.8 0.8]); hold on;
plot(t, y_filt, 'LineWidth', 1.5);
plot(t, theta_true, '--', 'LineWidth', 1.5);
legend('measured', 'filtered', 'true');
xlabel('t [s]');
ylabel('\theta [rad]');
title('First-order low-pass filter on encoder signal');
% In Simulink:
% - Use a "Transfer Fcn" block with numerator [wc] and denominator [1 wc].
% - Place this block in the measurement path before the controller input.
% - Robotics System Toolbox can then connect the Simulink controller to a
% robot model or a ROS network for full closed-loop simulations.
6.5 Wolfram Mathematica
tau = 0.05;
wc = 1/tau;
(* Continuous-time transfer function *)
fLP = TransferFunctionModel[wc/(s + wc), s];
(* Bode magnitude and phase *)
BodePlot[fLP, {1, 10^3},
GridLines -> Automatic,
PlotLayout -> "MagnitudePhase"
];
(* Time response to noisy joint signal *)
dt = 0.001;
t = Range[0, 1, dt];
thetaTrue = 0.5 t;
noise = 0.02 RandomVariate[NormalDistribution[0, 1], Length[t]];
yMeas = thetaTrue + noise;
yFilt = OutputResponse[fLP, yMeas, t];
ListLinePlot[
{
Transpose[{t, yMeas}],
Transpose[{t, yFilt}],
Transpose[{t, thetaTrue}]
},
PlotLegends -> {"measured", "filtered", "true"},
AxesLabel -> {"t [s]", "\[Theta] [rad]"}
]
Mathematica can be coupled with external robotics simulators or middleware, but even in isolation it is useful for symbolic and numeric exploration of \( s \)-plane filter designs before implementing them in embedded code.
7. Problems and Solutions
Problem 1 (Classifying a filter from its transfer function). Consider the transfer function
\[ F(s) = \frac{10}{s + 10}. \]
(a) List its poles and zeros. (b) Is it proper or strictly proper? (c) Is it internally stable? (d) Does it behave as a low-pass or high-pass filter?
Solution:
- (a) Denominator zero at \( s = -10 \), so one pole at \( p = -10 \). No finite zeros.
- (b) Numerator degree \( m = 0 \), denominator degree \( n = 1 \), so the system is strictly proper.
- (c) The single pole satisfies \( \Re(p) = -10 < 0 \), so the filter is internally stable.
- (d) It is a first-order low-pass filter with cutoff \( \omega_c = 10 \,\mathrm{rad/s} \).
Problem 2 (Magnitude response of a low-pass prototype). For the normalized low-pass filter
\[ F_{\mathrm{LP}}(s) = \frac{\omega_c}{s + \omega_c}, \]
derive \( |F_{\mathrm{LP}}(\mathrm{j}\omega)| \) and show that at \( \omega = \omega_c \) the magnitude is \( 1/\sqrt{2} \) of the low-frequency gain.
Solution: Substitute \( s = \mathrm{j}\omega \):
\[ F_{\mathrm{LP}}(\mathrm{j}\omega) = \frac{\omega_c}{\mathrm{j}\omega + \omega_c}. \]
The magnitude is
\[ |F_{\mathrm{LP}}(\mathrm{j}\omega)| = \frac{\omega_c}{\sqrt{\omega^2 + \omega_c^2}}. \]
For \( \omega \ll \omega_c \), \( |F_{\mathrm{LP}}(\mathrm{j}\omega)| \approx \omega_c/\omega_c = 1 \). At the cutoff frequency \( \omega = \omega_c \):
\[ |F_{\mathrm{LP}}(\mathrm{j}\omega_c)| = \frac{\omega_c}{\sqrt{\omega_c^2 + \omega_c^2}} = \frac{\omega_c}{\sqrt{2}\,\omega_c} = \frac{1}{\sqrt{2}}. \]
Thus the magnitude is reduced to \( 1/\sqrt{2} \) of its low-frequency value, corresponding to \( -3 \) dB.
Problem 3 (Designing a time constant from bandwidth). A robot joint controller operates with significant dynamics up to \( 20\,\mathrm{rad/s} \). You wish to add a first-order low-pass filter in the encoder measurement path that attenuates noise above roughly \( 100\,\mathrm{rad/s} \) while leaving the main joint motion essentially unaffected. Choose a reasonable cutoff frequency \( \omega_c \) and compute the corresponding time constant \( \tau \).
Solution: To avoid distorting the joint motion, choose \( \omega_c \) well above \( 20\,\mathrm{rad/s} \) but below the main noise band. A simple compromise is \( \omega_c = 80\,\mathrm{rad/s} \). Then
\[ \tau = \frac{1}{\omega_c} = \frac{1}{80} \approx 0.0125\,\mathrm{s}. \]
This time constant is short compared to the dominant joint dynamics but still filters higher frequencies effectively.
Problem 4 (High-pass filter classification). Consider
\[ F(s) = \frac{s}{s + 50}. \]
(a) Identify the pole and zero. (b) Determine whether this filter is low-pass or high-pass. (c) Compute \( |F(\mathrm{j}\omega)| \) and the limit as \( \omega \to 0 \).
Solution:
- (a) Zero at \( s = 0 \), pole at \( s = -50 \).
- (b) The zero at the origin suppresses low frequencies, while the pole stabilizes the system; this is a standard high-pass filter.
-
(c)
\[ F(\mathrm{j}\omega) = \frac{\mathrm{j}\omega}{\mathrm{j}\omega + 50}, \quad |F(\mathrm{j}\omega)| = \frac{\omega}{\sqrt{\omega^2 + 50^2}}. \]
As \( \omega \to 0 \), \( |F(\mathrm{j}\omega)| \to 0 \), confirming high-pass behavior.
Problem 5 (Stability constraint for filter design). You want to design a first-order filter of the form
\[ F(s) = \frac{K}{s - p} \]
to be used in a feedback loop. What constraint must hold on \( p \) for internal stability? Express the condition in terms of \( \Re(p) \), and interpret it in the \( s \)-plane.
Solution: Internal stability requires that all poles of \( F(s) \) lie in the open left half-plane. Thus
\[ \Re(p) < 0. \]
Geometrically, the point \( p \) must lie strictly to the left of the imaginary axis in the \( s \)-plane.
8. Summary
In this lesson we formalized linear filters as continuous-time LTI systems described by transfer functions \( F(s) \) with poles and zeros in the \( s \)-plane. We emphasized strict properness and pole locations as basic design constraints, and showed how first-order low-pass and high-pass filters arise from simple one-pole prototypes. By evaluating \( F(s) \) on the imaginary axis, we related pole locations to magnitude and phase behavior, clarifying why a pole at \( s = -\omega_c \) induces a low-pass response with cutoff \( \omega_c \). Finally, we implemented a first-order low-pass filter numerically in Python, C++, Java, MATLAB/Simulink, and Mathematica, in a form suitable for integration into robotic control software. Subsequent lessons will extend these ideas to multi-pole and multi-zero filters (band-pass, notch) and their roles in practical control architectures.
9. References
- Bode, H. W. (1945). Network analysis and feedback amplifier design. Van Nostrand.
- Zadeh, L. A. (1950). Frequency analysis of variable networks. Proceedings of the IRE, 38(3), 291–299.
- Butterworth, S. (1930). On the theory of filter amplifiers. Experimental Wireless & the Wireless Engineer, 7, 536–541.
- Cauer, W. (1931). Die Verwirklichung der Wechselstromwiderstände vorgeschriebener Frequenzabhängigkeit. Archiv für Elektrotechnik, 18, 355–388.
- Horowitz, I. M. (1963). Synthesis of feedback systems. Academic Press. (Chapters on linear filters in feedback loops.)
- Kailath, T. (1967). The divergence and shaping integrals of linear systems. IEEE Transactions on Automatic Control, 12(3), 244–252.
- Oppenheim, A. V., & Schafer, R. W. (1975). Digital filters. Prentice Hall. (Continuous-time prototypes and their mapping to digital filters.)
- Guillemin, E. A. (1957). Synthesis of passive networks. Wiley. (Classical pole–zero based filter synthesis.)
- Zames, G. (1966). On the input-output stability of time-varying nonlinear feedback systems. Part I: Conditions derived using concepts of loop gain, conicity, and positivity. IEEE Transactions on Automatic Control, 11(2), 228–238. (Foundational ideas for loop shaping.)