Skip to content

Commit db29b24

Browse files
committed
add feedforward
1 parent 2f0ed3a commit db29b24

2 files changed

Lines changed: 125 additions & 24 deletions

File tree

src/pid_control_multibody_planar.jl

Lines changed: 117 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ The loop closed around the angle only has a zero in the origin corresponding to
4141

4242
systems = @named begin
4343
plant = PlanarMultibodybot()
44-
controller = Blocks.LimPID(k=22.3659, Ti=false, Td=0.0632787, Nd=54.6269)#, u_max=15)
44+
controller = Blocks.LimPID(k=0.487401, Ti=0.0587352, Td=0.0420526, Nd=119.368)#, u_max=15)
4545
ref = Blocks.Constant(k=theta_ref)
4646
end
4747

@@ -62,7 +62,7 @@ x0 = [
6262
ssys.plant.body.w => 0.0 # Adjust for offset
6363
]
6464

65-
prob = ODEProblem(ssys, x0, (0.0, 10.0))
65+
prob = ODEProblem(ssys, x0, (0.0, 1.0))
6666
sol = solve(prob, Rodas5P())
6767
plot(sol, idxs=[ssys.plant.theta_output.u, ssys.plant.x_output.u, ssys.plant.tau]); hline!([0], l=(:dash, :black), primary=false)
6868

@@ -111,17 +111,17 @@ spec = JSC.PIDAutotuningAnalysisSpec(;
111111
step_input = "u",
112112
step_output = "y",
113113
Ts = 0.01, # Sample time
114-
duration = 2.0, # Simulation duration
114+
duration = 3.0, # Simulation duration
115115
Ms = 1.6, # Sensitivity peak constraint
116116
Mt = 1.6, # Complementary sensitivity peak constraint
117-
Mks = 100.0, # Control sensitivity constraint
117+
Mks = 2000.0, # Control sensitivity constraint
118118
wl = 1e-2, # Lower frequency bound
119119
wu = 1e3, # Upper frequency bound
120-
ki_ub = 0.0, # Tune PD controller
120+
# ki_ub = 0.0, # Tune PD controller
121121
num_frequencies = 200,
122122
soft = true,
123-
# homotopy = true,
124-
# auto_resolve = true,
123+
homotopy = true,
124+
# auto_resolve = false,
125125
)
126126

127127
# Run the autotuning analysis
@@ -150,15 +150,15 @@ display(optimized_params)
150150
systems = @named begin
151151
plant = PlanarMultibodybot()
152152
# Inner loop: angle controller
153-
inner_controller = Blocks.LimPID(k=22.3659, Ti=false, Td=0.0632787, Nd=54.6269)
153+
inner_controller = Blocks.LimPID(k=0.487401, Ti=0.0587352, Td=0.0420526, Nd=119.368)
154154
# Outer loop: velocity controller
155-
outer_controller = Blocks.LimPID(k=0.0463128, Ti=3.34679, Td=2.23125, Nd=2.8527, wd=1, wp=1, u_max=deg2rad(25.0))
155+
outer_controller = Blocks.LimPID(k=0.0666576, Ti=5.25024, Td=4.81393, Nd=4.76616, wd=1, wp=1, u_max=deg2rad(25.0))
156156
neg_gain = Blocks.Gain(k=1)
157157
ref = Blocks.Step(height=x_ref, start_time=10)
158158
# Add pi offset to inner loop reference
159159
pi_offset = Blocks.Constant(k=0)
160160
add_pi = Blocks.Add(k1=1, k2=1)
161-
ref_filter = Blocks.FirstOrder(T=2)
161+
ref_filter = Blocks.FirstOrder(T=1)
162162
end
163163

164164
eqs = [
@@ -188,16 +188,16 @@ x0 = [
188188
cascade_ssys.plant.body.phi => deg2rad(5)
189189
# cascade_ssys.plant.wheelJoint.color => [0,0,0,0.5]
190190
cascade_ssys.plant.wheelJoint.frame_a.render => true
191-
cascade_ssys.plant.wheelJoint.frame_a.length => 0.12
191+
cascade_ssys.plant.wheelJoint.frame_a.length => 0.05
192192
cascade_ssys.plant.wheelJoint.frame_a.radius => 0.004
193193
]
194194

195195
cascade_prob = ODEProblem(cascade_ssys, x0, (0.0, 20.0))
196-
cascade_sol = solve(cascade_prob, Rodas5P())
196+
cascade_sol = solve(cascade_prob, Rodas5P(), dt=0.005, adaptive=false)
197197
plot(cascade_sol, idxs=[cascade_ssys.plant.theta_output.u, cascade_ssys.plant.x_output.u, cascade_ssys.outer_controller.ctr_output.u]); hline!([0 0.15], l=(:dash, :black), primary=false, ylims=(-1, 3.5), size=(800,1600))
198198

199-
Multibody.render(cascade_model, cascade_sol, 0.0, lookat=[0,0.4,0], x=0, y=0.4, z=-1.0)[1]
200-
# Multibody.render(cascade_model, cascade_sol, lookat=[0,0.4,0], x=0, y=0.4, z=-1.0, timescale=0.5)
199+
Multibody.render(cascade_model, cascade_sol, 0.0, lookat=[0,0.2,0], x=0, y=0.2, z=-0.5)[1]
200+
# Multibody.render(cascade_model, cascade_sol, lookat=[0,0.2,0], x=0, y=0.2, z=-0.5, timescale=1)
201201
# plot(cascade_sol, idxs=[cascade_ssys.pi_offset.output.u])
202202

203203
##
@@ -260,7 +260,7 @@ bodeplot(L2)
260260

261261
## Swms = freqresp(S2, ws2)
262262

263-
Si2 = get_named_sensitivity(cascade_tuning_model, [cascade_tuning_model.u])
263+
Si2 = get_named_sensitivity(cascade_tuning_model, [cascade_tuning_model.u]; Multibody.linsys...)
264264
w = exp10.(LinRange(-1, 3, 1000))
265265
Msi2, ws2 = hinfnorm2(Si2)
266266
bodeplot(Si2, w, plotphase=false); hline!([Msi2], l=(:dash, :black), label="\$M_S = \$$(round(Msi2, digits=2))")
@@ -271,10 +271,111 @@ nyquistplot(Li2)
271271
marginplot(Li2, w, adjust_phase_start=true)
272272

273273

274-
Li22 = get_named_looptransfer(cascade_tuning_model, [cascade_tuning_model.u]) |> minreal
274+
Li22 = get_named_looptransfer(cascade_tuning_model, [cascade_tuning_model.u]; Multibody.linsys...) |> minreal
275275

276276
dmi2 = diskmargin(Li2)
277277
plot(dmi2)
278278

279279
dmi22 = diskmargin(Li22)
280280
plot(dmi22)
281+
282+
283+
284+
285+
# ==============================================================================
286+
## Feedforward generation
287+
# ==============================================================================
288+
289+
T0 = ss(tf([1], [0.1, 1]))
290+
Tr = T0^4
291+
292+
Ryur = DyadControlSystems.feedforward_generator(cascade_tuning_model; # requires master branch
293+
Tr,
294+
measurement = [cascade_tuning_model.y, cascade_tuning_model.y2],
295+
controlled_output = [cascade_tuning_model.y2],
296+
control_input = [cascade_tuning_model.u],
297+
Multibody.linsys...
298+
)
299+
300+
Ryur = balance_statespace(Ryur)[1]
301+
302+
@component function FilteredCascadeControlledFlatDyadBot(; name)
303+
pars = @parameters begin
304+
x_ref = 0.15 # Reference velocity
305+
end
306+
307+
systems = @named begin
308+
plant = PlanarMultibodybot()
309+
# Inner loop: angle controller
310+
inner_controller = Blocks.LimPID(k=22.3659, Ti=false, Td=0.0632787, Nd=54.6269)
311+
# Outer loop: velocity controller
312+
outer_controller = Blocks.LimPID(k=0.0666576, Ti=5.25024, Td=4.81393, Nd=4.76616, wd=1, wp=1, u_max=deg2rad(25.0))
313+
neg_gain = Blocks.Gain(k=1)
314+
ref = Blocks.Step(height=x_ref, start_time=5)
315+
# Add pi offset to inner loop reference
316+
pi_offset = Blocks.Constant(k=0)
317+
add_pi = Blocks.Add3(k1=1, k2=1)
318+
319+
torque_input = Blocks.Add()
320+
R = Blocks.StateSpace(ssdata(Ryur)...)
321+
end
322+
323+
eqs = [
324+
325+
326+
connect(ref.output, :r2, R.input)
327+
R.output.u[1] ~ add_pi.input3.u
328+
R.output.u[2] ~ outer_controller.reference.u
329+
R.output.u[3] ~ torque_input.input2.u
330+
331+
332+
# Outer loop: velocity reference -> angle reference
333+
connect(plant.x_output, neg_gain.input)
334+
connect(neg_gain.output, :y2, outer_controller.measurement)
335+
336+
# Add pi to outer controller output for inner loop reference
337+
connect(outer_controller.ctr_output, :u2, add_pi.input1)
338+
connect(pi_offset.output, add_pi.input2)
339+
340+
# Inner loop: angle reference -> torque
341+
connect(add_pi.output, inner_controller.reference)
342+
connect(plant.theta_output, :y, inner_controller.measurement)
343+
connect(inner_controller.ctr_output, :u, torque_input.input1)
344+
connect(torque_input.output, plant.control_input)
345+
]
346+
347+
System(eqs, t, [], pars; systems, name)
348+
end
349+
350+
@named filtered_model = FilteredCascadeControlledFlatDyadBot()
351+
filtered_ssys = multibody(filtered_model)
352+
353+
Pcl_angle = named_ss(filtered_model, [filtered_model.r2], [filtered_model.y]; allow_input_derivatives=true, Multibody.linsys...)
354+
Pcl_pos = named_ss(filtered_model, [filtered_model.r2], [filtered_model.y2]; allow_input_derivatives=true, Multibody.linsys...)
355+
356+
Pcl_angle = minreal(Pcl_angle)
357+
Pcl_pos = minreal(Pcl_pos)
358+
359+
w = exp10.(LinRange(-2.5, 1.5, 2000))
360+
bodeplot([Pcl_angle, Pcl_pos, Tr], w, legend=:bottom, plotphase=false, legendfontsize=8, hz=true, label=["Gar" "Gpr" "Tr"])
361+
362+
363+
x0 = [
364+
filtered_ssys.inner_controller.k => optimized_params[1, :Kp_standard];
365+
filtered_ssys.inner_controller.Ti => false;#optimized_params[1, :Ti_standard];
366+
filtered_ssys.inner_controller.Td => optimized_params[1, :Td_standard];
367+
filtered_ssys.inner_controller.Nd => optimized_params[1, :Nd];
368+
filtered_ssys.outer_controller.k => cascade_optimized_params[1, :Kp_standard];
369+
filtered_ssys.outer_controller.Ti => cascade_optimized_params[1, :Ti_standard];
370+
371+
filtered_ssys.plant.body.phi => deg2rad(-10)
372+
]
373+
374+
375+
filtered_prob = ODEProblem(filtered_ssys, x0, (0.0, 10.0))
376+
filtered_sol = solve(filtered_prob, Rodas5P(), dt=0.01, dtmin=0.01, adaptive=false)
377+
plot(filtered_sol, idxs=[filtered_ssys.plant.theta_output.u, filtered_ssys.plant.x_output.u, filtered_ssys.outer_controller.ctr_output.u, filtered_ssys.plant.tau]); hline!([0 0.15], l=(:dash, :black), primary=false, ylims=(-1, 3.5), size=(800,1600), legend=:right)
378+
379+
plot(filtered_sol, idxs=[filtered_ssys.R.input.u; filtered_ssys.R.output.u; ])
380+
381+
# Multibody.render(filtered_model, filtered_sol, lookat=[0,0.2,0], x=0, y=0.2, z=-0.5, timescale=1)

src/planar_multibody.jl

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,18 @@ import ModelingToolkitStandardLibrary.Blocks
77
using Random
88
@component function PlanarMultibodybot(; name, g=9.81)
99
pars = @parameters begin
10-
M = 1.0, [description="Body mass"]
11-
m = 0.1, [description="Wheel mass"]
12-
R = 0.1, [description="Wheel radius"]
13-
L = 0.5, [description="Distance from wheel axis to body center of mass"]
14-
Ic = 0.1, [description="Body moment of inertia"]
15-
Iw = 0.01, [description="Wheel moment of inertia"]
10+
M = 0.1, [description="Body mass"]
11+
m = 0.05, [description="Wheel mass"]
12+
R = 0.04, [description="Wheel radius"]
13+
L = 0.08, [description="Distance from wheel axis to body center of mass"]
14+
Ib = 2.5e-4, [description="Body moment of inertia"]
15+
Iw = 1e-4, [description="Wheel + motor moment of inertia"]
1616
g = g, [description="Gravity"]
17-
b_rot = 0.01, [description="Rotational damping coefficient"]
17+
b_rot = 0.0, [description="Rotational damping coefficient"]
1818
end
1919

2020
systems = @named begin
21-
body = Pl.Body(m = M, I = Ic, phi=0, w=0.0, radius=0.05, gy=-g)
21+
body = Pl.Body(m = M, I = Ib, phi=0, w=0.0, radius=0.05, gy=-g)
2222
wheel_body = Pl.Body(m = m, I = Iw, radius=0.01, gy=-g, phi=0, w=0.00)
2323
translation_cm = Pl.FixedTranslation(r = [0, L], radius=0.01)
2424
wheel_rotation = Pl.Revolute(axisflange=true, render=false)

0 commit comments

Comments
 (0)