Chapter 19: Integration Lab: Building a Complete Robot Stack
Lesson 3: Simulation-to-Hardware Deployment
This lesson formalizes the mathematical and practical steps required to move a controller and robot model from a purely simulated environment to a real robot. We connect continuous-time models, their discrete-time implementations, sampling and delay, unit scaling and calibration, and show how to keep stability and performance guarantees when the real robot deviates from the simulation model. Short multi-language code snippets illustrate how to implement the same discrete-time controller in Python, C++, Java, and MATLAB/Simulink.
1. What Is Simulation-to-Hardware Deployment?
In this integration lab, we assume you already have a closed-loop design for a simple robot subsystem (e.g., a wheel velocity controller or a single revolute joint), tested in a physics-based simulator. Simulation-to-hardware deployment is the process of taking the same mathematical controller and:
- Discretizing it for a given sampling period \( T_s \),
- Mapping simulated signals to real sensor/actuator channels,
- Accounting for delays, noise, and unmodeled dynamics, and
- Verifying that closed-loop stability and performance are preserved (within margins).
Let the (continuous-time) robot subsystem be modeled as an LTI system with state \( \mathbf{x}(t)\in\mathbb{R}^n \), input \( \mathbf{u}(t)\in\mathbb{R}^m \) and output \( \mathbf{y}(t)\in\mathbb{R}^p \):
\[ \dot{\mathbf{x}}(t) = \mathbf{A}\mathbf{x}(t) + \mathbf{B}\mathbf{u}(t),\quad \mathbf{y}(t) = \mathbf{C}\mathbf{x}(t) + \mathbf{D}\mathbf{u}(t). \]
In simulation, the physics engine numerically integrates this model (possibly with contact, friction, etc.) at a small step size. For deployment on a digital controller running at sampling period \( T_s \), we require an equivalent discrete-time model and a controller that respects timing and unit constraints.
flowchart TD
A["Design CT/LTI model and controller"] --> B["Discretize model and controller (Ts)"]
B --> C["Implement controller loop in code"]
C --> D["Connect to real sensors and actuators"]
D --> E["Log data and compare to simulation"]
E --> F["Adjust model/parameters and redeploy"]
This lesson focuses on the mathematical backbone of this flow: how discretization, uncertainty, and scaling interact when moving from simulation to real hardware.
2. Nominal Model vs. Real Robot
Even for a simple revolute joint driven by a DC motor, the simulator and the real robot rarely match perfectly. Consider a simplified joint model:
\[ J \ddot{q}(t) + b \dot{q}(t) = u(t), \]
where \( q(t) \) is the joint angle, \( J > 0 \) the inertia, \( b \ge 0 \) a viscous friction coefficient, and \( u(t) \) is a torque command (e.g., proportional to motor current).
Defining the state \( \mathbf{x}(t) = \begin{bmatrix} q(t) \\ \dot{q}(t) \end{bmatrix} \), we obtain
\[ \dot{\mathbf{x}}(t) = \begin{bmatrix} 0 & 1 \\ 0 & -\dfrac{b}{J} \end{bmatrix}\mathbf{x}(t) + \begin{bmatrix} 0 \\ \dfrac{1}{J} \end{bmatrix} u(t) = \mathbf{A}\mathbf{x}(t) + \mathbf{B}u(t). \]
In simulation, you choose \( J \) and \( b \) as nominal parameters \( J_0, b_0 \), yielding a nominal model \( (\mathbf{A}_0, \mathbf{B}_0) \). The real robot has slightly different parameters \( J = J_0 + \Delta J \), \( b = b_0 + \Delta b \), and additional unmodeled effects. After discretization with sampling period \( T_s \), we obtain:
\[ \mathbf{x}_{k+1}^{\text{real}} = \mathbf{A}_d \,\mathbf{x}_k^{\text{real}} + \mathbf{B}_d \,u_k + \mathbf{w}_k, \]
where \( \mathbf{w}_k \) lumps together:
- Parameter mismatch (\( \Delta J, \Delta b \)),
- Disturbance torques (e.g., friction spikes, contact),
- Sensor noise and quantization.
The key idea: if your controller is robust to bounded disturbances \( \mathbf{w}_k \) and parameter errors, then it is more likely to “survive” the simulation-to-hardware transition.
3. Continuous-to-Discrete Conversion (Zero-Order Hold)
A common situation in robotics is a microcontroller applying a piecewise-constant control input between sampling instants: \( u(t) = u_k \) for \( kT_s \le t < (k+1)T_s \). This is the zero-order hold (ZOH) assumption. For an LTI system \( \dot{\mathbf{x}} = \mathbf{A}\mathbf{x} + \mathbf{B}\mathbf{u} \) with ZOH, the exact discrete-time model is:
\[ \mathbf{x}_{k+1} = \mathbf{A}_d \mathbf{x}_k + \mathbf{B}_d \mathbf{u}_k, \quad \mathbf{A}_d = e^{\mathbf{A}T_s},\quad \mathbf{B}_d = \int_0^{T_s} e^{\mathbf{A}\tau}\,\mathbf{B}\, \mathrm{d}\tau. \]
For the first-order example \( \dot{x}(t) = a x(t) + b u(t) \) with scalar state and input, we obtain
\[ x_{k+1} = e^{aT_s} x_k + \left(\dfrac{e^{aT_s} - 1}{a}\right) b\, u_k \quad (a \ne 0). \]
If \( a < 0 \), the continuous-time system is asymptotically stable. The corresponding discrete-time eigenvalue is \( \lambda_d = e^{aT_s} \). Because \( a < 0 \Rightarrow e^{aT_s} \in (0,1) \), we have:
\[ | \lambda_d | = | e^{aT_s} | = e^{aT_s} < 1, \]
so the discrete-time system is Schur stable (all eigenvalues strictly inside the unit disc). This argument generalizes: if all eigenvalues of \( \mathbf{A} \) satisfy \( \Re(\lambda_i)<0 \), then all eigenvalues of \( \mathbf{A}_d = e^{\mathbf{A}T_s} \) satisfy \( |\lambda_{i,d}| < 1 \).
In practice, simulators may internally use much smaller step sizes \( h_{\text{sim}} \). When deploying to hardware, you must ensure that your chosen \( T_s \) is:
- Compatible with real-time constraints (CPU load, communication), and
- Small enough that discretization does not destroy stability margins (e.g., \( T_s \ll \frac{1}{\omega_n} \) for a second-order system with natural frequency \( \omega_n \)).
4. Closed-Loop Dynamics and Sim-to-Real Gap
Suppose we use state feedback (designed in your linear control course) in discrete time:
\[ \mathbf{u}_k = -\mathbf{K} \mathbf{x}_k. \]
The nominal discrete closed loop is
\[ \mathbf{x}_{k+1} = (\mathbf{A}_d - \mathbf{B}_d\mathbf{K})\mathbf{x}_k = \mathbf{A}_{\text{cl}}\mathbf{x}_k. \]
In reality, with disturbance and modeling error \( \mathbf{w}_k \), we have
\[ \mathbf{x}_{k+1}^{\text{real}} = \mathbf{A}_{\text{cl}} \mathbf{x}_k^{\text{real}} + \mathbf{w}_k. \]
Proposition (bounded-error under Schur stability).
Assume that \( \mathbf{A}_{\text{cl}} \) is Schur (all eigenvalues strictly inside the unit disc) and that \( \|\mathbf{w}_k\| \le \bar{w} \) for all \( k \ge 0 \). Then there exists a constant \( M > 0 \) such that
\[ \|\mathbf{x}_k^{\text{real}}\| \le M \|\mathbf{x}_0^{\text{real}}\| + \dfrac{M}{1-\rho}\,\bar{w}, \quad \rho = \rho(\mathbf{A}_{\text{cl}}) < 1, \]
where \( \rho(\cdot) \) is the spectral radius. The proof uses the discrete-time variation of constants formula:
\[ \mathbf{x}_k^{\text{real}} = \mathbf{A}_{\text{cl}}^k \mathbf{x}_0^{\text{real}} + \sum_{i=0}^{k-1} \mathbf{A}_{\text{cl}}^{k-1-i}\mathbf{w}_i, \]
and the fact that powers of a Schur matrix are geometrically bounded, \( \|\mathbf{A}_{\text{cl}}^k\| \le M \rho^k \) for some induced matrix norm. This bound tells us that if your feedback gain \( \mathbf{K} \) stabilizes the nominal discrete model with a comfortable margin (\( \rho \ll 1 \)), then moderate modeling errors and disturbances in hardware will only lead to bounded steady-state deviations.
Therefore, when transitioning from simulation to hardware, you should:
- Estimate or conservatively bound \( \|\mathbf{w}_k\| \) (e.g., from friction and sensor noise),
- Check that \( \rho(\mathbf{A}_{\text{cl}}) \) is significantly below 1 in simulation, and
- Use logs on the real robot to see if the closed-loop behavior matches the predicted bounds.
5. Units, Scaling, and Calibration
Simulators usually express quantities in SI units (meters, radians, seconds). Real sensors and actuators operate in raw units (encoder counts, ADC values, PWM duty cycles). The mapping between physical and raw units can be written as affine transformations:
\[ \mathbf{y}_{\text{phys}} = \mathbf{S}_y \mathbf{y}_{\text{raw}} + \mathbf{b}_y, \quad \mathbf{u}_{\text{raw}} = \mathbf{S}_u \mathbf{u}_{\text{phys}} + \mathbf{b}_u, \]
where \( \mathbf{S}_y \) and \( \mathbf{S}_u \) are (typically diagonal) scaling matrices and \( \mathbf{b}_y, \mathbf{b}_u \) are offsets (e.g., zero biases). For a single encoder with resolution \( N \) counts per revolution, the joint angle in radians is:
\[ q = \dfrac{2\pi}{N}\,c, \]
where \( c \) is the count. Similarly, a PWM signal in the range \( d \in [d_{\min}, d_{\max}] \) might correspond to a torque range \( u_{\text{phys}} \in [u_{\min}, u_{\max}] \) via
\[ u_{\text{phys}} = \dfrac{u_{\max} - u_{\min}}{d_{\max} - d_{\min}} \left(d - d_{\min}\right) + u_{\min}. \]
In your discrete-time model, it is best to keep states and inputs in physical units, and embed all scaling in the I/O interface. That is, your controller computes \( \mathbf{u}_{\text{phys},k} \) from \( \mathbf{y}_{\text{phys},k} \), and a thin wrapper converts between raw and physical units.
6. Python Implementation of a Discrete-Time Joint Controller
We implement a simple discrete-time PD controller for the joint angle \( q_k \) with reference \( q_k^{\ast} \). The control law is
\[ u_k = K_p (q_k^{\ast} - q_k) + K_d (\dot{q}_k^{\ast} - \dot{q}_k). \]
The same controller can be used in simulation and hardware, as long as the sampling period \( T_s \) and unit conversions are consistent. In Python, a minimal implementation (without ROS2 details) looks like:
import time
import numpy as np
# Controller gains (tuned in simulation)
Kp = 8.0
Kd = 1.5
Ts = 0.01 # 10 ms sampling
# Desired trajectory (constant reference for simplicity)
q_ref = 1.0 # radians
dq_ref = 0.0 # rad/s
def counts_to_rad(counts, N_counts):
return 2.0 * np.pi * counts / N_counts
def pwm_from_torque(u_phys, u_min, u_max, d_min, d_max):
# Clip torque command
u = max(min(u_phys, u_max), u_min)
# Affine mapping to duty cycle
return (u - u_min) * (d_max - d_min) / (u_max - u_min) + d_min
# Hardware-specific stubs (replace with real API, e.g. ROS2, vendor SDK)
def read_encoder_counts():
# TODO: read from real encoder
return 0
def read_velocity_estimate():
# TODO: could be estimated via finite differences or a velocity sensor
return 0.0
def write_pwm_command(duty):
# TODO: write to motor driver
pass
N_counts = 4096
u_min, u_max = -2.0, 2.0 # Nm
d_min, d_max = 0.1, 0.9 # duty cycle range
next_time = time.time()
while True:
# Wait until next sample
now = time.time()
if now < next_time:
time.sleep(next_time - now)
next_time += Ts
# Read sensors in raw units and convert
c = read_encoder_counts()
q = counts_to_rad(c, N_counts)
dq = read_velocity_estimate()
# PD control in physical units
e = q_ref - q
de = dq_ref - dq
u = Kp * e + Kd * de
# Convert to PWM and send
duty = pwm_from_torque(u, u_min, u_max, d_min, d_max)
write_pwm_command(duty)
In simulation (e.g., PyBullet, MuJoCo), you can reuse exactly the same
control loop, replacing the hardware-specific functions by simulator
calls. In ROS2,
rclpy would handle the timing and sensor/actuator topics,
but the core PD update remains identical.
7. C++ and Java Implementations of the Same Controller
The same discrete-time PD law should be written once mathematically and then translated into your implementation languages. Below is a sketch of a C++ class that could be used either in a simulation node or on embedded hardware:
#include <cmath>
#include <algorithm>
class JointPDController {
public:
JointPDController(double Kp, double Kd,
double u_min, double u_max,
double d_min, double d_max,
int N_counts)
: Kp_(Kp), Kd_(Kd),
u_min_(u_min), u_max_(u_max),
d_min_(d_min), d_max_(d_max),
N_counts_(N_counts) {}
double step(int counts, double dq,
double q_ref, double dq_ref)
{
double q = countsToRad(counts);
double e = q_ref - q;
double de = dq_ref - dq;
double u = Kp_ * e + Kd_ * de; // torque [Nm]
// Saturate
u = std::max(u_min_, std::min(u_max_, u));
// Convert to duty cycle
return torqueToDuty(u);
}
private:
double countsToRad(int counts) const {
return 2.0 * M_PI * static_cast<double>(counts)
/ static_cast<double>(N_counts_);
}
double torqueToDuty(double u) const {
return (u - u_min_) * (d_max_ - d_min_) / (u_max_ - u_min_) + d_min_;
}
double Kp_, Kd_;
double u_min_, u_max_;
double d_min_, d_max_;
int N_counts_;
};
In Java, the same structure applies. A minimal version (e.g., used inside a control loop thread) is:
public class JointPDController {
private final double Kp;
private final double Kd;
private final double uMin, uMax;
private final double dMin, dMax;
private final int nCounts;
public JointPDController(double Kp, double Kd,
double uMin, double uMax,
double dMin, double dMax,
int nCounts) {
this.Kp = Kp;
this.Kd = Kd;
this.uMin = uMin;
this.uMax = uMax;
this.dMin = dMin;
this.dMax = dMax;
this.nCounts = nCounts;
}
private double countsToRad(int counts) {
return 2.0 * Math.PI * ((double) counts) / ((double) nCounts);
}
private double torqueToDuty(double u) {
double uu = Math.max(uMin, Math.min(uMax, u));
return (uu - uMin) * (dMax - dMin) / (uMax - uMin) + dMin;
}
public double step(int counts, double dq,
double qRef, double dqRef) {
double q = countsToRad(counts);
double e = qRef - q;
double de = dqRef - dq;
double u = Kp * e + Kd * de;
return torqueToDuty(u);
}
}
In ROS2, the C++ variant would typically use rclcpp to
subscribe to joint states and publish duty-cycle commands. In Java, a
similar approach can be adopted using ROSJava or a custom TCP/UDP
communication layer. The mathematical controller stays unchanged.
flowchart TD
SModel["Single joint CT model"] --> Disc["Compute discrete A_d, B_d (ZOH)"]
Disc --> Ctrl["Design PD / state feedback gains"]
Ctrl --> SW["Implement same update in Python, C++, Java, MATLAB"]
SW --> Deploy["Connect to real sensors/actuators with scaling"]
Deploy --> Logs["Record logs and compare trajectories to simulation"]
8. MATLAB / Simulink Implementation and Verification
MATLAB and Simulink are widely used for rapid simulation and code generation for embedded controllers. A discrete-time PD controller with sampling period \( T_s \) can be expressed as:
\[ u_k = K_p (q_k^{\ast} - q_k) + K_d \dfrac{(q_k^{\ast} - q_{k-1}^{\ast}) - (q_k - q_{k-1})}{T_s}, \]
where the derivative term is implemented using finite differences. A simple MATLAB script to construct and simulate the discrete-time closed loop is:
Ts = 0.01; % Sampling period
J = 0.02; % Inertia
b = 0.05; % Viscous friction
A = [0 1;
0 -b/J];
B = [0;
1/J];
C = [1 0];
D = 0;
% Zero-order hold discretization
sysc = ss(A, B, C, D);
sysd = c2d(sysc, Ts, 'zoh');
Ad = sysd.A;
Bd = sysd.B;
% PD gains (from simulation tuning)
Kp = 8.0;
Kd = 1.5;
% State-space with PD on position and velocity (assuming full state feedback)
K = [Kp Kd];
Acl = Ad - Bd * K;
% Simulate step response
N = 1000;
x = [0; 0]; % Initial state
xHist = zeros(2, N);
for k = 1:N
xHist(:, k) = x;
u = -K * x; % reference = 0 for simplicity
x = Ad * x + Bd * u;
end
t = (0:N-1) * Ts;
q = xHist(1, :);
plot(t, q); grid on;
xlabel('time [s]');
ylabel('q [rad]');
title('Discrete-time closed-loop response');
In Simulink, you would represent the discrete-time PD controller using:
- A Discrete-Time Integrator or Unit Delay block for differences,
- A Gain block for \( K_p \) and \( K_d \),
- A Zero-Order Hold block at rate \( T_s \) on the plant input.
Once the discrete-time Simulink model is validated, you can use Embedded Coder (if available) to generate C code that should match your hand-written C++ implementation up to numerical details. Comparing responses between the Simulink simulation, your custom simulator, and the real robot is an essential part of closing the sim-to-real loop.
9. Problems and Solutions
Problem 1 (ZOH Discretization of a First-Order Motor Model). Consider the scalar system \( \dot{x}(t) = -a x(t) + b u(t) \) with \( a > 0 \), \( b > 0 \) and ZOH input \( u(t) = u_k \) on \( [kT_s,(k+1)T_s) \). Derive the exact discrete-time model \( x_{k+1} = \phi x_k + \gamma u_k \) and show that \( |\phi| < 1 \).
Solution. The solution of the ODE with constant input on one sampling interval is
\[ x((k+1)T_s) = e^{-aT_s} x(kT_s) + \int_0^{T_s} e^{-a(T_s - \tau)} b u_k \,\mathrm{d}\tau. \]
Evaluating the integral,
\[ \int_0^{T_s} e^{-a(T_s - \tau)} \,\mathrm{d}\tau = \int_0^{T_s} e^{-a\theta} \,\mathrm{d}\theta = \dfrac{1 - e^{-aT_s}}{a}. \]
Thus
\[ x_{k+1} = e^{-aT_s} x_k + \left(\dfrac{1 - e^{-aT_s}}{a}\right) b u_k. \]
Hence \( \phi = e^{-aT_s} \), \( \gamma = \dfrac{1 - e^{-aT_s}}{a} b \). For \( a > 0 \) and \( T_s > 0 \), we have \( e^{-aT_s} \in (0,1) \), so \( |\phi| < 1 \) and the discrete-time system is stable.
Problem 2 (Effect of Small Gain Mismatch on Steady-State Error). In simulation, the true plant is \( \dot{x}(t) = -a x(t) + b u(t) \) with \( a,b > 0 \). You design a proportional controller \( u(t) = k(x^{\ast} - x(t)) \) for a constant reference \( x^{\ast} \) and tune \( k \) such that the closed-loop continuous-time system has zero steady-state error in simulation. On the real robot, the input gain is actually \( b(1+\delta) \) with small \( |\delta| \ll 1 \). Compute the steady-state error on hardware (still in continuous time) as a function of \( \delta \).
Solution. The simulation closed loop is
\[ \dot{x} = -a x + b k (x^{\ast} - x) = -(a + b k) x + b k x^{\ast}. \]
The equilibrium satisfies \( 0 = -(a + b k) x_{\text{ss}} + b k x^{\ast} \), hence \( x_{\text{ss}} = \dfrac{b k}{a + b k} x^{\ast} \). Zero steady-state error requires \( x_{\text{ss}} = x^{\ast} \), so we need
\[ \dfrac{b k}{a + b k} = 1 \quad \Rightarrow \quad a = 0, \]
which is impossible for \( a > 0 \). Thus a pure proportional controller cannot remove steady-state error for a type-0 plant. In practice, the proportional gain is tuned so that the error is small but nonzero. Let us instead consider the hardware steady state:
\[ \dot{x} = -a x + b(1+\delta) k (x^{\ast} - x) = -(a + b(1+\delta)k)x + b(1+\delta)k x^{\ast}. \]
The steady state on hardware is
\[ x_{\text{ss,real}} = \dfrac{b(1+\delta)k}{a + b(1+\delta)k} x^{\ast}. \]
The steady-state error is
\[ e_{\text{ss}} = x^{\ast} - x_{\text{ss,real}} = x^{\ast}\left(1 - \dfrac{b(1+\delta)k}{a + b(1+\delta)k}\right) = x^{\ast}\dfrac{a}{a + b(1+\delta)k}. \]
Compared to the simulated error \( e_{\text{ss,sim}} = x^{\ast}\dfrac{a}{a + b k} \), the gain mismatch perturbs the denominator. For small \( \delta \), the relative change in error is approximately linear in \( \delta \), via a first-order Taylor expansion.
Problem 3 (Sampling Constraint from Continuous Poles). A continuous-time closed-loop robot joint has dominant poles at \( s_{1,2} = -\zeta\omega_n \pm j \omega_n \sqrt{1-\zeta^2} \) with \( \zeta \in (0,1) \), natural frequency \( \omega_n \). Show that the corresponding discrete-time poles under ZOH with sampling period \( T_s \) satisfy \( |z_{1,2}| = e^{-\zeta\omega_n T_s} \). What condition on \( T_s \) guarantees that the magnitude is at most \( r \in (0,1) \)?
Solution. Under sampling, each continuous pole \( s \) maps to a discrete pole \( z = e^{s T_s} \). Thus
\[ z_{1,2} = e^{(-\zeta\omega_n \pm j \omega_n \sqrt{1-\zeta^2})T_s} = e^{-\zeta\omega_n T_s} \cdot e^{\pm j \omega_n \sqrt{1-\zeta^2} T_s}. \]
The magnitude is therefore
\[ |z_{1,2}| = e^{-\zeta\omega_n T_s}. \]
We require \( |z_{1,2}| \le r \), so \( e^{-\zeta\omega_n T_s} \le r \). Taking natural logarithms:
\[ -\zeta\omega_n T_s \le \ln r \quad \Rightarrow \quad T_s \ge -\dfrac{\ln r}{\zeta\omega_n}. \]
Since \( r \in (0,1) \), we have \( \ln r < 0 \), so the inequality direction is correct. In practice, you choose \( T_s \) smaller than this upper bound on the contraction factor to maintain your desired discrete damping.
Problem 4 (Encoder Calibration and Position Error). A joint encoder returns integer counts \( c_k \) in \( [0,N-1] \) over a full revolution (no index pulse). You use the calibration \( q_k = \dfrac{2\pi}{N} c_k \). However, due to slight mechanics, the true angle is \( q_k^{\text{true}} = q_k + \varepsilon_k \), with \( |\varepsilon_k| \le \varepsilon_{\max} \). Derive a bound on the error in the PD torque command \( u_k = K_p (q^{\ast} - q_k) \) due to this calibration error.
Solution. The true error is \( e_k^{\text{true}} = q^{\ast} - q_k^{\text{true}} \), while the measured error is \( e_k = q^{\ast} - q_k \). Their difference is
\[ e_k^{\text{true}} - e_k = (q^{\ast} - q_k - \varepsilon_k) - (q^{\ast} - q_k) = -\varepsilon_k. \]
The torque command using the measured error is \( u_k = K_p e_k \), while the ideal torque (with perfect angle) would be \( u_k^{\text{ideal}} = K_p e_k^{\text{true}} \). Their difference is
\[ u_k^{\text{ideal}} - u_k = K_p (e_k^{\text{true}} - e_k) = -K_p \varepsilon_k. \]
Therefore, the torque error is bounded by \( |u_k^{\text{ideal}} - u_k| \le |K_p| \varepsilon_{\max} \). This gives a direct link between encoder calibration accuracy and actuation error; in sim-to-real deployment, ensuring small \( \varepsilon_{\max} \) (via mechanical alignment and calibration) is crucial if \( K_p \) is large.
10. Summary
In this lesson, we connected your linear control knowledge to the practical task of deploying a controller from simulation to real robot hardware. We:
- Modeled a simple joint as an LTI system and introduced the idea of a nominal vs. real plant,
- Derived exact ZOH discretizations and showed how continuous stability maps to discrete stability,
- Analyzed the closed-loop dynamics under disturbances, obtaining bounds that help reason about the sim-to-real gap,
- Formalized unit scaling and calibration as affine mappings on inputs and outputs,
- Implemented the same discrete-time PD controller in Python, C++, Java, and MATLAB / Simulink, suitable for both simulation and hardware.
These concepts are essential for the integration lab: they let you design and test controllers in a safe simulator, then systematically migrate them onto real robot hardware while keeping track of stability, performance, and measurement errors.
11. References
- Åström, K. J., & Wittenmark, B. (1984). Computer controlled systems: Theory and design. IEEE Transactions and associated journal articles.
- Franklin, G. F., Powell, J. D., & Workman, M. L. (1990). Digital control of dynamic systems. Various earlier journal contributions on discrete-time control.
- Zames, G. (1981). Feedback and optimal sensitivity: Model reference transformations, multiplicative seminorms, and approximate inverses. IEEE Transactions on Automatic Control, 26(2), 301–320.
- Doyle, J. C. (1982). Analysis of feedback systems with structured uncertainties. IEE Proceedings D (Control Theory and Applications), 129(6), 242–250.
- Goodwin, G. C., Ramadge, P. J., & Caines, P. E. (1978). Discrete-time multivariable adaptive control. IEEE Transactions on Automatic Control, 23(6), 954–962.
- Jakobi, N., Husbands, P., & Harvey, I. (1995). Noise and the reality gap: The use of simulation in evolutionary robotics. Proceedings of the European Conference on Artificial Life, 704–720.
- Spong, M. W., & Vidyasagar, M. (1987). Robust linear compensator design for nonlinear robotic control. IEEE Journal of Robotics and Automation, 3(4), 345–351.
- Slotine, J.-J. E., & Li, W. (1987). On the adaptive control of robot manipulators. International Journal of Robotics Research, 6(3), 49–59.
- Khalil, H. K. (1980). On the robustness of linear feedback systems to a class of nonlinear perturbations. IEEE Transactions on Automatic Control, 25(4), 883–886.
- Kuo, B. C. (1963). Theory and applications of sampled-data systems. Journal and conference articles on sampling and discrete-time control.