Chapter 12: Digital Control and Real-Time Implementation

Lesson 5: Lab: Implement Controller with Real Loop Timing

This lab connects the theory of sampled-data robot control to an actual real-time loop implementation. You will implement a discrete-time joint-space controller, measure real loop timing (sampling, computation delay, jitter), and interpret its effect on stability and tracking performance. We combine state-space sampled-data models with practical timing primitives in Python, C++, Java, MATLAB/Simulink, and Wolfram Mathematica, using robotics-related libraries where appropriate.

1. Lab Objectives and Setup

We assume you already designed a stabilizing continuous-time joint-space controller in previous chapters (e.g., PD or computed-torque) and discretized it with a zero-order hold in Lessons 12.1–12.2. In this lab, we:

  • Implement a discrete-time joint controller with sampling period \( T_s \).
  • Measure the actual inter-sample intervals \( T_k \) and computation time \( T_c(k) \).
  • Model sampling jitter as a perturbation of the nominal discrete-time closed loop and analyze robustness.
  • Use language-specific real-time constructs (threads, timers, executors) and robotics frameworks:
    • Python: rclpy (ROS 2), numpy, simple timing with time.perf_counter().
    • C++: rclcpp, Eigen, <chrono>/<thread> for timing.
    • Java: ScheduledExecutorService, optionally rosjava for integration.
    • MATLAB/Simulink: discrete-time blocks, Robotics System Toolbox.
    • Wolfram Mathematica: numerical integration and timed loops.

We focus on a single revolute joint modeled as a linearized second-order system around a given operating point. Let \( q(t) \) denote joint position, \( \dot{q}(t) \) velocity, and control input \( \tau(t) \) the actuator torque command.

\[ \mathbf{x}(t) = \begin{bmatrix} q(t) \\ \dot{q}(t) \end{bmatrix}, \quad \dot{\mathbf{x}}(t) = \mathbf{A}\,\mathbf{x}(t) + \mathbf{B}\,u(t), \quad y(t) = \mathbf{C}\,\mathbf{x}(t), \quad u(t) = \tau(t) \]

Here \( \mathbf{A} \in \mathbb{R}^{2\times 2} \) and \( \mathbf{B} \in \mathbb{R}^{2\times 1} \) come from the linearization of the robot joint dynamics around the operating point, which you already know from kinematics and dynamics.

2. Discrete-Time Controller with Nominal Sampling

Using a zero-order hold (ZOH) with nominal sample time \( T_s \), the equivalent discrete-time model is:

\[ \mathbf{x}[k+1] = \mathbf{\Phi}\,\mathbf{x}[k] + \mathbf{\Gamma}\,u[k], \quad y[k] = \mathbf{C}\,\mathbf{x}[k], \]

\[ \mathbf{\Phi} = \exp(\mathbf{A} T_s), \quad \mathbf{\Gamma} = \int_{0}^{T_s} \exp\!\big(\mathbf{A}\sigma\big)\,\mathrm{d}\sigma\, \mathbf{B}. \]

A joint-space PD controller in discrete time for tracking a desired trajectory \( q_d[k] \) is:

\[ e[k] = q_d[k] - q[k], \quad \dot{e}[k] \approx \frac{e[k] - e[k-1]}{T_s}, \]

\[ \tau[k] = K_p\,e[k] + K_d\,\dot{e}[k] = K_p\,e[k] + K_d\frac{e[k]-e[k-1]}{T_s}. \]

In state-space form, if we augment the state with the previous error \( e[k-1] \), this becomes a linear dynamic controller. When combined with the discrete-time plant, we obtain a closed-loop system \( \mathbf{x}_c[k+1] = \mathbf{A}_c \mathbf{x}_c[k] \) whose eigenvalues must satisfy \( |\lambda_i(\mathbf{A}_c)| < 1 \) for stability.

3. Modeling Real Loop Timing and Jitter

In a real implementation, the loop does not execute at perfectly periodic instants. Let \( t_k \) be the time at which the control input \( u[k] \) is applied (after sensing and computation). We model:

\[ t_k = k T_s + \delta_k, \]

where \( \delta_k \) is the (unknown) timing error. The actual inter-sample interval is

\[ T_k = t_{k+1} - t_k = T_s + \Delta T_k, \quad \Delta T_k = \delta_{k+1} - \delta_k. \]

We call \( \Delta T_k \) the sampling jitter. In practice, the OS scheduler, cache misses, communication delay, and other tasks cause \( \Delta T_k \neq 0 \). Assume \( |\Delta T_k| \leq J_{\max} \) (bounded jitter).

The sampled-data model with aperiodic sampling is

\[ \mathbf{x}[k+1] = \mathbf{\Phi}_k\,\mathbf{x}[k] + \mathbf{\Gamma}_k\,u[k], \]

\[ \mathbf{\Phi}_k = \exp\big(\mathbf{A}(T_s+\Delta T_k)\big), \quad \mathbf{\Gamma}_k = \int_{0}^{T_s+\Delta T_k} \exp(\mathbf{A}\sigma)\,\mathrm{d}\sigma\, \mathbf{B}. \]

For small jitter \( |\Delta T_k| \ll T_s \), a first-order expansion yields

\[ \mathbf{\Phi}_k \approx \mathbf{\Phi} + \Delta T_k\,\mathbf{A}\mathbf{\Phi}, \quad \mathbf{\Gamma}_k \approx \mathbf{\Gamma} + \Delta T_k\,\mathbf{B}. \]

With state feedback \( u[k] = -\mathbf{K}\mathbf{x}[k] \), the closed-loop dynamics become

\[ \mathbf{x}[k+1] = \big(\mathbf{\Phi}_k - \mathbf{\Gamma}_k\mathbf{K}\big)\mathbf{x}[k] = \big(\mathbf{A}_d + \Delta\mathbf{A}_k\big)\mathbf{x}[k], \]

\[ \mathbf{A}_d = \mathbf{\Phi} - \mathbf{\Gamma}\mathbf{K}, \quad \Delta\mathbf{A}_k \approx \Delta T_k\left(\mathbf{A}\mathbf{\Phi} - \mathbf{B}\mathbf{K}\right). \]

Thus jitter appears as a multiplicative perturbation of the nominal discrete-time closed-loop matrix \( \mathbf{A}_d \). We can analyze robustness using matrix norms.

Lemma (robust stability with small jitter).

Suppose there exists a matrix norm \( \|\cdot\| \) and a number \( \alpha \) with \( \|\mathbf{A}_d\| \leq \alpha < 1 \). If

\[ \|\Delta\mathbf{A}_k\| \leq \varepsilon \quad \text{for all } k \]

and \( \alpha + \varepsilon < 1 \), then the origin of the perturbed system \( \mathbf{x}[k+1]=( \mathbf{A}_d + \Delta\mathbf{A}_k )\mathbf{x}[k] \) is exponentially stable.

Proof sketch.

From submultiplicativity: \( \|\mathbf{x}[k+1]\| \leq \|\mathbf{A}_d+\Delta\mathbf{A}_k\|\;\|\mathbf{x}[k]\| \leq (\alpha+\varepsilon)\|\mathbf{x}[k]\| \). Induction gives

\[ \|\mathbf{x}[k]\| \leq (\alpha+\varepsilon)^k \|\mathbf{x}[0]\|. \]

Because \( \alpha+\varepsilon < 1 \), the bound decays geometrically, implying exponential stability.

In the lab, you will estimate jitter from timing measurements and reason whether it threatens the stability margin implied by your discrete-time design.

4. Real-Time Control Loop Flow

The following flow describes the structure of a real-time control task executing at nominal period \( T_s \). This is the pattern you will implement in each language.

flowchart TD
  START["Start lab program"] --> INIT["Initialize robot interface, gains, Ts"]
  INIT --> TIMESET["Set 'next_time' = 'now' + Ts"]
  TIMESET --> LOOP{"Loop while 't' < 't_final'"}
  LOOP --> SENSE["Read joint states q, dq from sensors or simulator"]
  SENSE --> REF["Compute reference q_d, dq_d at current time"]
  REF --> CTRL["Compute error e, de and control torque tau_k"]
  CTRL --> SEND["Send tau_k to robot or simulator"]
  SEND --> LOG["Log timestamps, measured dt, jitter, and state"]
  LOG --> SLEEP["Sleep until 'next_time' (if time remains)"]
  SLEEP --> UPDATE["Update 'next_time' = 'next_time' + Ts"]
  UPDATE --> LOOP
        

Critical measurements for analysis:

  • Measured period: \( T_k = t_{k} - t_{k-1} \).
  • Computation time: wall-clock between sensing and end of control computation.
  • Jitter: \( \Delta T_k = T_k - T_s \).

For each experiment, you will plot \( T_k \), \( \Delta T_k \), and tracking error \( e[k] \) against time to relate timing and control quality.

5. Python Lab Implementation (ROS 2 / Simulation Friendly)

Python is not a hard real-time language, but it is convenient for rapid prototyping and simulation. We assume either:

  • A ROS 2 node using rclpy subscribing to joint states and publishing torque commands, or
  • A simulation backend (e.g., PyBullet or a custom ODE integrator) that advances in real time.

The code below illustrates a 1-DOF PD controller with timing measurement. Replace the read_joint() and write_torque() functions with your robot or simulator APIs.


import time
import math
import numpy as np

# Controller parameters
Ts = 0.002  # nominal sampling time [s]
Kp = 50.0
Kd = 2.0

# Reference trajectory: simple sinusoid
def q_ref(t):
    qd = 0.5 * math.sin(2.0 * math.pi * 0.5 * t)  # 0.5 rad amplitude, 0.5 Hz
    dqd = 0.5 * 2.0 * math.pi * 0.5 * math.cos(2.0 * math.pi * 0.5 * t)
    return qd, dqd

# Robot I/O (to be implemented for your platform)
def read_joint():
    """
    Return (q, dq) for the controlled joint.
    Connect this to your low-level interface or simulator.
    """
    # Example dummy implementation:
    # return sim.get_joint_state()
    raise NotImplementedError

def write_torque(tau):
    """
    Send torque tau to the actuator.
    """
    # Example dummy implementation:
    # sim.set_joint_torque(tau)
    raise NotImplementedError

# Logging arrays
log_t = []
log_Tk = []
log_jitter = []
log_q = []
log_qd = []
log_e = []

t_final = 5.0  # run 5 seconds
t0 = time.perf_counter()
last_time = t0
next_time = t0 + Ts

e_prev = 0.0

while True:
    now = time.perf_counter()
    t = now - t0
    if t >= t_final:
        break

    # Measured period and jitter
    Tk = now - last_time
    jitter = Tk - Ts
    last_time = now

    # Read current joint state
    q, dq = read_joint()

    # Reference at current time
    qd, dqd = q_ref(t)

    # PD control in discrete time
    e = qd - q
    de = (e - e_prev) / max(Tk, 1e-6)  # use measured Tk in derivative
    e_prev = e

    tau = Kp * e + Kd * de

    # Send control
    write_torque(tau)

    # Log signals
    log_t.append(t)
    log_Tk.append(Tk)
    log_jitter.append(jitter)
    log_q.append(q)
    log_qd.append(qd)
    log_e.append(e)

    # Sleep until next nominal sample time
    next_time += Ts
    sleep_time = next_time - time.perf_counter()
    if sleep_time > 0.0:
        time.sleep(sleep_time)

# After the loop, analyze logs:
# - Plot Tk vs t
# - Plot jitter vs t
# - Plot tracking error vs t
      

Remark. In this implementation, the derivative term uses the measured \( T_k \) instead of nominal \( T_s \), partially compensating for jitter in the derivative estimate. For small jitter and sufficiently low closed-loop bandwidth, this improves robustness compared to a fixed \( T_s \)-based derivative.

6. C++ Implementation with High-Resolution Timing

C++ is common for low-level control, particularly in ROS 2 with rclcpp and ros2_control. Here we show a bare loop using std::chrono. In a full system, the same logic would be inside a controller update method called by the real-time manager.


#include <chrono>
#include <thread>
#include <vector>
#include <cmath>

// Replace with your own robot interface
struct JointInterface {
  double readPosition();
  double readVelocity();
  void   writeTorque(double tau);
};

int main() {
  using clock = std::chrono::high_resolution_clock;
  using std::chrono::duration_cast;
  using std::chrono::microseconds;

  JointInterface joint;

  const double Ts = 0.002;   // 2 ms
  const double Kp = 50.0;
  const double Kd = 2.0;

  auto t0 = clock::now();
  auto last = t0;
  auto next = t0 + std::chrono::duration<double>(Ts);

  double e_prev = 0.0;
  double t_final = 5.0;

  std::vector<double> log_t, log_Tk, log_jitter, log_q, log_qd, log_e;

  while (true) {
    auto now = clock::now();
    double t = std::chrono::duration<double>(now - t0).count();
    if (t >= t_final) break;

    double Tk = std::chrono::duration<double>(now - last).count();
    last = now;
    double jitter = Tk - Ts;

    // Joint state
    double q  = joint.readPosition();
    double dq = joint.readVelocity();

    // Reference
    double qd  = 0.5 * std::sin(2.0 * M_PI * 0.5 * t);
    double dqd = 0.5 * 2.0 * M_PI * 0.5 * std::cos(2.0 * M_PI * 0.5 * t);

    double e  = qd - q;
    double de = (e - e_prev) / std::max(Tk, 1e-6);
    e_prev = e;

    double tau = Kp * e + Kd * de;

    joint.writeTorque(tau);

    // Log signals
    log_t.push_back(t);
    log_Tk.push_back(Tk);
    log_jitter.push_back(jitter);
    log_q.push_back(q);
    log_qd.push_back(qd);
    log_e.push_back(e);

    // Sleep until next nominal instant
    next += std::chrono::duration<double>(Ts);
    auto now2 = clock::now();
    if (next > now2) {
      std::this_thread::sleep_until(next);
    }
  }

  // Post-process logs (e.g., save to file) for plotting.
  return 0;
}
      

On POSIX systems you can further improve determinism by assigning a real-time scheduling policy (SCHED_FIFO) and core affinity to the control thread. These OS-level details are beyond the scope of this lab, but are essential for hard real-time guarantees.

7. Java Implementation with ScheduledExecutorService

Java is less common in low-level robotics, but illustrates periodic tasks with ScheduledExecutorService. Integrations with ROS are possible via rosjava. Here, the emphasis is measuring timing and logging jitter.


import java.util.concurrent.*;
import java.util.*;
import java.lang.Math;

class JointInterface {
    public double readPosition() { /* ... */ return 0.0; }
    public double readVelocity() { /* ... */ return 0.0; }
    public void writeTorque(double tau) { /* ... */ }
}

public class RealTimeController {
    public static void main(String[] args) {
        final double Ts = 0.002; // 2 ms
        final double Kp = 50.0;
        final double Kd = 2.0;
        final double tFinal = 5.0;

        JointInterface joint = new JointInterface();

        List<Double> log_t = new ArrayList<>();
        List<Double> log_Tk = new ArrayList<>();
        List<Double> log_jitter = new ArrayList<>();
        List<Double> log_q = new ArrayList<>();
        List<Double> log_qd = new ArrayList<>();
        List<Double> log_e = new ArrayList<>();

        ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();

        final long startNano = System.nanoTime();
        final double[] ePrev = new double[]{0.0};
        final long periodNano = (long)(Ts * 1e9);

        Runnable task = new Runnable() {
            long lastNano = startNano;

            @Override
            public void run() {
                long nowNano = System.nanoTime();
                double t = (nowNano - startNano) / 1e9;
                if (t >= tFinal) {
                    exec.shutdown();
                    return;
                }

                double Tk = (nowNano - lastNano) / 1e9;
                lastNano = nowNano;
                double jitter = Tk - Ts;

                double q  = joint.readPosition();
                double dq = joint.readVelocity();

                double qd  = 0.5 * Math.sin(2.0 * Math.PI * 0.5 * t);
                double dqd = 0.5 * 2.0 * Math.PI * 0.5 * Math.cos(2.0 * Math.PI * 0.5 * t);

                double e  = qd - q;
                double de = (e - ePrev[0]) / Math.max(Tk, 1e-6);
                ePrev[0] = e;

                double tau = Kp * e + Kd * de;
                joint.writeTorque(tau);

                log_t.add(t);
                log_Tk.add(Tk);
                log_jitter.add(jitter);
                log_q.add(q);
                log_qd.add(qd);
                log_e.add(e);
            }
        };

        exec.scheduleAtFixedRate(task, 0, periodNano, TimeUnit.NANOSECONDS);
    }
}
      

The JVM introduces nontrivial jitter compared to C++. The experiment of comparing \( \Delta T_k \) distributions between Java and C++ is instructive for appreciating language/runtime overhead in control.

8. MATLAB/Simulink Implementation

MATLAB and Simulink are widely used for prototyping and hardware-in-the-loop experiments. We outline both a script-based discrete-time loop and a Simulink model configuration.

8.1 Script-Based Discrete Loop in MATLAB


Ts = 0.002;
Kp = 50;
Kd = 2;
tFinal = 5.0;

% Robot interface: implement these as needed
readJoint  = @() deal(0.0, 0.0);  % [q, dq]
writeTorque = @(tau) [];          % send torque

t0 = tic;
lastTime = 0.0;
nextTime = Ts;

e_prev = 0.0;

log_t = [];
log_Tk = [];
log_jitter = [];
log_q = [];
log_qd = [];
log_e = [];

while true
    now = toc(t0);
    if now >= tFinal
        break;
    end

    Tk = now - lastTime;
    lastTime = now;
    jitter = Tk - Ts;

    [q, dq] = readJoint();

    qd  = 0.5 * sin(2*pi*0.5*now);
    dqd = 0.5 * 2*pi*0.5 * cos(2*pi*0.5*now);

    e  = qd - q;
    de = (e - e_prev) / max(Tk, 1e-6);
    e_prev = e;

    tau = Kp * e + Kd * de;
    writeTorque(tau);

    log_t(end+1,1) = now;
    log_Tk(end+1,1) = Tk;
    log_jitter(end+1,1) = jitter;
    log_q(end+1,1) = q;
    log_qd(end+1,1) = qd;
    log_e(end+1,1) = e;

    % Sleep to enforce nominal Ts
    pause(max(0.0, nextTime - now));
    nextTime = nextTime + Ts;
end

% Plot log_Tk, log_jitter, and log_e vs log_t
      

8.2 Simulink Model

In Simulink, create a model with:

  • A discrete-time controller subsystem (sample time \( T_s \)) implementing the PD law.
  • A continuous-time joint plant (from prior chapters) connected through a zero-order hold.
  • A Rate Transition block if you simulate multi-rate loops.
  • Scope blocks logging sample times via Clock and difference blocks to measure \( T_k \).

For hardware deployment (e.g., Simulink Real-Time), the same model can execute on a real-time target, giving near-ideal sampling with minimal jitter, against which you can compare PC-based implementations.

9. Wolfram Mathematica Implementation

Mathematica is most convenient for symbolic analysis and offline simulation, but we can still emulate a real-time loop by sampling \( \mathbf{A} \), \( \mathbf{B} \) and iterating a discrete-time model while measuring wall-clock time.


(* Parameters *)
Ts = 0.002;
Kp = 50.;
Kd = 2.;
tFinal = 5.0;

(* Simple linearized joint model: x = {q, dq} *)
A = { {0., 1.}, {-100., -5.} };
B = { {0.}, {100.} };

Phi = MatrixExp[A Ts];
Gamma = Integrate[MatrixExp[A s], {s, 0, Ts}].B;

(* Initial state *)
x = {0., 0.};
ePrev = 0.;

(* Logging *)
log = {};

t0 = AbsoluteTime[];
last = t0;
next = t0 + Ts;

While[True,
  now = AbsoluteTime[];
  t = now - t0;
  If[t >= tFinal, Break[]];

  Tk = now - last;
  last = now;
  jitter = Tk - Ts;

  (* Reference *)
  qd  = 0.5*Sin[2.*Pi*0.5*t];
  dqd = 0.5*2.*Pi*0.5*Cos[2.*Pi*0.5*t];

  q = x[[1]];
  dq = x[[2]];
  e = qd - q;
  de = (e - ePrev)/Max[Tk, 10.^-6];
  ePrev = e;

  tau = Kp*e + Kd*de;

  (* Discrete-time update using nominal Phi, Gamma *)
  x = Phi.x + Gamma*tau;

  AppendTo[log, {t, Tk, jitter, q, qd, e}];

  pause = next - AbsoluteTime[];
  If[pause > 0., Pause[pause]];
  next = next + Ts;
]

(* 'log' contains {t, Tk, jitter, q, qd, e} for plotting and analysis *)
      

Symbolic capabilities can further be used to derive \( \mathbf{\Phi}(T) \) and \( \mathbf{\Gamma}(T) \) exactly as functions of sample time, enabling analytic sensitivity analysis of closed-loop eigenvalues to timing variations.

10. Problems and Solutions

Problem 1 (Discrete-Time Model from Continuous-Time Joint Dynamics). Consider the linearized joint model \( \dot{\mathbf{x}}(t) = \mathbf{A}\mathbf{x}(t) + \mathbf{B}u(t) \) with constant matrices \( \mathbf{A} \), \( \mathbf{B} \). Show that under a zero-order hold with sampling period \( T_s \), the discrete-time model is \( \mathbf{x}[k+1] = \mathbf{\Phi}\,\mathbf{x}[k] + \mathbf{\Gamma}\,u[k] \) with \( \mathbf{\Phi} = \exp(\mathbf{A} T_s) \) and \( \mathbf{\Gamma} = \int_0^{T_s} \exp(\mathbf{A}\sigma)\,\mathrm{d}\sigma\,\mathbf{B} \).

Solution.

Assume \( u(t) = u[k] \) for \( t \in [kT_s,(k+1)T_s) \). The solution of the linear ODE from \( t_k = kT_s \) to \( t_{k+1} = (k+1)T_s \) is

\[ \mathbf{x}(t_{k+1}) = \exp(\mathbf{A} T_s)\,\mathbf{x}(t_k) + \int_{0}^{T_s} \exp\big(\mathbf{A}(T_s-\sigma)\big)\,\mathbf{B}u[k]\;\mathrm{d}\sigma. \]

Using the substitution \( r = T_s-\sigma \), \( \mathrm{d}r = -\mathrm{d}\sigma \), the limits reverse and we get

\[ \int_{0}^{T_s} \exp\big(\mathbf{A}(T_s-\sigma)\big)\,\mathrm{d}\sigma = \int_0^{T_s} \exp(\mathbf{A}r)\,\mathrm{d}r. \]

Identifying \( \mathbf{x}[k] = \mathbf{x}(t_k) \) and \( \mathbf{x}[k+1]=\mathbf{x}(t_{k+1}) \) yields the ZOH formulas for \( \mathbf{\Phi} \) and \( \mathbf{\Gamma} \).

Problem 2 (Jitter-Induced Perturbation Bound). Let \( \mathbf{A}_d = \mathbf{\Phi} - \mathbf{\Gamma}\mathbf{K} \) be the nominal closed-loop matrix for a state-feedback design with sampling period \( T_s \). Suppose jitter produces time-varying matrices \( \mathbf{A}_d + \Delta\mathbf{A}_k \) with \( \|\Delta\mathbf{A}_k\| \leq c J_{\max} \) for some known constant \( c > 0 \). Give a sufficient inequality on \( J_{\max} \) to guarantee robust stability in the sense of the lemma from Section 3.

Solution.

From the lemma, a sufficient condition for exponential stability is the existence of a norm with \( \|\mathbf{A}_d\| \leq \alpha < 1 \) and \( \alpha + \varepsilon < 1 \), where \( \varepsilon \geq \|\Delta\mathbf{A}_k\| \) for all \( k \). If \( \|\Delta\mathbf{A}_k\| \leq c J_{\max} \), we can choose \( \varepsilon = c J_{\max} \) and require

\[ \alpha + c J_{\max} < 1 \quad \Longrightarrow \quad J_{\max} < \frac{1-\alpha}{c}. \]

Hence any jitter bound \( J_{\max} \) satisfying this inequality preserves stability.

Problem 3 (Real-Time Feasibility of the Control Task). Consider a single control task with worst-case computation time \( C \) and sampling period \( T_s \). Under a simple rate-monotonic analysis for a single task, show that if \( C < T_s \), the task can meet its deadline. Then, for your implementation, estimate \( C \) from timing logs and check feasibility.

Solution.

For a single periodic task with relative deadline equal to period, the schedulability condition under a uniprocessor scheduler is that its processor utilization \( U = C/T_s \) is less than or equal to 1. If \( C < T_s \), then \( U < 1 \), meaning the CPU has enough time within each period to execute the task before the next release. In your implementation, the measured computation time per iteration can be approximated as

\[ C \approx \max_k \big(t_k^{\text{end}} - t_k^{\text{start}}\big), \]

where \( t_k^{\text{start}} \) is the timestamp just before sensing and \( t_k^{\text{end}} \) just after control output is sent. If the empirical \( C \) satisfies \( C < T_s \), your controller is schedulable under ideal conditions. If not, you must reduce computation (e.g., simplify computations, move heavy tasks to slower threads) or increase \( T_s \).

Problem 4 (Effect of Using \( T_k \) vs \( T_s \) in the Derivative Term). In the PD controller, consider two implementations of the derivative:

\[ \text{(a)}\quad \dot{e}[k] = \frac{e[k]-e[k-1]}{T_s}, \qquad \text{(b)}\quad \dot{e}[k] = \frac{e[k]-e[k-1]}{T_k}. \]

Qualitatively analyze which implementation is more robust to jitter and explain the trade-off in terms of noise amplification.

Solution.

Implementation (a) ignores jitter and uses constant \( T_s \). When \( T_k \) deviates from \( T_s \), the effective derivative gain becomes

\[ K_d^{\text{eff}}(k) = K_d \frac{T_k}{T_s}, \]

i.e., timing variation translates into multiplicative variation of the derivative gain, which can destabilize the loop for large jitter. Implementation (b) uses the actual period \( T_k \), which keeps the derivative estimate closer to the true derivative \( \frac{\mathrm{d}e}{\mathrm{d}t} \) and thus is more robust to jitter. However, \( T_k \) fluctuates sample to sample, and noise in the measurement is divided by a random quantity. If \( T_k \) occasionally becomes very small, the derivative can amplify sensor noise dramatically. In practice, a hybrid approach with saturation

\[ \dot{e}[k] = \frac{e[k]-e[k-1]}{\max(T_k,T_{\min})} \]

with a lower bound \( T_{\min} > 0 \) balances jitter compensation and noise robustness.

Problem 5 (Conceptual Timing Analysis Flow). Sketch a conceptual flow for evaluating whether a proposed control period \( T_s \) is feasible on given hardware, taking into account computation time, scheduling, and robustness margin. Use the flow to justify either decreasing or increasing \( T_s \) in your lab implementation.

Solution (flow).

flowchart TD
  S["Choose nominal Ts and design discrete controller"] --> M["Measure worst case computation time C"]
  M --> F{"Is C < Ts with margin?"}
  F -->|yes| J["Measure jitter and estimate J_max"]
  F -->|no| ADJ["Adjust: optimize code or increase Ts"]
  J --> R{"Is jitter bound small \nvs stability margin?"}
  R -->|yes| OK["Accept Ts and deploy controller"]
  R -->|no| RED["Reduce bandwidth or increase Ts"]
        

This flow emphasizes that timing analysis and control design must be performed together: an aggressive \( T_s \) with too heavy computations can violate both schedulability and stability.

11. Summary

In this lab, you bridged the gap between discrete-time control theory and actual real-time implementation. Starting from a linearized joint model, you implemented a sampled PD controller, measured real sampling periods, computation times, and jitter, and interpreted their influence on stability and performance via a perturbation view of the closed-loop matrix. Through implementations in Python, C++, Java, MATLAB/Simulink, and Mathematica, you observed language- and runtime-dependent jitter characteristics and learned how to structure a deterministic control loop and check basic schedulability conditions. These skills underpin advanced real-time control architectures and safety-critical controllers in later chapters.

12. References

  1. Åström, K. J., & Wittenmark, B. (1984). Computer-controlled systems: Theory and design. Prentice Hall.
  2. Henningsson, T., Cervin, A., & Årzén, K.-E. (2008). Sampling jitter effects in computer control systems. European Journal of Control, 14(3), 241–254.
  3. Fujioka, H. (2009). Stability analysis of systems with aperiodic sample-and-hold devices. Automatica, 45(3), 771–775.
  4. Zhang, W., Branicky, M. S., & Phillips, S. M. (2001). Stability of networked control systems. IEEE Control Systems Magazine, 21(1), 84–99.
  5. Cervin, A., Henriksson, D., Lincoln, B., Eker, J., & Årzén, K.-E. (2003). How does control timing affect performance? Analysis and simulation of timing using Jitterbug and TrueTime. IEEE Control Systems Magazine, 23(3), 16–30.
  6. Buttazzo, G. C., Lipari, G., & Abeni, L. (1998). Elastic task model for adaptive rate control. IEEE Real-Time Systems Symposium, 286–295.
  7. Goodwin, G. C., Salgado, M. E., Middleton, R. H., & McInnis, B. C. (1995). Frequency domain tools for designing fast sampling rate controllers. Automatica, 31(4), 659–669.
  8. Nesic, D., Teel, A. R., & Carnevale, D. (2009). Explicit computation of the sampling period in emulation of controllers for nonlinear sampled-data systems. IEEE Transactions on Automatic Control, 54(3), 619–624.