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

  1. Bode, H. W. (1945). Network analysis and feedback amplifier design. Van Nostrand.
  2. Zadeh, L. A. (1950). Frequency analysis of variable networks. Proceedings of the IRE, 38(3), 291–299.
  3. Butterworth, S. (1930). On the theory of filter amplifiers. Experimental Wireless & the Wireless Engineer, 7, 536–541.
  4. Cauer, W. (1931). Die Verwirklichung der Wechselstromwiderstände vorgeschriebener Frequenzabhängigkeit. Archiv für Elektrotechnik, 18, 355–388.
  5. Horowitz, I. M. (1963). Synthesis of feedback systems. Academic Press. (Chapters on linear filters in feedback loops.)
  6. Kailath, T. (1967). The divergence and shaping integrals of linear systems. IEEE Transactions on Automatic Control, 12(3), 244–252.
  7. Oppenheim, A. V., & Schafer, R. W. (1975). Digital filters. Prentice Hall. (Continuous-time prototypes and their mapping to digital filters.)
  8. Guillemin, E. A. (1957). Synthesis of passive networks. Wiley. (Classical pole–zero based filter synthesis.)
  9. 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.)