State Space representation
We can convert the set of ODE into a state space representation. The final bode plot of the block position is:
Code
using DiscretePIDs , ControlSystems , Plots , LinearAlgebra
# System parameters
Ts = 0.02 # sampling time
Tf = 2.5 ; #final simulation time
g = 9.81 #gravity
α = 0.0 # slope
μ = 1.0 # friction coefficient
x_0 = - 2.0 # starting position
dx_0 = 0.0 # starting velocity
τ = 20.0 # torque constant
# State Space Matrix
A = [0 1 0
0 - μ 1
0 0 - τ];
B = [0
0
τ];
C = [1 0 0
0 1 0 ];
sys = ss (A, B, C, 0.0 ) # Continuous
plot! (bodeplot (tf (sys)),pzmap (tf (sys)))
Starting Bode Plot and PZ Map
It has the shape we expect from a motor + friction. Slow pole for the mass + friction and a faster pole for the current & inductance.
Numerically they are:
Code
3-element Vector{Float64}:
-20.0
-1.0
0.0
We see that we start with all the pole in the left-half plane, which is good.
Pole Placement
We can design a controller with pole placement.
For some reason pole placement doesn’t work for the observer, I use a Kalman Filter with random fast values.
Code
observability (A, C).isobservable &
controllability (A, B).iscontrollable; #OK
ε = 0.01 ;
pp = 15.0 ;
poles_cont = - 2.0 * [pp + ε, pp - ε, pp];
L = real (place (sys, poles_cont, : c));
poles_obs = poles_cont * 10.0 ;
K = place (1.0 * A' , 1.0 * C' , poles_obs)'
cont = observer_controller (sys, L, K; direct= false );
We can check the effect of the new controller on the loop
Code
closedLoop = feedback (sys * cont)
print (poles (closedLoop));
setPlotScale ("dB" )
plot! (bodeplot (closedLoop[1 , 1 ], 0.1 : 40 ), pzmap (closedLoop))
ComplexF64[-29.979998924597755 + 0.0im, -30.000002152199972 + 0.0im, -30.019998923202337 + 0.0im, -300.0000000000004 + 0.0im, -300.1999999999752 + 0.0im, -299.80000000004117 + 0.0im]
We can compare this to the open-loop response in @start-bode . We can see that we achieve unitary gain throughout the whole low-frequency range.
We can convert the pole placement controller into the standard PD gain form.
Code
K = L[1 ];
Ti = 0 ;
Td = L[2 ] / L[1 ];
pid = DiscretePID (; K, Ts, Ti, Td);
Simulation
We can simulate this with a motor that only outputs the position:
Code
sysreal = ss (A, B, [1 0 0 ], 0.0 )
ctrl = function (x, t)
y = (sysreal.C* x)[] # measurement
d = 0 * [1.0 ] # disturbance
r = 2.0 * (t >= 1 ) # reference
# u = pid(r, y) # control signal
# u + d # Plant input is control signal + disturbance
# u =1
e = x - [r; 0.0 ; 0.0 ]
e [3 ] = 0.0 # torque not observable, just ignore it in the final feedback
u = - L * e + d
u = [maximum ([- 20.0 minimum ([20.0 u])])]
end
t = 0 : Ts: Tf
res = lsim (sysreal, ctrl, t)
display (plot (res,
plotu= true ,
plotx= true ,
ploty= false
))
ylabel! ("u" , sp= 1 );
ylabel! ("x" , sp= 2 );
ylabel! ("v" , sp= 3 );
ylabel! ("T" , sp= 4 );
For more stats:
Code
si = stepinfo (res);
plot (si);title! ("Step Response" )
We can also simulate it in a SIMULINK-like environment:
Code
using FMI , DifferentialEquations
fmuPath = abspath (joinpath (@__DIR__ ,
".." ,".." ,".." ,
"modelica" ,
"ControlChallenges" ,
"ControlChallenges.BlockOnSlope_Challenges.Examples.WithFriction.fmu" ))
fmu = loadFMU (fmuPath);
simData = simulateME (
fmu,
(0.0 , 5.0 );
recordValues= ["blockOnSlope.x" ,
"blockOnSlope.xd" ,
"blockOnSlope.usat" ],
showProgress= false );
unloadFMU (fmu);
plot (simData, states= false , timeEvents= false )
There is a slight difference between the lsim simulation and the FMU simulation. I need to recheck some stuff.