From a datasheet model to a robust deadbeat controller, on real hardware.
Design, discretization and experimental validation of four digital speed controllers for a DC motor,
running in real time on a Texas Instruments C2000 microcontroller.
Full report (PDF, 78 pages) · Results · Getting started · Repository structure
- Four controllers, one real motor: cascade PI with feedforward, gain-scheduled PI on identified parameters, Deadbeat and Dahlin, all executed on a LAUNCHXL-F28379D at 10 kHz.
- The real motor differs from the datasheet: least-squares identification found an armature inductance ≈ 50 % higher than nominal (6.1 mH vs 4.1 mH). The model built on the estimated parameters overlaps the measured current transient almost exactly.
-
Asymmetric friction: the identification showed that viscous friction is 14.1 % higher in one direction of rotation. It is modelled with a velocity-dependent PCHIP map
$b(\omega)$ that drives gain scheduling of the speed PI. -
A deadbeat that actually works: the unconstrained deadbeat asks for ±2700 V with violent ringing. A custom polynomial anti-windup
$F(z)$ makes it the best performer on the real motor, with the fastest rise, no overshoot and robustness to load. - Steady-state ripple of ±4 rpm at 1000 rpm (< 0.5 %), limited by encoder quantization and PWM ripple.
- Experimental setup
- Control architecture
- 1 · Modelling and speed filtering
- 2 · Cascade PI control
- 3 · Least-squares parameter identification
- 4 · Deadbeat controller
- 5 · Dahlin controller
- Final comparison
- Repository structure
- Getting started
- Authors
- License
At 1500 rpm the encoder produces only about 20 counts per sample, so a single count is about 5 % of the measured speed. This is why the speed feedback must be filtered.
flowchart LR
REF(["ω_ref"]) --> E1(("Σ"))
subgraph OUTER["Outer loop · speed"]
E1 --> SPI["Speed controller<br/><b>PI · Deadbeat · Dahlin</b>"]
SPI --> SAT1["Current limit<br/>+ anti-windup"]
end
subgraph INNER["Inner loop · current · τ_i = 1 ms"]
E2(("Σ")) --> CPI["Current PI"]
CPI --> E3(("+"))
E3 --> SAT2["Voltage limit ± V_dc<br/>+ anti-windup"]
end
subgraph HW["LAUNCHXL-F28379D + test bench"]
PWM["PWM<br/>10 kHz"] --> MOT[["DC motor<br/>+ load"]]
MOT --> ENC["Encoder<br/>8192 counts/rev"]
ENC --> LPF["Low-pass filter<br/>τ_f = 2 ms"]
end
FF["Feedforward<br/>J·ω̇_d / k_m"] -.-> E2
SAT1 --> E2
SAT2 --> PWM
MOT -- "i (shunt + ADC)" --> E2
LPF -- "ω" --> E1
LPF -. "back-EMF k_m·ω" .-> E3
Every saturation block has its own anti-windup: back-calculation for the PI loops, and a polynomial
The motor is modelled as two coupled first-order subsystems:
The raw encoder speed is filtered by a first-order low-pass filter, discretized with backward Euler:
The time constant
📂 01_DC_Motor_Model_and_PWM · 02_Base_Model_and_Encoder
Both PI controllers are tuned by pole-zero cancellation, so that each closed loop behaves like a first-order system. The speed loop is chosen 10× slower than the current loop:
| Loop | Design rule | Target | Gains (datasheet parameters) |
|---|---|---|---|
| Current |
|
|
|
| Speed |
|
|
All values come from the motor datasheet:
Both controllers are discretized with Tustin and implemented in incremental form inside a MATLAB Function block. On saturation, the clamped output is stored as the controller state, which acts as the anti-windup:
out = (Kp*Ts/(2*Ti))*(in + in_prev) + Kp*(in - in_prev) + out_prev;
out = min(max(out, LowSat), HiSat); % saturation
out_prev = out; in_prev = in; % anti-windup: store the saturated valueFor trajectory tracking, a feedforward current
![]() 2000 rpm step: anti-windup removes the overshoot and the undershoot at the falling edge |
Tracking a trapezoidal trajectory with positive and negative speeds |
📂 03_PI_Current_Control · 04_PI_Speed_Control · 05_Trajectory_Tracking
Each subsystem is discretized with backward Euler into the regression form
-
Electrical (R, L): a voltage step is applied and the current is sampled for 1000 samples, before the rotor starts moving (so the back-EMF is zero). This gives
$R = (1-\alpha_1)/\alpha_2$ and$L = \alpha_1 T_s/\alpha_2$ . -
Mechanical (J, b): square-wave tests from 14 V to 48 V, with torque
$k_m i$ as input and$\omega$ as output, over 20 000 samples.$J$ is averaged over the unsaturated tests.
| Parameter | Nominal | Estimated |
|---|---|---|
| Armature resistance |
3.2 Ω | 3.1532 Ω |
| Armature inductance |
4.1 mH | 6.1 mH (+49 %) |
| Equivalent inertia |
7.04 × 10⁻⁵ kg·m² | 8.127 × 10⁻⁵ kg·m² |
| Viscous friction |
6.65 × 10⁻⁴ N·m·s/rad |
non-linear, asymmetric |
Unidirectional tests at 20 V gave
![]() Current transient: datasheet model (top) vs. identified model (bottom) against the real motor |
![]() Identified asymmetric friction profile b(ω), PCHIP interpolation |
The deadbeat controller is synthesized directly in the z-domain. The plant (estimated parameters,
Unconstrained, the controller requests ±2700 V with alternating-sign ringing. A polynomial anti-windup replaces the controller denominator with a stable
![]() Simulated control effort: unconstrained deadbeat (red, ±2700 V ringing) vs. anti-windup (blue) |
![]() Real motor, 0 → 1000 rpm, with and without load: anti-windup (blue) vs. none (red) |
Without anti-windup, the real motor overshoots past 1500 rpm and shows a delayed "bump" below zero after the falling edge. With
The Dahlin controller relaxes the deadbeat specification. Instead of a pure delay, the closed loop must behave like a delayed first-order system with time constant
![]() Effect of λ on the speed response |
![]() Real motor, with and without load and anti-windup |
The best version of each controller was tested on the same 0 → 1000 → 0 rpm sequence, with and without load.
![]() Without load |
![]() With load |
![]() Rising edge (no load) |
![]() Falling edge (no load) |
| Cascade PI | Deadbeat | Dahlin | |
|---|---|---|---|
| Synthesis | continuous-time, Tustin | direct discrete, |
direct discrete, |
| Rise time | fast start, long tail | 🥇 fastest | smooth exponential |
| Overshoot | none | none | none |
| Falling edge | small bump near zero | 🥇 sharp, no undershoot | deceleration plateau |
| Robustness to load | slower settling | 🥇 virtually unaffected | most degraded |
| Control effort | moderate | highest (relies on $F(z)$) | 🥇 lowest |
| Verdict | best performance / hardware-stress compromise | best overall | when smoothness matters more than speed |
Takeaway: an aggressive discrete-time controller is only usable if saturation is handled correctly. Paired with a well-tuned anti-windup polynomial, the deadbeat goes from theoretically fragile to the most effective controller on this setup.
.
├── 01_DC_Motor_Model_and_PWM/ Continuous-time motor model, PWM generation and sampling
├── 02_Base_Model_and_Encoder/ Base hardware model: ADC, encoder, PWM on the F28379D
├── 03_PI_Current_Control/ Inner current loop
├── 04_PI_Speed_Control/ Cascade PI (current + speed) with anti-windup
├── 05_Trajectory_Tracking/ Trajectory tracking with feedforward
├── 06_Least_Squares_Estimation/
│ ├── acquisition_model/ Firmware used to log the identification data
│ ├── LR_estimation/ R, L estimation script, validation model, figures
│ ├── Jb_estimation/ J, b estimation script, validation model, figures
│ └── data/ Raw acquisitions (14 V to 48 V, reversed motor, unidirectional)
├── 07_Deadbeat_Controller/
│ └── results/ simulation/ and experimental/ data, plots, scripts
├── 08_Dahlin_Controller/
│ └── results/ simulation/, lambda_tuning/ and experimental/
├── docs/images/ Figures used in this README
└── report/ Final project report (PDF)
Each module folder is self-contained: the models sit next to the parameter scripts they need, so you only have to cd into the folder.
- MATLAB / Simulink R2024b (all models are saved with this release)
- Embedded Coder + C2000 Microcontroller Blockset (hardware deployment)
- Control System Toolbox (
c2d,minreal, transfer functions) - Hardware, only for the experimental part: LAUNCHXL-F28379D, DC motor with encoder and a power stage
cd 08_Dahlin_Controller
Lez_11_DahlinModel_Parameters_Student % loads parameters and synthesizes D(z), F(z)
open_system('DahlinModel_Student') % press Runcdinto the module and run its parameter script. Setcomportto the board's serial port.- Open the target model (e.g.
Lez_11_basePerTracing.slx) and click Build, Deploy & Start on the LaunchPad. - Open the host interface model,
CommandInterface.slx(Lez_6_CommandInterface.slxin module 05), and run it to send speed references and log data over serial.
| Module | Parameter script | Simulation model | Target model (board) |
|---|---|---|---|
| 01 | param.m, C01A_Parameters.m |
DCMotor.slx, C01A_DC_MotorEquation.slx, PWM_Sampling.slx |
PWMAudible.slx, PWM_Lez_2.slx |
| 02 | BaseModelForControl_Parameters.m, DC_MotorEquation_Encoder_Parameters.m, param.m |
DC_MotorEquation_Encoder.slx, DCMotor.slx |
BaseModelForControl.slx |
| 03 | param.m |
DCMotor.slx |
|
| 04 | BaseModelForControl_Parameters.m |
BaseModelForControl.slx, BaseModelForControlPid.slx |
|
| 05 | Lez_6_Parameters.m |
Lez_6_BasePerTracing.slx (+ Lez_6_CommandInterface.slx) |
|
| 06 | acquisition_model/Our_parameter_new.m |
LR_Model.slx, Jb_Model.slx |
Lez_9_BasePerTracing.slx |
| 07 | Lez_10_parameters.m / DeadBeatModel_Parameters_Student.m |
DeadbeatModel_Student.slx |
Lez_10_basePerTracing.slx |
| 08 | Lez_11_DahlinModel_Parameters_Student.m |
DahlinModel_Student.slx |
Lez_11_basePerTracing.slx |
cd 06_Least_Squares_Estimation/LR_estimation
LR_estimation_DCMotor % R, L + validation against the datasheet model
cd ../Jb_estimation
Jb_estimation_DCMotor % J, b + validationEvery results/ folder contains the raw .mat logs and a plot_*.m script that regenerates its figures.
| Andrea Gallo | Vittorio Ragazzini | Leonardo Lodolini |
B.Sc. in Automation Engineering, Alma Mater Studiorum, University of Bologna. Developed for the course Laboratory of Automation Systems (A.Y. 2025/2026), held by Prof. Claudio Melchiorri and Prof. Davide Samorì.
The code and documentation written by the authors are released under the MIT License.
Some Simulink templates (the *_Student models and the base hardware models) were provided as course material and then completed by the authors. mcb_pmsm_foc_qep_f28379d_data.m is © The MathWorks, Inc. and is not covered by the MIT license.











