Skip to content

Commit 596f2d0

Browse files
authored
include full control analysis in quad example (#169)
* include full control analysis in quad example * update IC * tweak operating point
1 parent 677cb7b commit 596f2d0

File tree

1 file changed

+39
-45
lines changed

1 file changed

+39
-45
lines changed

docs/src/examples/quad.md

Lines changed: 39 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ load_mass = 0.1 # Mass of the load.
3131
cable_length = 1 # Length of the cable.
3232
cable_mass = 5 # Mass of the cable.
3333
cable_diameter = 0.01 # Diameter of the cable.
34-
number_of_links = 5 # Number of links in the cable.
34+
number_of_links = 4 # Number of links in the cable.
3535
3636
# PID Controller parameters
3737
kalt = 2.7
@@ -107,11 +107,8 @@ function RotorCraft(; closed_loop = true, addload=true, L=nothing, outputs = not
107107
108108
thrusters = [Thruster(name = Symbol("thruster$i"), clockwise = (i-1) % 2 == 0) for i = 1:num_arms]
109109
@named body = Body(m = body_mass, state_priority = 100, I_11=0.01, I_22=0.01, I_33=0.01, air_resistance=1, isroot=true)
110-
# @named freemotion = FreeMotion(state=true, isroot=true, quat=false) # We use Euler angles to describe the orientation of the rotorcraft.
111110
112111
connections = [
113-
# connect(world.frame_b, freemotion.frame_a)
114-
# connect(freemotion.frame_b, body.frame_a)
115112
y_alt ~ body.r_0[2]
116113
y_roll ~ body.phi[3]
117114
y_pitch ~ body.phi[1]
@@ -195,7 +192,8 @@ ssys = structural_simplify(multibody(model))
195192
# display(unknowns(ssys))
196193
op = [
197194
model.body.v_0[1] => 0;
198-
collect(model.cable.joint_2.phi) .=> 0.03;
195+
model.Calt.int.x => 4;
196+
collect(model.cable.joint_2.phi) .=> 0.1;
199197
]
200198
201199
prob = ODEProblem(ssys, op, (0, 20))
@@ -271,12 +269,11 @@ op = [
271269
model.body.r_0[2] => 1e-3
272270
model.body.r_0[3] => 1e-3
273271
model.body.r_0[1] => 1e-3
274-
collect(model.cable.joint_2.phi) .=> 0.1 # Usa a larger initial cable bend since this controller is more robust
272+
collect(model.cable.joint_2.phi) .=> 1 # Usa a larger initial cable bend since this controller is more robust
275273
model.world.g => 9.81;
276274
collect(model.body.phid) .=> 0;
277275
collect(D.(model.body.phi)) .=> 0;
278-
model.feedback_gain.input.u[9] => 0;
279-
model.feedback_gain.input.u[12] => 0;
276+
collect(model.feedback_gain.input.u) .=> 0;
280277
model.Ie_alt => -10; # Initialize the integrator state to avoid a very large initial transient. This pre-compensates for gravity
281278
] |> Dict
282279
prob = ODEProblem(ssys, op, (0, 20))
@@ -294,43 +291,40 @@ nothing # hide
294291
The observant reader may have noticed that we linearized the quadrotor without the cable-suspended load applied, but we simulated the closed-loop system with the load. Thankfully, the LQR controller is robust enough to stabilize the system despite this large model error. Before being satisfied with the controller, we should perform robustness analysis. Below, we compute sensitivity functions at the plant output and input and plot their sigma plots, as well as simultaneous diskmargins at the plant output and input.
295292

296293
```@example QUAD
297-
# NOTE: this section is temporarily disabled waiting for improved performance in linearization
298-
299-
# linop = merge(op, Dict([
300-
# collect(model.system_outputs.u) .=> 0
301-
# collect(model.body.r_0) .=> 1e-32
302-
# collect(model.load.v_0) .=> 1e-32 # To avoid singularity in linearization
303-
# collect(model.system_outputs.u) .=> 1e-32
304-
# collect(model.feedback_gain.input.u) .=> 1e-32
305-
# ]))
306-
# @time "Sensitivity function" S = get_named_sensitivity(model, :y; system_modifier=IRSystem, op=linop)
307-
# S = minreal(S, 1e-6)
308-
# isstable(S) || @error "Sensitivity function S is not stable"
309-
# T = I(S.ny) - S
310-
# T = minreal(T, 1e-6)
311-
# isstable(T) || @error "Sensitivity function T is not stable"
312-
# LT = feedback(T, -I(T.ny))#get_named_looptransfer(model, :y; system_modifier=IRSystem, op)
313-
314-
# @time "Comp Sensitivity function" Ti = get_named_comp_sensitivity(model, :u; system_modifier=IRSystem, op=linop)
315-
# Ti = minreal(Ti, 1e-6)
316-
# isstable(Ti) || @error "Sensitivity function Ti is not stable"
317-
# LTi = feedback(Ti, -I(Ti.ny)) # Input loop-transfer function
318-
319-
# CS = named_ss(model, :y, :u; op=linop, system_modifier=IRSystem) # Closed-loop system from measurement noise to control signal
320-
321-
# w = 2pi.*exp10.(LinRange(-2, 2, 200))
322-
# fig_dm = plot(diskmargin(LT, 0.5), label="Plant output") # Compute diskmargin with a positive skew of 0.5 to account for a likely gain increase when the load is dropped
323-
# plot!(diskmargin(LTi, 0.5), label="Plant input", titlefontsize=8) # Note, simultaneous diskmargins are somewhat conservative
324-
325-
# plot(
326-
# sigmaplot(S, w, hz=true, label="", title="S", legend=false),
327-
# sigmaplot(T, w, hz=true, label="", title="T", legend=false),
328-
# sigmaplot(LT, w, hz=true, label="", title="L", legend=false),
329-
# bodeplot(CS, w, hz=true, label="", title="CS", legend=false, plotphase=false, layout=1),
330-
# fig_dm,
331-
# layout=(2,3), size=(800,500), legend=:bottomright, ylims=(1e-4, Inf),
332-
# )
333-
nothing
294+
linop = merge(op, Dict([
295+
collect(model.cable.joint_2.phi) .=> 0
296+
collect(model.body.r_0) .=> 1e-32
297+
collect(model.body.v_0) .=> 1e-32 # To avoid singularity in linearization
298+
collect(model.system_outputs.u) .=> 1e-32
299+
collect(model.feedback_gain.input.u) .=> 1e-32
300+
]))
301+
@time "Sensitivity function" S = get_named_sensitivity(model, :y; system_modifier=IRSystem, op=linop)
302+
S = minreal(S, 1e-6)
303+
isstable(S) || @error "Sensitivity function S is not stable"
304+
T = I(S.ny) - S
305+
T = minreal(T, 1e-6)
306+
isstable(T) || @error "Sensitivity function T is not stable"
307+
LT = feedback(T, -I(T.ny))#get_named_looptransfer(model, :y; system_modifier=IRSystem, op)
308+
309+
@time "Comp Sensitivity function" Ti = get_named_comp_sensitivity(model, :u; system_modifier=IRSystem, op=linop)
310+
Ti = minreal(Ti, 1e-6)
311+
isstable(Ti) || @error "Sensitivity function Ti is not stable"
312+
LTi = feedback(Ti, -I(Ti.ny)) # Input loop-transfer function
313+
314+
CS = named_ss(model, :y, :u; op=linop, system_modifier=IRSystem) # Closed-loop system from measurement noise to control signal
315+
316+
w = 2pi.*exp10.(LinRange(-2, 2, 200))
317+
fig_dm = plot(diskmargin(LT, 0.5), label="Plant output") # Compute diskmargin with a positive skew of 0.5 to account for a likely gain increase when the load is dropped
318+
plot!(diskmargin(LTi, 0.5), label="Plant input", titlefontsize=8) # Note, simultaneous diskmargins are somewhat conservative
319+
320+
plot(
321+
sigmaplot(S, w, hz=true, label="", title="S", legend=false),
322+
sigmaplot(T, w, hz=true, label="", title="T", legend=false),
323+
sigmaplot(LT, w, hz=true, label="", title="L", legend=false),
324+
bodeplot(CS, w, hz=true, label="", title="CS", legend=false, plotphase=false, layout=1),
325+
fig_dm,
326+
layout=(2,3), size=(800,500), legend=:bottomright, ylims=(1e-4, Inf),
327+
)
334328
```
335329

336330
While gain and phase margins appear to be reasonable, we have a large high-frequency gain in the transfer functions from measurement noise to control signal, ``C(s)S(s)``. For a rotor craft where the control signal manipulates the current through motor windings, this may lead to excessive heat generation in the motors if the sensor measurements are noisy.

0 commit comments

Comments
 (0)