Skip to content

Commit 15ce572

Browse files
committed
add text to quad tutorial
1 parent d2ca9b9 commit 15ce572

File tree

2 files changed

+39
-36
lines changed

2 files changed

+39
-36
lines changed

docs/src/examples/quad.md

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Quadrotor with cable-suspended load
22

3+
![quadrotor animation](quadrotor.gif)
4+
5+
This example builds a simple model of a quadrotor that carries a load suspended by a cable. The quadrotor has four arms, each with a thruster at the end. The quadrotor is controlled by three PID controllers: one for altitude, one for roll, and one for pitch (for simplicity, no position controller is included here).
6+
7+
The main body of the aircraft is modeled using a [`Body`](@ref), and the arms are modeled using [`BodyCylinder`](@ref) components. The total inertia of the body and arms are automatically computed using the geometry and density properties of the bodies involved. The thrusters are modeled using a custom component called `Thruster` that applies a force in the y-direction. The thruster model is kinematical only, and does not model the rotation dynamics of the motors or any aerodynamics. The quadrotor is connected to the world frame using a [`FreeMotion`](@ref) joint, which allows the quadrotor to move freely in space.
38

49
```@example QUAD
510
using Multibody
@@ -27,6 +32,8 @@ cable_length = 1 # Length of the cable.
2732
cable_mass = 0.1 # Mass of the cable.
2833
cable_diameter = 0.01 # Diameter of the cable.
2934
number_of_links = 5 # Number of links in the cable.
35+
36+
# Controller parameters
3037
kalt = 1
3138
Tialt = 3
3239
Tdalt = 3
@@ -42,7 +49,7 @@ Tdpitch = 1
4249
@mtkmodel Thruster begin
4350
@components begin
4451
frame_b = Frame()
45-
thrust3d = WorldForce(resolve_frame = :frame_b, scale=0.1, radius=0.02)
52+
thrust3d = WorldForce(resolve_frame = :frame_b, scale=0.1, radius=0.02) # The thrust force is resolved in the local frame of the thruster.
4653
thrust = RealInput()
4754
end
4855
@variables begin
@@ -57,12 +64,10 @@ Tdpitch = 1
5764
end
5865
end
5966
60-
rou(x) = round(x, digits=3)
61-
62-
function RotorCraft(; cl = true, addload=true)
67+
function RotorCraft(; closed_loop = true, addload=true)
6368
arms = [
6469
BodyCylinder(
65-
r = rou.([arm_length*cos(angle_between_arms*(i-1)), 0, arm_length*sin(angle_between_arms*(i-1))]),
70+
r = [arm_length*cos(angle_between_arms*(i-1)), 0, arm_length*sin(angle_between_arms*(i-1))],
6671
diameter = arm_outer_diameter,
6772
inner_diameter = arm_inner_diameter,
6873
density = arm_density,
@@ -71,32 +76,8 @@ function RotorCraft(; cl = true, addload=true)
7176
]
7277
7378
thrusters = [Thruster(name = Symbol("thruster$i")) for i = 1:num_arms]
74-
75-
@parameters Galt[1:4] = ones(4)
76-
@parameters Groll[1:4] = [1,0,-1,0]
77-
@parameters Gpitch[1:4] = [0,1,0,-1]
78-
79-
@named Calt = PID(; k=kalt, Ti=Tialt, Td=Tdalt)
80-
@named Croll = PID(; k=kroll, Ti=Tiroll, Td=Tdroll)
81-
@named Cpitch = PID(; k=kpitch, Ti=Tipitch, Td=Tdpitch)
82-
83-
8479
@named body = Body(m = body_mass, state_priority = 0, I_11=0.01, I_22=0.01, I_33=0.01, air_resistance=1)
85-
@named load = Body(m = load_mass, air_resistance=1)
86-
@named freemotion = FreeMotion(state=true, isroot=true, quat=false, state_priority=1000, neg_w=false)
87-
88-
@named cable = Rope(
89-
l = cable_length,
90-
m = cable_mass,
91-
n = number_of_links,
92-
c = 0,
93-
d = 0,
94-
air_resistance = 0.5,
95-
d_joint = 0.1,
96-
radius = cable_diameter/2,
97-
color = [0.5, 0.4, 0.4, 1],
98-
dir = [0.0, -1, 0]
99-
)
80+
@named freemotion = FreeMotion(state=true, isroot=true, quat=false) # We use Euler angles to describe the orientation of the rotorcraft.
10081
10182
connections = [
10283
connect(world.frame_b, freemotion.frame_a)
@@ -106,21 +87,43 @@ function RotorCraft(; cl = true, addload=true)
10687
]
10788
systems = [world; arms; body; thrusters; freemotion]
10889
if addload
90+
@named load = Body(m = load_mass, air_resistance=0.1)
91+
@named cable = Rope(
92+
l = cable_length,
93+
m = cable_mass,
94+
n = number_of_links,
95+
c = 0,
96+
d = 0,
97+
air_resistance = 0.1,
98+
d_joint = 0.1,
99+
radius = cable_diameter/2,
100+
color = [0.5, 0.4, 0.4, 1],
101+
dir = [0.0, -1, 0]
102+
)
109103
push!(systems, load)
110104
push!(systems, cable)
111105
112106
push!(connections, connect(body.frame_a, cable.frame_a))
113107
push!(connections, connect(cable.frame_b, load.frame_a))
114108
end
115-
if cl
109+
if closed_loop # add controllers
110+
111+
# Mixing matrices for the control signals
112+
@parameters Galt[1:4] = ones(4) # The altitude controller affects all thrusters equally
113+
@parameters Groll[1:4] = [-1,0,1,0]
114+
@parameters Gpitch[1:4] = [0,1,0,-1]
115+
116+
@named Calt = PID(; k=kalt, Ti=Tialt, Td=Tdalt)
117+
@named Croll = PID(; k=kroll, Ti=Tiroll, Td=Tdroll)
118+
@named Cpitch = PID(; k=kpitch, Ti=Tipitch, Td=Tdpitch)
116119
117120
uc = Galt*Calt.ctr_output.u + Groll*Croll.ctr_output.u + Gpitch*Cpitch.ctr_output.u
118121
uc = collect(uc)
119122
append!(connections, [thrusters[i].u ~ uc[i] for i = 1:num_arms])
120123
121124
append!(connections, [
122125
Calt.err_input.u ~ -body.r_0[2]
123-
Croll.err_input.u ~ freemotion.phi[3]
126+
Croll.err_input.u ~ -freemotion.phi[3]
124127
Cpitch.err_input.u ~ -freemotion.phi[1]
125128
])
126129
append!(systems, [Calt; Croll; Cpitch])
@@ -138,12 +141,11 @@ function RotorCraft(; cl = true, addload=true)
138141
@named model = ODESystem(connections, t; systems)
139142
complete(model)
140143
end
141-
model = RotorCraft(cl=true, addload=true)
144+
model = RotorCraft(closed_loop=true, addload=true)
142145
ssys = structural_simplify(IRSystem(model))
143146
144147
op = [
145148
model.body.v_0[1] => 0;
146-
# collect(model.freemotion.phi) .=> 0.1;
147149
collect(model.cable.joint_2.phi) .=> 0.03;
148150
model.world.g => 2;
149151
# model.body.frame_a.render => true
@@ -167,3 +169,4 @@ nothing # hide
167169

168170
![quadrotor animation](quadrotor.gif)
169171

172+
The green arrows in the animation indicate the force applied by the thrusters.

ext/Render.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ function render!(scene, ::typeof(Body), sys, sol, t)
253253
end
254254
mesh!(scene, thing; color, specular = Vec3f(1.5), shininess=20f0, diffuse=Vec3f(1))
255255

256-
iszero(r_cm(sol.t[1])) && (return true)
256+
iszero(r_cm(sol.t[1])) && (return false)
257257

258258
thing2 = @lift begin # Cylinder
259259
Ta = framefun($t)
@@ -274,7 +274,7 @@ function render!(scene, ::typeof(Body), sys, sol, t)
274274
Makie.GeometryBasics.Cylinder(origin, extremity, cylinder_radius)
275275
end
276276
mesh!(scene, thing2; color, specular = Vec3f(1.5), shininess=20f0, diffuse=Vec3f(1))
277-
true
277+
false
278278
end
279279

280280
function render!(scene, ::typeof(World), sys, sol, t)

0 commit comments

Comments
 (0)