Chapter 20: Two-Degree-of-Freedom (2-DOF) Linear Controllers

Lesson 3: Reference Pre-Filters for Improved Tracking

In this lesson we study reference pre-filters in 2-DOF linear controllers. A reference pre-filter is a causal transfer function inserted in the reference path to shape the closed-loop tracking response without modifying disturbance rejection or noise attenuation of the feedback loop. We derive closed-loop input–output relations, present model-matching design formulas, and implement pre-filters in Python, C++, Java, MATLAB/Simulink, and Wolfram Mathematica, with emphasis on robotic servo applications.

1. Conceptual Overview of Reference Pre-Filters

Consider a standard unity-feedback loop with plant \( P(s) \) and controller \( C(s) \). In a 1-DOF structure, the reference \( r(t) \) is compared directly with the output \( y(t) \), and the controller sees the error \( e(t) = r(t) - y(t) \). A reference pre-filter is an additional, stable, proper transfer function \( F(s) \) placed in series with the reference before the summing junction.

Working in the Laplace domain, denote the Laplace transforms by capital letters. Let \( D(s) \) be a disturbance added at the plant input. The loop with pre-filter \( F(s) \) has the structure illustrated conceptually below.

flowchart TD
  R["Reference r"] --> F["Pre-filter F(s)"]
  F --> SUM["Summing node: r_f - y"]
  SUM --> C["Controller C(s)"]
  C --> P["Plant P(s)"]
  P --> Y["Output y"]
  Y -->|feedback| SUM
  D["Disturbance d"] --> P
        

Denote the loop transfer function by \( L(s) = C(s)P(s) \). A key property of reference pre-filters is that they only modify how the reference is injected into the loop; they do not change the feedback denominator \( 1 + L(s) \) and therefore do not affect closed-loop pole locations.

2. Closed-Loop Relations with a Reference Pre-Filter

We now derive the closed-loop transfer functions rigorously.

Let the filtered reference be \( R_f(s) = F(s)R(s) \). The error signal is

\[ E(s) = R_f(s) - Y(s) = F(s)R(s) - Y(s). \]

The controller output is

\[ U(s) = C(s)E(s) = C(s)\left(F(s)R(s) - Y(s)\right). \]

Suppose the disturbance \( D(s) \) enters additively at the plant input. The plant output is then

\[ Y(s) = P(s)\big(U(s) + D(s)\big) = P(s)\big(C(s)(F(s)R(s) - Y(s)) + D(s)\big). \]

Rearranging gives

\[ \begin{aligned} Y(s) &= P(s)C(s)F(s)R(s) - P(s)C(s)Y(s) + P(s)D(s) \\ \big(1 + C(s)P(s)\big)Y(s) &= C(s)P(s)F(s)R(s) + P(s)D(s). \end{aligned} \]

Hence the closed-loop transfer functions from reference and disturbance to output are

\[ \frac{Y(s)}{R(s)} = \frac{F(s)C(s)P(s)}{1 + C(s)P(s)}, \qquad \frac{Y(s)}{D(s)} = \frac{P(s)}{1 + C(s)P(s)}. \]

Define the standard closed-loop sensitivity and complementary sensitivity (to be treated in depth in a later chapter) as

\[ S(s) = \frac{1}{1 + C(s)P(s)}, \qquad T_0(s) = \frac{C(s)P(s)}{1 + C(s)P(s)}. \]

Then the previous relations can be written compactly as

\[ \frac{Y(s)}{R(s)} = F(s)T_0(s), \qquad \frac{Y(s)}{D(s)} = P(s)S(s). \]

This shows algebraically that \( F(s) \) influences only the reference-to-output transfer function, while the disturbance-to-output transfer, and thus disturbance rejection properties, remain unchanged.

3. Design Objectives for Reference Pre-Filters

Let \( T_0(s) \) be the closed-loop transfer function from reference to output for the underlying 1-DOF design (no pre-filter). The pre-filter yields a modified tracking transfer function

\[ T_r(s) = \frac{Y(s)}{R(s)} = F(s)T_0(s). \]

Typical design goals for \( F(s) \) include:

  • Adjusting transient shape: reducing overshoot while preserving acceptable rise time and settling time.
  • Limiting actuator effort: shaping \( r(t) \) so that the commanded motion respects physical acceleration or jerk limits (important for robot joints).
  • Preserving steady-state accuracy: ensuring \( \lim_{t \to \infty} y(t) = r_{\text{step}} \) for step references.
  • Separating objectives: one first designs \( C(s) \) for stability and disturbance rejection, then tunes \( F(s) \) for tracking.

For step tracking, steady-state accuracy requires that the DC gain of \( T_r(s) \) be one:

\[ \lim_{s \to 0} T_r(s) = \lim_{s \to 0} F(s)T_0(s) = 1. \]

Since many servo loops are designed such that \( T_0(0) = 1 \), a common constraint is simply \( F(0) = 1 \), which implies unit DC gain for the pre-filter.

4. Model-Matching Pre-Filter Design

Suppose the 1-DOF design yields a closed-loop model \( T_0(s) \). A natural idea is to specify a desired tracking model \( M(s) \) and choose \( F(s) \) so that

\[ T_r(s) = F(s)T_0(s) \approx M(s). \]

In ideal model matching, we enforce equality:

\[ F(s) = \frac{M(s)}{T_0(s)}. \]

Using the definition of \( T_0(s) \), this can be written as

\[ F(s) = \frac{M(s)}{\dfrac{C(s)P(s)}{1 + C(s)P(s)}} = M(s)\frac{1 + C(s)P(s)}{C(s)P(s)}. \]

For \( F(s) \) to be realizable as a physical filter, it must be:

  • Proper: the degree of the denominator is at least the degree of the numerator.
  • Causal and stable: all poles of \( F(s) \) lie in the open left half-plane.
  • Well-conditioned: avoid aggressive pole-zero cancellations with weakly damped closed-loop poles of \( T_0(s) \), to maintain robustness to modeling errors.

If exact model matching yields a non-proper or poorly conditioned \( F(s) \), we approximate \( M(s) \) by a realizable model or approximate \( F(s) \) by a lower-order, well-damped filter.

5. Example – Overshoot Reduction for a Second-Order Closed Loop

Assume the underlying feedback design yields the standard second-order closed-loop transfer function

\[ T_0(s) = \frac{\omega_n^2}{s^2 + 2\zeta \omega_n s + \omega_n^2}, \qquad 0 < \zeta < 1, \]

where \( \omega_n > 0 \) is the natural frequency and \( \zeta \) is the damping ratio. When \( \zeta \) is small, step responses have significant overshoot.

A more desirable tracking model for many servo systems is a critically damped response with the same natural frequency:

\[ M(s) = \frac{\omega_n^2}{(s + \omega_n)^2}. \]

The model-matching pre-filter is then

\[ F(s) = \frac{M(s)}{T_0(s)} = \frac{\dfrac{\omega_n^2}{(s + \omega_n)^2}}{\dfrac{\omega_n^2}{s^2 + 2\zeta \omega_n s + \omega_n^2}} = \frac{s^2 + 2\zeta \omega_n s + \omega_n^2}{(s + \omega_n)^2}. \]

Expanding the denominator \( (s + \omega_n)^2 = s^2 + 2\omega_n s + \omega_n^2 \) yields

\[ F(s) = \frac{s^2 + 2\zeta \omega_n s + \omega_n^2}{s^2 + 2\omega_n s + \omega_n^2}. \]

This \( F(s) \) is proper and stable for \( 0 < \zeta < 1 \). Moreover, it has unit DC gain:

\[ F(0) = \frac{\omega_n^2}{\omega_n^2} = 1, \quad T_r(0) = F(0)T_0(0) = 1, \]

so step tracking remains exact in steady state. In time domain, the pre-filter removes the underdamped component of the response, yielding a smoother, overshoot-free motion. In a robot joint position control loop, this corresponds to a command shaper that protects against oscillations while leaving the feedback robustness properties essentially unchanged.

A simpler, commonly used design is a first-order low-pass pre-filter

\[ F(s) = \frac{1}{\tau_f s + 1}, \]

where \( \tau_f > 0 \) is a design time constant. A larger \( \tau_f \) slows the effective reference and reduces overshoot and actuator peaks, at the cost of slower tracking.

6. Python Implementation for Servo Tracking with Pre-Filter

We now implement the second-order example using Python, the python-control library, and standard scientific libraries (numpy, matplotlib). This workflow is representative of rapid prototyping for robotic joint controllers.

flowchart TD
  A["Specify closed-loop model T0(s)"] --> B["Choose desired model M(s)"]
  B --> C["Compute F(s) = M(s) / T0(s)"]
  C --> D["Implement T0(s) and F(s) in python-control"]
  D --> E["Simulate step responses"]
  E --> F["Compare overshoot, rise time, actuator effort"]
        

import numpy as np
import matplotlib.pyplot as plt

# Robotics-oriented control toolbox
import control as ct  # python-control

# Closed-loop model (without pre-filter): underdamped 2nd-order
wn = 10.0   # natural frequency [rad/s]
zeta = 0.2  # damping ratio (underdamped)

num_T0 = [wn**2]
den_T0 = [1.0, 2.0*zeta*wn, wn**2]
T0 = ct.tf(num_T0, den_T0)

# Desired critically damped model with same wn
num_M = [wn**2]
den_M = [1.0, 2.0*wn, wn**2]
M = ct.tf(num_M, den_M)

# Model-matching pre-filter F(s) = M(s) / T0(s)
F_match = ct.minreal(M / T0, verbose=False)

# Alternative: simple first-order low-pass pre-filter
tau_f = 0.1  # design time constant
F_lp = ct.tf([1.0], [tau_f, 1.0])

# Combined tracking transfer functions
T_match = ct.minreal(F_match * T0, verbose=False)
T_lp    = ct.minreal(F_lp * T0, verbose=False)

# Time vector for simulation
t = np.linspace(0.0, 2.0, 500)

# Step responses
t0, y0 = ct.step_response(T0, T=t)        # no pre-filter
t1, y1 = ct.step_response(T_match, T=t)   # model-matching F(s)
t2, y2 = ct.step_response(T_lp, T=t)      # low-pass F(s)

plt.figure()
plt.plot(t0, y0, label="T0(s) (no pre-filter)")
plt.plot(t1, y1, label="F_match * T0(s)")
plt.plot(t2, y2, label="F_lp * T0(s)")
plt.xlabel("Time [s]")
plt.ylabel("Output y(t)")
plt.title("Effect of Reference Pre-Filters on Step Tracking")
plt.grid(True)
plt.legend()

plt.show()
      

In a robotics context, T0(s) would be the closed-loop transfer function from reference joint angle to actual joint angle, obtained from a model of \( P(s) \) and \( C(s) \) (e.g., a PID position controller). The pre-filter can be integrated into a higher-level motion planner that generates shaped references before sending them to the low-level servo loop.

7. C++ Implementation of a Discrete-Time Pre-Filter

On embedded controllers for robots (e.g., microcontrollers or real-time PCs in ROS-based systems), the pre-filter is typically implemented in discrete time. For the first-order low-pass pre-filter

\[ F(s) = \frac{1}{\tau_f s + 1}, \]

a simple forward Euler discretization with sampling time \( T_s \) yields the recursion

\[ y_f[k] = y_f[k-1] + \alpha\big(r[k] - y_f[k-1]\big), \qquad \alpha = \frac{T_s}{\tau_f}, \]

where \( r[k] \) is the raw reference and \( y_f[k] \) is the filtered reference supplied to the feedback controller.


#include <cmath>

struct LowPassPrefilter {
    double tau_f;  // continuous-time time constant
    double Ts;     // sampling period
    double y;      // internal state (filtered reference)

    LowPassPrefilter(double tau_f_, double Ts_)
        : tau_f(tau_f_), Ts(Ts_), y(0.0) {}

    // Update with new raw reference r_k, return filtered reference y_k
    double update(double r_k) {
        double alpha = Ts / tau_f;          // discretization gain
        y += alpha * (r_k - y);             // Euler integration
        return y;
    }
};

// Example usage inside a robot joint control loop (pseudo-code):
//
// LowPassPrefilter r_filter(0.1, 0.001);  // tau_f = 0.1 s, Ts = 1 ms
//
// while (true) {
//     double r_raw = read_joint_command();      // desired joint position
//     double r_shaped = r_filter.update(r_raw); // shaped reference
//
//     double y_meas = read_joint_position();    // actual position
//     double u = pid_controller(r_shaped, y_meas);
//     write_actuator_command(u);
// }
      

The same structure can be reused for multiple joints, with individual time constants tuned per axis according to mechanical bandwidth and allowable accelerations.

8. Java Implementation of a Reference Pre-Filter

In Java-based control frameworks (e.g., for simulation or high-level control of robotic manipulators), we can implement the same discrete-time low-pass pre-filter using an object-oriented design.


public class LowPassPrefilter {
    private final double tauF;  // time constant
    private final double Ts;    // sampling period
    private double y;           // internal state

    public LowPassPrefilter(double tauF, double Ts) {
        this.tauF = tauF;
        this.Ts = Ts;
        this.y = 0.0;
    }

    // Update with new reference r_k and return filtered reference
    public double update(double r) {
        double alpha = Ts / tauF;
        y += alpha * (r - y);
        return y;
    }
}

// Example usage in a servo loop:
//
// LowPassPrefilter rFilter = new LowPassPrefilter(0.1, 0.002); // tauF = 0.1 s, Ts = 2 ms
//
// while (running) {
//     double rRaw = referenceGenerator.getNextCommand();
//     double rShaped = rFilter.update(rRaw);
//
//     double yMeas = robot.readJointPosition(jointIndex);
//     double control = pid.compute(rShaped, yMeas);
//     robot.writeJointTorque(jointIndex, control);
// }
      

Even though Java is less common on low-level embedded platforms, it is widely used for simulation, testing, and high-level behaviors in robotics. The same pre-filter logic can be translated to JVM-based real-time environments or wrapped into middleware components.

9. MATLAB/Simulink Implementation

MATLAB and Simulink are standard tools in control and robotics. Using the Control System Toolbox, we can implement and compare pre-filter designs symbolically and in simulation.


% Closed-loop model T0(s): underdamped second-order
wn   = 10;    % rad/s
zeta = 0.2;

s = tf('s');
T0 = wn^2 / (s^2 + 2*zeta*wn*s + wn^2);

% Desired critically damped model M(s)
M = wn^2 / (s + wn)^2;

% Model-matching pre-filter
F_match = minreal(M / T0);

% First-order low-pass pre-filter
tau_f = 0.1;
F_lp  = 1 / (tau_f*s + 1);

% Tracking transfer functions
T_match = minreal(F_match * T0);
T_lp    = minreal(F_lp * T0);

figure;
step(T0, T_match, T_lp);
legend('T0(s)', 'F\_match*T0(s)', 'F\_lp*T0(s)');
grid on;
title('Reference Pre-Filters: MATLAB Simulation');
xlabel('Time [s]');
ylabel('Output y(t)');

% Simulink implementation sketch:
%   - Place Transfer Fcn block with numerator/denominator of F_match or F_lp
%   - Feed reference signal into this block
%   - Output of pre-filter goes to summing junction of existing feedback loop
      

In Simulink, the pre-filter is simply another transfer function block placed in the reference path. This separation allows the engineer to reuse the same feedback controller across trajectories with different command shaping requirements.

10. Wolfram Mathematica Implementation

Mathematica provides symbolic and numeric tools for control engineering. The following notebook-style code constructs \( T_0(s) \), the pre-filter, and step responses.


(* Parameters *)
wn   = 10.0;
zeta = 0.2;

s = LaplaceTransformVariable;

(* Closed-loop transfer function T0(s) *)
T0 = TransferFunctionModel[
   wn^2/(s^2 + 2 zeta wn s + wn^2), s];

(* Desired critically damped model M(s) *)
M = TransferFunctionModel[
   wn^2/(s + wn)^2, s];

(* Model-matching pre-filter F(s) = M(s) / T0(s) *)
Fmatch = TransferFunctionModel[
   M["TransferFunction"][[1]] / T0["TransferFunction"][[1]], s];

(* First-order low-pass pre-filter *)
tauF = 0.1;
Flp = TransferFunctionModel[1/(tauF s + 1), s];

(* Tracking transfer functions *)
Tmatch = SystemsModelSeries[Flp, T0];
Tplp   = SystemsModelSeries[Flp, T0];

(* Step responses *)
StepResponsePlot[
 {T0, Tmatch, Tplp},
 {t, 0, 2},
 PlotLegends -> {
   "T0(s)",
   "F_match * T0(s)",
   "F_lp * T0(s)"},
 PlotLabel -> "Reference Pre-Filters in Mathematica",
 Frame -> True
]
      

Symbolic transfer-function manipulation is useful when deriving pre-filters analytically (e.g., algebraic model matching) before exporting parameters to numerical code running on a robot controller.

11. Problems and Solutions

Problem 1 (Derivation of Closed-Loop Relations): Consider the loop of Section 2 with plant \( P(s) \), controller \( C(s) \), reference pre-filter \( F(s) \), and disturbance \( D(s) \) at the plant input. Starting from

\[ E(s) = F(s)R(s) - Y(s), \quad U(s) = C(s)E(s), \quad Y(s) = P(s)\big(U(s) + D(s)\big), \]

derive \( Y(s)/R(s) \) and \( Y(s)/D(s) \).

Solution:

Substituting \( U(s) = C(s)(F(s)R(s) - Y(s)) \) into the plant equation gives

\[ Y(s) = P(s)\big(C(s)(F(s)R(s) - Y(s)) + D(s)\big) = P(s)C(s)F(s)R(s) - \\ P(s)C(s)Y(s) + P(s)D(s). \]

Move the term \( -P(s)C(s)Y(s) \) to the left:

\[ \big(1 + C(s)P(s)\big)Y(s) = C(s)P(s)F(s)R(s) + P(s)D(s). \]

Dividing both sides by \( 1 + C(s)P(s) \) and grouping terms yields

\[ \frac{Y(s)}{R(s)} = \frac{F(s)C(s)P(s)}{1 + C(s)P(s)}, \qquad \frac{Y(s)}{D(s)} = \frac{P(s)}{1 + C(s)P(s)}. \]

Thus the pre-filter appears only in the numerator of \( Y(s)/R(s) \), not in \( Y(s)/D(s) \).


Problem 2 (Poles Unchanged by Reference Pre-Filter): Show that the closed-loop poles of the system with pre-filter \( F(s) \) are identical to those of the underlying 1-DOF loop without \( F(s) \).

Solution:

Without a pre-filter, the tracking transfer function is

\[ T_0(s) = \frac{C(s)P(s)}{1 + C(s)P(s)}. \]

With a pre-filter, the tracking transfer function becomes

\[ T_r(s) = \frac{F(s)C(s)P(s)}{1 + C(s)P(s)} = F(s)T_0(s). \]

Assuming \( F(s) \) is proper and stable, its poles are in the open left half-plane and do not coincide with the unstable region. The denominator of \( T_r(s) \) is \( 1 + C(s)P(s) \), identical to that of \( T_0(s) \). Therefore, the closed-loop poles associated with feedback are unchanged; \( F(s) \) only introduces additional stable poles in the reference path, which do not affect internal closed-loop stability.


Problem 3 (Model-Matching Pre-Filter): Let the feedback design yield

\[ T_0(s) = \frac{\omega_n^2}{s^2 + 2\zeta \omega_n s + \omega_n^2}, \qquad 0 < \zeta < 1, \]

and suppose you want the tracking behavior

\[ M(s) = \frac{\omega_n^2}{(s + \omega_n)^2}. \]

Compute the model-matching pre-filter \( F(s) = M(s)/T_0(s) \) and verify that \( F(0) = 1 \).

Solution:

Direct computation gives

\[ F(s) = \frac{M(s)}{T_0(s)} = \frac{\dfrac{\omega_n^2}{(s + \omega_n)^2}}{\dfrac{\omega_n^2}{s^2 + 2\zeta \omega_n s + \omega_n^2}} = \frac{s^2 + 2\zeta \omega_n s + \omega_n^2}{(s + \omega_n)^2}. \]

Since \( (s + \omega_n)^2 = s^2 + 2\omega_n s + \omega_n^2 \), we finally obtain

\[ F(s) = \frac{s^2 + 2\zeta \omega_n s + \omega_n^2}{s^2 + 2\omega_n s + \omega_n^2}. \]

Evaluating at \( s = 0 \) yields

\[ F(0) = \frac{\omega_n^2}{\omega_n^2} = 1, \]

and thus \( T_r(0) = F(0)T_0(0) = 1 \), preserving step tracking accuracy.


Problem 4 (First-Order Pre-Filter Design from Slope Constraint): Consider a servo for which the actuator saturates if the reference slope exceeds \( \dot{r}_{\max} \). The desired reference is a unit step. You design a low-pass pre-filter

\[ F(s) = \frac{1}{\tau_f s + 1}, \]

so that the filtered command \( r_f(t) \) is an exponential with slope at \( t = 0 \) given by \( 1/\tau_f \). Choose \( \tau_f \) such that \( \max_t |\dot{r}_f(t)| \le \dot{r}_{\max} \).

Solution:

The step response of \( F(s) \) to a unit step is

\[ r_f(t) = 1 - \mathrm{e}^{-t/\tau_f}, \qquad t \ge 0. \]

Differentiating gives

\[ \dot{r}_f(t) = \frac{1}{\tau_f}\mathrm{e}^{-t/\tau_f}. \]

The maximum occurs at \( t = 0 \), where \( \dot{r}_f(0) = 1/\tau_f \). Imposing the constraint \( 1/\tau_f \le \dot{r}_{\max} \) yields

\[ \tau_f \ge \frac{1}{\dot{r}_{\max}}. \]

Thus choosing \( \tau_f = 1/\dot{r}_{\max} \) satisfies the constraint with equality; larger \( \tau_f \) produces even smaller slopes but slows the effective reference.

12. Summary

In this lesson we introduced reference pre-filters as a core ingredient of two-degree-of-freedom linear controllers. By deriving the closed-loop relations, we showed that a pre-filter affects only the reference-to-output transfer function \( T_r(s) = F(s)T_0(s) \), leaving disturbance rejection unchanged. This separation allows independent tuning of tracking and regulation properties.

We presented an algebraic model-matching method \( F(s) = M(s)/T_0(s) \) and demonstrated overshoot reduction for a second-order closed loop by converting an underdamped model into a critically damped one while preserving DC gain. Practical implementations in Python, C++, Java, MATLAB/Simulink, and Mathematica illustrated how to realize pre-filters in both simulation and embedded robotics software. These concepts pave the way for the next lesson, where tracking and disturbance rejection objectives will be systematically separated in more general 2-DOF controller parameterizations.

13. References

  1. Horowitz, I.M., & Sidi, M. (1972). Synthesis of two-degree-of-freedom feedback systems. International Journal of Control, 16(2), 287–309.
  2. Horowitz, I.M. (1963). Synthesis of Feedback Systems. Academic Press. (Foundational text on 2-DOF designs and command shaping.)
  3. Åström, K.J., & Wittenmark, B. (1973). On self-tuning regulators. Automatica, 9(2), 185–199. (Includes early discussions of 2-DOF structures.)
  4. Chen, B.M., & Saberi, A. (1993). A new two-degree-of-freedom design method for robust tracking and disturbance rejection. International Journal of Control, 58(2), 365–393.
  5. Åström, K.J., & Hägglund, T. (1995). PID Controllers: Theory, Design, and Tuning. Instrument Society of America. (Chapters on set-point weighting and 2-DOF PID.)
  6. Skogestad, S., & Postlethwaite, I. (2005). Multivariable Feedback Control: Analysis and Design (2nd ed.). Wiley. (Sections on 2-DOF control and reference model matching.)