├── .gitattributes ├── .gitignore ├── LICENSE ├── PyDy Scripts ├── 00 - Basic │ ├── Quad_3D_flu_ENU_Euler.py │ ├── Quad_3D_flu_ENU_Quat.py │ ├── Quad_3D_frd_NED_Euler.py │ ├── Quad_3D_frd_NED_Quat.py │ └── Quad_3D_uvw │ │ ├── Quad_3D_flu_ENU_Euler_uvw.py │ │ ├── Quad_3D_flu_ENU_Quat_uvw.py │ │ ├── Quad_3D_frd_NED_Euler_uvw.py │ │ └── Quad_3D_frd_NED_Quat_uvw.py ├── 01 - Added Gyroscopic Precession │ ├── Quad_3D_flu_ENU_Euler.py │ ├── Quad_3D_flu_ENU_Quat.py │ ├── Quad_3D_frd_NED_Euler.py │ ├── Quad_3D_frd_NED_Quat.py │ └── Quad_3D_frd_NED_Quat_RotorBodies.py └── 02 - Added Wind and Aero Drag │ ├── Quad_3D_flu_ENU_Quat.py │ └── Quad_3D_frd_NED_Quat.py ├── README.md └── Simulation ├── config.py ├── ctrl.py ├── quadFiles ├── __init__.py ├── initQuad.py └── quad.py ├── run_3D_simulation.py ├── trajectory.py ├── utils ├── __init__.py ├── animation.py ├── display.py ├── mixer.py ├── quaternionFunctions.py ├── rotationConversion.py ├── stateConversions.py └── windModel.py └── waypoints.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | # *.gif filter=lfs diff=lfs merge=lfs -text 4 | # *Gifs/ filter=lfs diff=lfs merge=lfs -text 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | 10 | # Packages # 11 | ############ 12 | # it's better to unpack these files and commit the raw source 13 | # git has its own built in compression methods 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.jar 19 | *.rar 20 | *.tar 21 | *.zip 22 | 23 | # Logs and databases # 24 | ###################### 25 | *.log 26 | *.sql 27 | *.sqlite 28 | 29 | # Applications # 30 | ################ 31 | .vscode/ 32 | .idea/ 33 | __pycache__/ 34 | Gifs/ 35 | Gifs/Raw/ 36 | 37 | # OS generated files # 38 | ###################### 39 | .DS_Store 40 | .DS_Store? 41 | ._* 42 | .Spotlight-V100 43 | .Trashes 44 | ehthumbs.db 45 | Thumbs.db -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 John Bass 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PyDy Scripts/00 - Basic/Quad_3D_flu_ENU_Euler.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | author: John Bass 4 | email: john.bobzwik@gmail.com 5 | license: MIT 6 | Please feel free to use and modify this, but keep the above information. Thanks! 7 | """ 8 | 9 | """ 10 | Using PyDy and Sympy, this script generates the equations for the state derivatives 11 | of a quadcopter in a 3-dimensional space. The states in this particular script are: 12 | 13 | x, y, z : Position of the drone's center of mass in the inertial frame, 14 | expressed in the inertial frame 15 | xdot, ydot, zdot : Velocity of the drone's center of mass in the inertial frame, 16 | expressed in the inertial frame 17 | phi, theta, psi : Orientation (roll, pitch, yaw angles) of the drone in the 18 | inertial frame, following the order ZYX (yaw, pitch, roll) 19 | p, q, r : Angular velocity of the drone in the inertial frame, 20 | expressed in the drone's frame 21 | 22 | Important note : This script uses a flu body orientation (front-left-up) and 23 | a ENU world orientation (East-North-Up). 24 | 25 | Other note : In the resulting state derivatives, there are still simplifications 26 | that can be made that SymPy cannot simplify (factoring). 27 | 28 | author: John Bass 29 | email: 30 | """ 31 | 32 | from sympy import symbols 33 | from sympy.physics.mechanics import * 34 | 35 | # Reference frames and Points 36 | # --------------------------- 37 | N = ReferenceFrame('N') # Inertial Frame 38 | B = ReferenceFrame('B') # Drone after X (roll) rotation (Final rotation) 39 | C = ReferenceFrame('C') # Drone after Y (pitch) rotation 40 | D = ReferenceFrame('D') # Drone after Z (yaw) rotation (First rotation) 41 | 42 | No = Point('No') 43 | Bcm = Point('Bcm') # Drone's center of mass 44 | M1 = Point('M1') # Motor 1 is front left, then the rest increments CW (Drone is in X configuration, not +) 45 | M2 = Point('M2') 46 | M3 = Point('M3') 47 | M4 = Point('M4') 48 | 49 | # Variables 50 | # --------------------------- 51 | # x, y and z are the drone's coordinates in the inertial frame, expressed with the inertial frame 52 | # xdot, ydot and zdot are the drone's velocities in the inertial frame, expressed with the inertial frame 53 | # phi, theta and psi represents the drone's orientation in the inertial frame, expressed with a ZYX Body rotation 54 | # p, q and r are the drone's angular velocities in the inertial frame, expressed with the drone's frame 55 | 56 | x, y, z, xdot, ydot, zdot = dynamicsymbols('x y z xdot ydot zdot') 57 | phi, theta, psi, p, q, r = dynamicsymbols('phi theta psi p q r') 58 | 59 | # First derivatives of the variables 60 | xd, yd, zd, xdotd, ydotd, zdotd = dynamicsymbols('x y z xdot ydot zdot', 1) 61 | phid, thetad, psid, pd, qd, rd = dynamicsymbols('phi theta psi p q r', 1) 62 | 63 | # Constants 64 | # --------------------------- 65 | mB, g, dxm, dym, dzm, IBxx, IByy, IBzz = symbols('mB g dxm dym dzm IBxx IByy IBzz') 66 | ThrM1, ThrM2, ThrM3, ThrM4, TorM1, TorM2, TorM3, TorM4 = symbols('ThrM1 ThrM2 ThrM3 ThrM4 TorM1 TorM2 TorM3 TorM4') 67 | 68 | # Rotation ZYX Body 69 | # --------------------------- 70 | D.orient(N, 'Axis', [psi, N.z]) 71 | C.orient(D, 'Axis', [theta, D.y]) 72 | B.orient(C, 'Axis', [phi, C.x]) 73 | 74 | # Origin 75 | # --------------------------- 76 | No.set_vel(N, 0) 77 | 78 | # Translation 79 | # --------------------------- 80 | Bcm.set_pos(No, x*N.x + y*N.y + z*N.z) 81 | Bcm.set_vel(N, Bcm.pos_from(No).dt(N)) 82 | 83 | # Motor placement 84 | # M1 is front left, then clockwise numbering 85 | # dzm is positive for motors above center of mass 86 | # --------------------------- 87 | M1.set_pos(Bcm, dxm*B.x + dym*B.y + dzm*B.z) 88 | M2.set_pos(Bcm, dxm*B.x - dym*B.y + dzm*B.z) 89 | M3.set_pos(Bcm, -dxm*B.x - dym*B.y + dzm*B.z) 90 | M4.set_pos(Bcm, -dxm*B.x + dym*B.y + dzm*B.z) 91 | M1.v2pt_theory(Bcm, N, B) 92 | M2.v2pt_theory(Bcm, N, B) 93 | M3.v2pt_theory(Bcm, N, B) 94 | M4.v2pt_theory(Bcm, N, B) 95 | 96 | # Inertia Dyadic 97 | # --------------------------- 98 | IB = inertia(B, IBxx, IByy, IBzz) 99 | 100 | # Create Bodies 101 | # --------------------------- 102 | BodyB = RigidBody('BodyB', Bcm, B, mB, (IB, Bcm)) 103 | BodyList = [BodyB] 104 | 105 | # Forces and Torques 106 | # --------------------------- 107 | Grav_Force = (Bcm, -mB*g*N.z) 108 | FM1 = (M1, ThrM1*B.z) 109 | FM2 = (M2, ThrM2*B.z) 110 | FM3 = (M3, ThrM3*B.z) 111 | FM4 = (M4, ThrM4*B.z) 112 | 113 | TM1 = (B, TorM1*B.z) 114 | TM2 = (B, -TorM2*B.z) 115 | TM3 = (B, TorM3*B.z) 116 | TM4 = (B, -TorM4*B.z) 117 | ForceList = [Grav_Force, FM1, FM2, FM3, FM4, TM1, TM2, TM3, TM4] 118 | 119 | # Kinematic Differential Equations 120 | # --------------------------- 121 | kd = [xdot - xd, ydot - yd, zdot - zd, p - dot(B.ang_vel_in(N), B.x), q - dot(B.ang_vel_in(N), B.y), r - dot(B.ang_vel_in(N), B.z)] 122 | 123 | # Kane's Method 124 | # --------------------------- 125 | KM = KanesMethod(N, q_ind=[x, y, z, phi, theta, psi], u_ind=[xdot, ydot, zdot, p, q, r], kd_eqs=kd) 126 | (fr, frstar) = KM.kanes_equations(BodyList, ForceList) 127 | 128 | # Equations of Motion 129 | # --------------------------- 130 | MM = KM.mass_matrix_full 131 | kdd = KM.kindiffdict() 132 | rhs = KM.forcing_full 133 | rhs = rhs.subs(kdd) 134 | 135 | MM.simplify() 136 | print('Mass Matrix') 137 | print('-----------') 138 | mprint(MM) 139 | print() 140 | 141 | rhs.simplify() 142 | print('Right Hand Side') 143 | print('---------------') 144 | mprint(rhs) 145 | print() 146 | 147 | # So, MM*x = rhs, where x is the State Derivatives 148 | # Solve for x 149 | stateDot = MM.inv()*rhs 150 | print('State Derivatives') 151 | print('-----------------------------------') 152 | mprint(stateDot) 153 | print() 154 | 155 | # POST-PROCESSING 156 | # --------------------------- 157 | 158 | # Useful Numbers 159 | # --------------------------- 160 | # p, q, r are the drone's angular velocities in the inertial frame, expressed in the drone's frame. 161 | # These calculations are not relevant to the ODE, but might be used for control. 162 | print('P, Q, R (Angular velocities in drone frame)') 163 | print('-------------------------------------------') 164 | mprint(dot(B.ang_vel_in(N), B.x)) 165 | print() 166 | mprint(dot(B.ang_vel_in(N), B.y)) 167 | print() 168 | mprint(dot(B.ang_vel_in(N), B.z)) 169 | print() 170 | 171 | # u, v, w are the drone's velocities in the inertial frame, expressed in the drone's frame. 172 | # These calculations are not relevant to the ODE, but might be used for control. 173 | u = dot(Bcm.vel(N).subs(kdd), B.x) 174 | v = dot(Bcm.vel(N).subs(kdd), B.y) 175 | w = dot(Bcm.vel(N).subs(kdd), B.z) 176 | print('u, v, w (Velocities in drone frame)') 177 | print('-----------------------------------') 178 | mprint(u) 179 | print() 180 | mprint(v) 181 | print() 182 | mprint(w) 183 | print() 184 | 185 | # uFlat, vFlat, wFlat are the drone's velocities in the inertial frame, expressed in a frame 186 | # parallel to the ground, but with the drone's heading (so only after the YAW rotation). 187 | # These calculations are not relevant to the ODE, but might be used for control. 188 | uFlat = dot(Bcm.vel(N).subs(kdd), D.x) 189 | vFlat = dot(Bcm.vel(N).subs(kdd), D.y) 190 | wFlat = dot(Bcm.vel(N).subs(kdd), D.z) 191 | print('uFlat, vFlat, wFlat (Velocities in drone frame (after Yaw))') 192 | print('-----------------------------------------------------------') 193 | mprint(uFlat) 194 | print() 195 | mprint(vFlat) 196 | print() 197 | mprint(wFlat) 198 | print() 199 | -------------------------------------------------------------------------------- /PyDy Scripts/00 - Basic/Quad_3D_flu_ENU_Quat.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | author: John Bass 4 | email: john.bobzwik@gmail.com 5 | license: MIT 6 | Please feel free to use and modify this, but keep the above information. Thanks! 7 | """ 8 | 9 | """ 10 | Using PyDy and Sympy, this script generates the equations for the state derivatives 11 | of a quadcopter in a 3-dimensional space. The states in this particular script are: 12 | 13 | x, y, z : Position of the drone's center of mass in the inertial frame, 14 | expressed in the inertial frame 15 | xdot, ydot, zdot : Velocity of the drone's center of mass in the inertial frame, 16 | expressed in the inertial frame 17 | q0, q1, q2, q3 : Orientation of the drone in the inertial frame using quaternions 18 | p, q, r : Angular velocity of the drone in the inertial frame, 19 | expressed in the drone's frame 20 | 21 | Important note : This script uses a flu body orientation (front-left-up) and 22 | a ENU world orientation (East-North-Up) 23 | 24 | Other note : In the resulting state derivatives, there are still simplifications 25 | that can be made that SymPy cannot simplify (factoring). 26 | 27 | author: John Bass 28 | email: 29 | """ 30 | 31 | from sympy import symbols, Matrix 32 | from sympy.physics.mechanics import * 33 | 34 | # Reference frames and Points 35 | # --------------------------- 36 | N = ReferenceFrame('N') # Inertial Frame 37 | B = ReferenceFrame('B') # Drone after X (roll) rotation (Final rotation) 38 | 39 | No = Point('No') 40 | Bcm = Point('Bcm') # Drone's center of mass 41 | M1 = Point('M1') # Motor 1 is front left, then the rest increments CW (Drone is in X configuration, not +) 42 | M2 = Point('M2') 43 | M3 = Point('M3') 44 | M4 = Point('M4') 45 | 46 | # Variables 47 | # --------------------------- 48 | # x, y and z are the drone's coordinates in the inertial frame, expressed with the inertial frame 49 | # xdot, ydot and zdot are the drone's speeds in the inertial frame, expressed with the inertial frame 50 | # q0, q1, q2, q3 represents the drone's orientation in the inertial frame, using quaternions 51 | # p, q and r are the drone's angular velocities in the inertial frame, expressed with the drone's frame 52 | 53 | x, y, z, xdot, ydot, zdot = dynamicsymbols('x y z xdot ydot zdot') 54 | q0, q1, q2, q3, p, q, r = dynamicsymbols('q0 q1 q2 q3 p q r') 55 | 56 | # First derivatives of the variables 57 | xd, yd, zd, xdotd, ydotd, zdotd = dynamicsymbols('x y z xdot ydot zdot', 1) 58 | q0d, q1d, q2d, q3d, pd, qd, rd = dynamicsymbols('q0 q1 q2 q3 p q r', 1) 59 | 60 | # Constants 61 | # --------------------------- 62 | mB, g, dxm, dym, dzm, IBxx, IByy, IBzz = symbols('mB g dxm dym dzm IBxx IByy IBzz') 63 | ThrM1, ThrM2, ThrM3, ThrM4, TorM1, TorM2, TorM3, TorM4 = symbols('ThrM1 ThrM2 ThrM3 ThrM4 TorM1 TorM2 TorM3 TorM4') 64 | 65 | # Rotation Quaternion 66 | # --------------------------- 67 | B.orient(N, 'Quaternion', [q0, q1, q2, q3]) 68 | B.set_ang_vel(N, p*B.x + q*B.y + r*B.z) 69 | 70 | # Origin 71 | # --------------------------- 72 | No.set_vel(N, 0) 73 | 74 | # Translation 75 | # --------------------------- 76 | Bcm.set_pos(No, x*N.x + y*N.y + z*N.z) 77 | Bcm.set_vel(N, Bcm.pos_from(No).dt(N)) 78 | 79 | # Motor placement 80 | # M1 is front left, then clockwise numbering 81 | # dzm is positive for motors above center of mass 82 | # --------------------------- 83 | M1.set_pos(Bcm, dxm*B.x + dym*B.y + dzm*B.z) 84 | M2.set_pos(Bcm, dxm*B.x - dym*B.y + dzm*B.z) 85 | M3.set_pos(Bcm, -dxm*B.x - dym*B.y + dzm*B.z) 86 | M4.set_pos(Bcm, -dxm*B.x + dym*B.y + dzm*B.z) 87 | M1.v2pt_theory(Bcm, N, B) 88 | M2.v2pt_theory(Bcm, N, B) 89 | M3.v2pt_theory(Bcm, N, B) 90 | M4.v2pt_theory(Bcm, N, B) 91 | 92 | # Inertia Dyadic 93 | # --------------------------- 94 | IB = inertia(B, IBxx, IByy, IBzz) 95 | 96 | # Create Bodies 97 | # --------------------------- 98 | BodyB = RigidBody('BodyB', Bcm, B, mB, (IB, Bcm)) 99 | BodyList = [BodyB] 100 | 101 | # Forces and Torques 102 | # --------------------------- 103 | Grav_Force = (Bcm, -mB*g*N.z) 104 | FM1 = (M1, ThrM1*B.z) 105 | FM2 = (M2, ThrM2*B.z) 106 | FM3 = (M3, ThrM3*B.z) 107 | FM4 = (M4, ThrM4*B.z) 108 | 109 | TM1 = (B, TorM1*B.z) 110 | TM2 = (B, -TorM2*B.z) 111 | TM3 = (B, TorM3*B.z) 112 | TM4 = (B, -TorM4*B.z) 113 | ForceList = [Grav_Force, FM1, FM2, FM3, FM4, TM1, TM2, TM3, TM4] 114 | 115 | # Calculate Quaternion Derivative 116 | # --------------------------- 117 | Gquat = Matrix([[-q1, q0, q3, -q2], 118 | [-q2, -q3, q0, q1], 119 | [-q3, q2, -q1, q0]]) 120 | 121 | angVel = Matrix([[dot(B.ang_vel_in(N), B.x)],[dot(B.ang_vel_in(N), B.y)],[dot(B.ang_vel_in(N), B.z)]]) # Basically the same as: angVel = Matrix([[p],[q],[r]]) 122 | quat_dot = 1.0/2*Gquat.T*angVel 123 | 124 | # Kinematic Differential Equations 125 | # --------------------------- 126 | kd = [xdot - xd, ydot - yd, zdot - zd, q0d - quat_dot[0], q1d - quat_dot[1], q2d - quat_dot[2], q3d - quat_dot[3]] 127 | 128 | # Kane's Method 129 | # --------------------------- 130 | KM = KanesMethod(N, q_ind=[x, y, z, q0, q1, q2, q3], u_ind=[xdot, ydot, zdot, p, q, r], kd_eqs=kd) 131 | (fr, frstar) = KM.kanes_equations(BodyList, ForceList) 132 | 133 | # Equations of Motion 134 | # --------------------------- 135 | MM = KM.mass_matrix_full 136 | kdd = KM.kindiffdict() 137 | rhs = KM.forcing_full 138 | MM = MM.subs(kdd) 139 | rhs = rhs.subs(kdd) 140 | 141 | MM.simplify() 142 | # MM = MM.subs(q0**2 + q1**2 + q2**2 + q3**2, 1) 143 | print() 144 | print('Mass Matrix') 145 | print('-----------') 146 | mprint(MM) 147 | 148 | rhs.simplify() 149 | # rhs = rhs.subs(q0**2 + q1**2 + q2**2 + q3**2, 1) 150 | print('Right Hand Side') 151 | print('---------------') 152 | mprint(rhs) 153 | print() 154 | 155 | # So, MM*x = rhs, where x is the State Derivatives 156 | # Solve for x 157 | stateDot = MM.inv()*rhs 158 | print('State Derivatives') 159 | print('-----------------------------------') 160 | mprint(stateDot) 161 | print() 162 | 163 | # POST-PROCESSING 164 | # --------------------------- 165 | 166 | # Useful Numbers 167 | # --------------------------- 168 | # p, q, r are the drone's angular velocities in the inertial frame, expressed in the drone's frame. 169 | # These calculations are not relevant to the ODE, but might be used for control. 170 | quat_dot_2 = Matrix([[q0d],[q1d],[q2d],[q3d]]) 171 | angVel_2 = 2.0*Gquat*quat_dot_2 172 | print('P, Q, R (Angular velocities in drone frame)') 173 | print('-------------------------------------------') 174 | mprint(angVel_2[0]) 175 | print() 176 | mprint(angVel_2[1]) 177 | print() 178 | mprint(angVel_2[2]) 179 | print() 180 | 181 | # u, v, w are the drone's velocities in the inertial frame, expressed in the drone's frame. 182 | # These calculations are not relevant to the ODE, but might be used for control. 183 | u = dot(Bcm.vel(N).subs(kdd), B.x).simplify() 184 | v = dot(Bcm.vel(N).subs(kdd), B.y).simplify() 185 | w = dot(Bcm.vel(N).subs(kdd), B.z).simplify() 186 | print('u, v, w (Velocities in drone frame)') 187 | print('-----------------------------------') 188 | mprint(u) 189 | print() 190 | mprint(v) 191 | print() 192 | mprint(w) 193 | print() 194 | 195 | # uFlat, vFlat, wFlat are the drone's velocities in the inertial frame, expressed in a frame 196 | # parallel to the ground, but with the drone's heading. 197 | # These calculations are not relevant to the ODE, but might be used for control. 198 | uFlat = dot(Bcm.vel(N).subs(kdd), cross(B.y, N.z)).simplify() 199 | vFlat = dot(Bcm.vel(N).subs(kdd), -cross(B.x, N.z)).simplify() 200 | wFlat = dot(Bcm.vel(N).subs(kdd), N.z).simplify() 201 | print('uFlat, vFlat, wFlat (Velocities in drone frame (after Yaw))') 202 | print('-----------------------------------------------------------') 203 | mprint(uFlat) 204 | print() 205 | mprint(vFlat) 206 | print() 207 | mprint(wFlat) 208 | print() 209 | -------------------------------------------------------------------------------- /PyDy Scripts/00 - Basic/Quad_3D_frd_NED_Euler.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | author: John Bass 4 | email: john.bobzwik@gmail.com 5 | license: MIT 6 | Please feel free to use and modify this, but keep the above information. Thanks! 7 | """ 8 | 9 | """ 10 | Using PyDy and Sympy, this script generates the equations for the state derivatives 11 | of a quadcopter in a 3-dimensional space. The states in this particular script are: 12 | 13 | x, y, z : Position of the drone's center of mass in the inertial frame, 14 | expressed in the inertial frame 15 | xdot, ydot, zdot : Velocity of the drone's center of mass in the inertial frame, 16 | expressed in the inertial frame 17 | phi, theta, psi : Orientation (roll, pitch, yaw angles) of the drone in the 18 | inertial frame, following the order ZYX (yaw, pitch, roll) 19 | p, q, r : Angular velocity of the drone in the inertial frame, 20 | expressed in the drone's frame 21 | 22 | Important note : This script uses a frd body orientation (front-right-down) and 23 | a NED world orientation (North-East-Down). The drone's altitude is -z. 24 | 25 | Other note : In the resulting state derivatives, there are still simplifications 26 | that can be made that SymPy cannot simplify (factoring). 27 | 28 | author: John Bass 29 | email: 30 | """ 31 | 32 | from sympy import symbols 33 | from sympy.physics.mechanics import * 34 | 35 | # Reference frames and Points 36 | # --------------------------- 37 | N = ReferenceFrame('N') # Inertial Frame 38 | B = ReferenceFrame('B') # Drone after X (roll) rotation (Final rotation) 39 | C = ReferenceFrame('C') # Drone after Y (pitch) rotation 40 | D = ReferenceFrame('D') # Drone after Z (yaw) rotation (First rotation) 41 | 42 | No = Point('No') 43 | Bcm = Point('Bcm') # Drone's center of mass 44 | M1 = Point('M1') # Motor 1 is front left, then the rest increments CW (Drone is in X configuration, not +) 45 | M2 = Point('M2') 46 | M3 = Point('M3') 47 | M4 = Point('M4') 48 | 49 | # Variables 50 | # --------------------------- 51 | # x, y and z are the drone's coordinates in the inertial frame, expressed with the inertial frame 52 | # xdot, ydot and zdot are the drone's velocities in the inertial frame, expressed with the inertial frame 53 | # phi, theta and psi represents the drone's orientation in the inertial frame, expressed with a ZYX Body rotation 54 | # p, q and r are the drone's angular velocities in the inertial frame, expressed with the drone's frame 55 | 56 | x, y, z, xdot, ydot, zdot = dynamicsymbols('x y z xdot ydot zdot') 57 | phi, theta, psi, p, q, r = dynamicsymbols('phi theta psi p q r') 58 | 59 | # First derivatives of the variables 60 | xd, yd, zd, xdotd, ydotd, zdotd = dynamicsymbols('x y z xdot ydot zdot', 1) 61 | phid, thetad, psid, pd, qd, rd = dynamicsymbols('phi theta psi p q r', 1) 62 | 63 | # Constants 64 | # --------------------------- 65 | mB, g, dxm, dym, dzm, IBxx, IByy, IBzz = symbols('mB g dxm dym dzm IBxx IByy IBzz') 66 | ThrM1, ThrM2, ThrM3, ThrM4, TorM1, TorM2, TorM3, TorM4 = symbols('ThrM1 ThrM2 ThrM3 ThrM4 TorM1 TorM2 TorM3 TorM4') 67 | 68 | # Rotation ZYX Body 69 | # --------------------------- 70 | D.orient(N, 'Axis', [psi, N.z]) 71 | C.orient(D, 'Axis', [theta, D.y]) 72 | B.orient(C, 'Axis', [phi, C.x]) 73 | 74 | # Origin 75 | # --------------------------- 76 | No.set_vel(N, 0) 77 | 78 | # Translation 79 | # --------------------------- 80 | Bcm.set_pos(No, x*N.x + y*N.y + z*N.z) 81 | Bcm.set_vel(N, Bcm.pos_from(No).dt(N)) 82 | 83 | # Motor placement 84 | # M1 is front left, then clockwise numbering 85 | # dzm is positive for motors above center of mass 86 | # --------------------------- 87 | M1.set_pos(Bcm, dxm*B.x - dym*B.y - dzm*B.z) 88 | M2.set_pos(Bcm, dxm*B.x + dym*B.y - dzm*B.z) 89 | M3.set_pos(Bcm, -dxm*B.x + dym*B.y - dzm*B.z) 90 | M4.set_pos(Bcm, -dxm*B.x - dym*B.y - dzm*B.z) 91 | M1.v2pt_theory(Bcm, N, B) 92 | M2.v2pt_theory(Bcm, N, B) 93 | M3.v2pt_theory(Bcm, N, B) 94 | M4.v2pt_theory(Bcm, N, B) 95 | 96 | # Inertia Dyadic 97 | # --------------------------- 98 | IB = inertia(B, IBxx, IByy, IBzz) 99 | 100 | # Create Bodies 101 | # --------------------------- 102 | BodyB = RigidBody('BodyB', Bcm, B, mB, (IB, Bcm)) 103 | BodyList = [BodyB] 104 | 105 | # Forces and Torques 106 | # --------------------------- 107 | Grav_Force = (Bcm, mB*g*N.z) 108 | FM1 = (M1, -ThrM1*B.z) 109 | FM2 = (M2, -ThrM2*B.z) 110 | FM3 = (M3, -ThrM3*B.z) 111 | FM4 = (M4, -ThrM4*B.z) 112 | 113 | TM1 = (B, -TorM1*B.z) 114 | TM2 = (B, TorM2*B.z) 115 | TM3 = (B, -TorM3*B.z) 116 | TM4 = (B, TorM4*B.z) 117 | ForceList = [Grav_Force, FM1, FM2, FM3, FM4, TM1, TM2, TM3, TM4] 118 | 119 | # Kinematic Differential Equations 120 | # --------------------------- 121 | kd = [xdot - xd, ydot - yd, zdot - zd, p - dot(B.ang_vel_in(N), B.x), q - dot(B.ang_vel_in(N), B.y), r - dot(B.ang_vel_in(N), B.z)] 122 | 123 | # Kane's Method 124 | # --------------------------- 125 | KM = KanesMethod(N, q_ind=[x, y, z, phi, theta, psi], u_ind=[xdot, ydot, zdot, p, q, r], kd_eqs=kd) 126 | (fr, frstar) = KM.kanes_equations(BodyList, ForceList) 127 | 128 | # Equations of Motion 129 | # --------------------------- 130 | MM = KM.mass_matrix_full 131 | kdd = KM.kindiffdict() 132 | rhs = KM.forcing_full 133 | rhs = rhs.subs(kdd) 134 | 135 | MM.simplify() 136 | print('Mass Matrix') 137 | print('-----------') 138 | mprint(MM) 139 | print() 140 | 141 | rhs.simplify() 142 | print('Right Hand Side') 143 | print('---------------') 144 | mprint(rhs) 145 | print() 146 | 147 | # So, MM*x = rhs, where x is the State Derivatives 148 | # Solve for x 149 | stateDot = MM.inv()*rhs 150 | print('State Derivatives') 151 | print('-----------------------------------') 152 | mprint(stateDot) 153 | print() 154 | 155 | # POST-PROCESSING 156 | # --------------------------- 157 | 158 | # Useful Numbers 159 | # --------------------------- 160 | # p, q, r are the drone's angular velocities in the inertial frame, expressed in the drone's frame. 161 | # These calculations are not relevant to the ODE, but might be used for control. 162 | print('P, Q, R (Angular velocities in drone frame)') 163 | print('-------------------------------------------') 164 | mprint(dot(B.ang_vel_in(N), B.x)) 165 | print() 166 | mprint(dot(B.ang_vel_in(N), B.y)) 167 | print() 168 | mprint(dot(B.ang_vel_in(N), B.z)) 169 | print() 170 | 171 | # u, v, w are the drone's velocities in the inertial frame, expressed in the drone's frame. 172 | # These calculations are not relevant to the ODE, but might be used for control. 173 | u = dot(Bcm.vel(N).subs(kdd), B.x) 174 | v = dot(Bcm.vel(N).subs(kdd), B.y) 175 | w = dot(Bcm.vel(N).subs(kdd), B.z) 176 | print('u, v, w (Velocities in drone frame)') 177 | print('-----------------------------------') 178 | mprint(u) 179 | print() 180 | mprint(v) 181 | print() 182 | mprint(w) 183 | print() 184 | 185 | # uFlat, vFlat, wFlat are the drone's velocities in the inertial frame, expressed in a frame 186 | # parallel to the ground, but with the drone's heading (so only after the YAW rotation). 187 | # These calculations are not relevant to the ODE, but might be used for control. 188 | uFlat = dot(Bcm.vel(N).subs(kdd), D.x) 189 | vFlat = dot(Bcm.vel(N).subs(kdd), D.y) 190 | wFlat = dot(Bcm.vel(N).subs(kdd), D.z) 191 | print('uFlat, vFlat, wFlat (Velocities in drone frame (after Yaw))') 192 | print('-----------------------------------------------------------') 193 | mprint(uFlat) 194 | print() 195 | mprint(vFlat) 196 | print() 197 | mprint(wFlat) 198 | print() 199 | -------------------------------------------------------------------------------- /PyDy Scripts/00 - Basic/Quad_3D_frd_NED_Quat.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | author: John Bass 4 | email: john.bobzwik@gmail.com 5 | license: MIT 6 | Please feel free to use and modify this, but keep the above information. Thanks! 7 | """ 8 | 9 | """ 10 | Using PyDy and Sympy, this script generates the equations for the state derivatives 11 | of a quadcopter in a 3-dimensional space. The states in this particular script are: 12 | 13 | x, y, z : Position of the drone's center of mass in the inertial frame, 14 | expressed in the inertial frame 15 | xdot, ydot, zdot : Velocity of the drone's center of mass in the inertial frame, 16 | expressed in the inertial frame 17 | q0, q1, q2, q3 : Orientation of the drone in the inertial frame using quaternions 18 | p, q, r : Angular velocity of the drone in the inertial frame, 19 | expressed in the drone's frame 20 | 21 | Important note : This script uses a frd body orientation (front-right-down) and 22 | a NED world orientation (North-East-Down). The drone's altitude is -z. 23 | 24 | Other note : In the resulting state derivatives, there are still simplifications 25 | that can be made that SymPy cannot simplify (factoring). 26 | 27 | author: John Bass 28 | email: 29 | """ 30 | 31 | from sympy import symbols, Matrix 32 | from sympy.physics.mechanics import * 33 | 34 | # Reference frames and Points 35 | # --------------------------- 36 | N = ReferenceFrame('N') # Inertial Frame 37 | B = ReferenceFrame('B') # Drone after X (roll) rotation (Final rotation) 38 | 39 | No = Point('No') 40 | Bcm = Point('Bcm') # Drone's center of mass 41 | M1 = Point('M1') # Motor 1 is front left, then the rest increments CW (Drone is in X configuration, not +) 42 | M2 = Point('M2') 43 | M3 = Point('M3') 44 | M4 = Point('M4') 45 | 46 | # Variables 47 | # --------------------------- 48 | # x, y and z are the drone's coordinates in the inertial frame, expressed with the inertial frame 49 | # xdot, ydot and zdot are the drone's speeds in the inertial frame, expressed with the inertial frame 50 | # q0, q1, q2, q3 represents the drone's orientation in the inertial frame, using quaternions 51 | # p, q and r are the drone's angular velocities in the inertial frame, expressed with the drone's frame 52 | 53 | x, y, z, xdot, ydot, zdot = dynamicsymbols('x y z xdot ydot zdot') 54 | q0, q1, q2, q3, p, q, r = dynamicsymbols('q0 q1 q2 q3 p q r') 55 | 56 | # First derivatives of the variables 57 | xd, yd, zd, xdotd, ydotd, zdotd = dynamicsymbols('x y z xdot ydot zdot', 1) 58 | q0d, q1d, q2d, q3d, pd, qd, rd = dynamicsymbols('q0 q1 q2 q3 p q r', 1) 59 | 60 | # Constants 61 | # --------------------------- 62 | mB, g, dxm, dym, dzm, IBxx, IByy, IBzz = symbols('mB g dxm dym dzm IBxx IByy IBzz') 63 | ThrM1, ThrM2, ThrM3, ThrM4, TorM1, TorM2, TorM3, TorM4 = symbols('ThrM1 ThrM2 ThrM3 ThrM4 TorM1 TorM2 TorM3 TorM4') 64 | 65 | # Rotation Quaternion 66 | # --------------------------- 67 | B.orient(N, 'Quaternion', [q0, q1, q2, q3]) 68 | B.set_ang_vel(N, p*B.x + q*B.y + r*B.z) 69 | 70 | # Origin 71 | # --------------------------- 72 | No.set_vel(N, 0) 73 | 74 | # Translation 75 | # --------------------------- 76 | Bcm.set_pos(No, x*N.x + y*N.y + z*N.z) 77 | Bcm.set_vel(N, Bcm.pos_from(No).dt(N)) 78 | 79 | # Motor placement 80 | # M1 is front left, then clockwise numbering 81 | # dzm is positive for motors above center of mass 82 | # --------------------------- 83 | M1.set_pos(Bcm, dxm*B.x - dym*B.y - dzm*B.z) 84 | M2.set_pos(Bcm, dxm*B.x + dym*B.y - dzm*B.z) 85 | M3.set_pos(Bcm, -dxm*B.x + dym*B.y - dzm*B.z) 86 | M4.set_pos(Bcm, -dxm*B.x - dym*B.y - dzm*B.z) 87 | M1.v2pt_theory(Bcm, N, B) 88 | M2.v2pt_theory(Bcm, N, B) 89 | M3.v2pt_theory(Bcm, N, B) 90 | M4.v2pt_theory(Bcm, N, B) 91 | 92 | # Inertia Dyadic 93 | # --------------------------- 94 | IB = inertia(B, IBxx, IByy, IBzz) 95 | 96 | # Create Bodies 97 | # --------------------------- 98 | BodyB = RigidBody('BodyB', Bcm, B, mB, (IB, Bcm)) 99 | BodyList = [BodyB] 100 | 101 | # Forces and Torques 102 | # --------------------------- 103 | Grav_Force = (Bcm, mB*g*N.z) 104 | FM1 = (M1, -ThrM1*B.z) 105 | FM2 = (M2, -ThrM2*B.z) 106 | FM3 = (M3, -ThrM3*B.z) 107 | FM4 = (M4, -ThrM4*B.z) 108 | 109 | TM1 = (B, -TorM1*B.z) 110 | TM2 = (B, TorM2*B.z) 111 | TM3 = (B, -TorM3*B.z) 112 | TM4 = (B, TorM4*B.z) 113 | ForceList = [Grav_Force, FM1, FM2, FM3, FM4, TM1, TM2, TM3, TM4] 114 | 115 | # Calculate Quaternion Derivative 116 | # --------------------------- 117 | Gquat = Matrix([[-q1, q0, q3, -q2], 118 | [-q2, -q3, q0, q1], 119 | [-q3, q2, -q1, q0]]) 120 | 121 | angVel = Matrix([[dot(B.ang_vel_in(N), B.x)],[dot(B.ang_vel_in(N), B.y)],[dot(B.ang_vel_in(N), B.z)]]) # Basically the same as: angVel = Matrix([[p],[q],[r]]) 122 | quat_dot = 1.0/2*Gquat.T*angVel 123 | 124 | # Kinematic Differential Equations 125 | # --------------------------- 126 | kd = [xdot - xd, ydot - yd, zdot - zd, q0d - quat_dot[0], q1d - quat_dot[1], q2d - quat_dot[2], q3d - quat_dot[3]] 127 | 128 | # Kane's Method 129 | # --------------------------- 130 | KM = KanesMethod(N, q_ind=[x, y, z, q0, q1, q2, q3], u_ind=[xdot, ydot, zdot, p, q, r], kd_eqs=kd) 131 | (fr, frstar) = KM.kanes_equations(BodyList, ForceList) 132 | 133 | # Equations of Motion 134 | # --------------------------- 135 | MM = KM.mass_matrix_full 136 | kdd = KM.kindiffdict() 137 | rhs = KM.forcing_full 138 | MM = MM.subs(kdd) 139 | rhs = rhs.subs(kdd) 140 | 141 | MM.simplify() 142 | # MM = MM.subs(q0**2 + q1**2 + q2**2 + q3**2, 1) 143 | print() 144 | print('Mass Matrix') 145 | print('-----------') 146 | mprint(MM) 147 | 148 | rhs.simplify() 149 | # rhs = rhs.subs(q0**2 + q1**2 + q2**2 + q3**2, 1) 150 | print('Right Hand Side') 151 | print('---------------') 152 | mprint(rhs) 153 | print() 154 | 155 | # So, MM*x = rhs, where x is the State Derivatives 156 | # Solve for x 157 | stateDot = MM.inv()*rhs 158 | print('State Derivatives') 159 | print('-----------------------------------') 160 | mprint(stateDot) 161 | print() 162 | 163 | # POST-PROCESSING 164 | # --------------------------- 165 | 166 | # Useful Numbers 167 | # --------------------------- 168 | # p, q, r are the drone's angular velocities in the inertial frame, expressed in the drone's frame. 169 | # These calculations are not relevant to the ODE, but might be used for control. 170 | quat_dot_2 = Matrix([[q0d],[q1d],[q2d],[q3d]]) 171 | angVel_2 = 2.0*Gquat*quat_dot_2 172 | print('P, Q, R (Angular velocities in drone frame)') 173 | print('-------------------------------------------') 174 | mprint(angVel_2[0]) 175 | print() 176 | mprint(angVel_2[1]) 177 | print() 178 | mprint(angVel_2[2]) 179 | print() 180 | 181 | # u, v, w are the drone's velocities in the inertial frame, expressed in the drone's frame. 182 | # These calculations are not relevant to the ODE, but might be used for control. 183 | u = dot(Bcm.vel(N).subs(kdd), B.x).simplify() 184 | v = dot(Bcm.vel(N).subs(kdd), B.y).simplify() 185 | w = dot(Bcm.vel(N).subs(kdd), B.z).simplify() 186 | print('u, v, w (Velocities in drone frame)') 187 | print('-----------------------------------') 188 | mprint(u) 189 | print() 190 | mprint(v) 191 | print() 192 | mprint(w) 193 | print() 194 | 195 | # uFlat, vFlat, wFlat are the drone's velocities in the inertial frame, expressed in a frame 196 | # parallel to the ground, but with the drone's heading. 197 | # These calculations are not relevant to the ODE, but might be used for control. 198 | uFlat = dot(Bcm.vel(N).subs(kdd), cross(B.y, N.z)).simplify() 199 | vFlat = dot(Bcm.vel(N).subs(kdd), -cross(B.x, N.z)).simplify() 200 | wFlat = dot(Bcm.vel(N).subs(kdd), N.z).simplify() 201 | print('uFlat, vFlat, wFlat (Velocities in drone frame (after Yaw))') 202 | print('-----------------------------------------------------------') 203 | mprint(uFlat) 204 | print() 205 | mprint(vFlat) 206 | print() 207 | mprint(wFlat) 208 | print() 209 | -------------------------------------------------------------------------------- /PyDy Scripts/00 - Basic/Quad_3D_uvw/Quad_3D_flu_ENU_Euler_uvw.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | author: John Bass 4 | email: john.bobzwik@gmail.com 5 | license: MIT 6 | Please feel free to use and modify this, but keep the above information. Thanks! 7 | """ 8 | 9 | """ 10 | Using PyDy and Sympy, this script generates the equations for the state derivatives 11 | of a quadcopter in a 3-dimensional space. The states in this particular script are: 12 | 13 | x, y, z : Position of the drone's center of mass in the inertial frame, 14 | expressed in the inertial frame 15 | u, v, w : Velocity of the drone's center of mass in the inertial frame, 16 | expressed in the drone's frame 17 | phi, theta, psi : Orientation (roll, pitch, yaw angles) of the drone in the 18 | inertial frame, following the order ZYX (yaw, pitch, roll) 19 | p, q, r : Angular velocity of the drone in the inertial frame, 20 | expressed in the drone's frame 21 | 22 | Important note : This script uses a flu body orientation (front-left-up) and 23 | a ENU world orientation (East-North-Up) 24 | 25 | Other note : In the resulting state derivatives, there are still simplifications 26 | that can be made that SymPy cannot simplify (factoring). 27 | 28 | author: John Bass 29 | email: 30 | """ 31 | 32 | from sympy import symbols 33 | from sympy.physics.mechanics import * 34 | 35 | # Reference frames and Points 36 | # --------------------------- 37 | N = ReferenceFrame('N') # Inertial Frame 38 | B = ReferenceFrame('B') # Drone after X (roll) rotation (Final rotation) 39 | C = ReferenceFrame('C') # Drone after Y (pitch) rotation 40 | D = ReferenceFrame('D') # Drone after Z (yaw) rotation (First rotation) 41 | 42 | No = Point('No') 43 | Bcm = Point('Bcm') # Drone's center of mass 44 | M1 = Point('M1') # Motor 1 is front left, then the rest increments CW (Drone is in X configuration, not +) 45 | M2 = Point('M2') 46 | M3 = Point('M3') 47 | M4 = Point('M4') 48 | 49 | # Variables 50 | # --------------------------- 51 | # x, y and z are the drone's coordinates in the inertial frame, expressed with the inertial frame 52 | # u, v and w are the drone's velocities in the inertial frame, expressed with the drone's frame 53 | # phi, theta and psi represents the drone's orientation in the inertial frame, expressed with a ZYX Body rotation 54 | # p, q and r are the drone's angular velocities in the inertial frame, expressed with the drone's frame 55 | 56 | x, y, z, u, v, w = dynamicsymbols('x y z u v w') 57 | phi, theta, psi, p, q, r = dynamicsymbols('phi theta psi p q r') 58 | 59 | # First derivatives of the variables 60 | xd, yd, zd, ud, vd, wd = dynamicsymbols('x y z u v w', 1) 61 | phid, thetad, psid, pd, qd, rd = dynamicsymbols('phi theta psi p q r', 1) 62 | 63 | # Constants 64 | # --------------------------- 65 | mB, g, dxm, dym, dzm, IBxx, IByy, IBzz = symbols('mB g dxm dym dzm IBxx IByy IBzz') 66 | ThrM1, ThrM2, ThrM3, ThrM4, TorM1, TorM2, TorM3, TorM4 = symbols('ThrM1 ThrM2 ThrM3 ThrM4 TorM1 TorM2 TorM3 TorM4') 67 | 68 | # Rotation ZYX Body 69 | # --------------------------- 70 | D.orient(N, 'Axis', [psi, N.z]) 71 | C.orient(D, 'Axis', [theta, D.y]) 72 | B.orient(C, 'Axis', [phi, C.x]) 73 | 74 | # Origin 75 | # --------------------------- 76 | No.set_vel(N, 0) 77 | 78 | # Translation 79 | # --------------------------- 80 | Bcm.set_pos(No, x*N.x + y*N.y + z*N.z) 81 | Bcm.set_vel(N, u*B.x + v*B.y + w*B.z) 82 | 83 | # Motor placement 84 | # M1 is front left, then clockwise numbering 85 | # dzm is positive for motors above center of mass 86 | # --------------------------- 87 | M1.set_pos(Bcm, dxm*B.x + dym*B.y + dzm*B.z) 88 | M2.set_pos(Bcm, dxm*B.x - dym*B.y + dzm*B.z) 89 | M3.set_pos(Bcm, -dxm*B.x - dym*B.y + dzm*B.z) 90 | M4.set_pos(Bcm, -dxm*B.x + dym*B.y + dzm*B.z) 91 | M1.v2pt_theory(Bcm, N, B) 92 | M2.v2pt_theory(Bcm, N, B) 93 | M3.v2pt_theory(Bcm, N, B) 94 | M4.v2pt_theory(Bcm, N, B) 95 | 96 | # Inertia Dyadic 97 | # --------------------------- 98 | IB = inertia(B, IBxx, IByy, IBzz) 99 | 100 | # Create Bodies 101 | # --------------------------- 102 | BodyB = RigidBody('BodyB', Bcm, B, mB, (IB, Bcm)) 103 | BodyList = [BodyB] 104 | 105 | # Forces and Torques 106 | # --------------------------- 107 | Grav_Force = (Bcm, -mB*g*N.z) 108 | FM1 = (M1, ThrM1*B.z) 109 | FM2 = (M2, ThrM2*B.z) 110 | FM3 = (M3, ThrM3*B.z) 111 | FM4 = (M4, ThrM4*B.z) 112 | 113 | TM1 = (B, TorM1*B.z) 114 | TM2 = (B, -TorM2*B.z) 115 | TM3 = (B, TorM3*B.z) 116 | TM4 = (B, -TorM4*B.z) 117 | ForceList = [Grav_Force, FM1, FM2, FM3, FM4, TM1, TM2, TM3, TM4] 118 | 119 | # Kinematic Differential Equations 120 | # --------------------------- 121 | kd = [xd - dot(Bcm.vel(N), N.x), yd - dot(Bcm.vel(N), N.y), zd - dot(Bcm.vel(N), N.z), p - dot(B.ang_vel_in(N), B.x), q - dot(B.ang_vel_in(N), B.y), r - dot(B.ang_vel_in(N), B.z)] 122 | 123 | # Kane's Method 124 | # --------------------------- 125 | KM = KanesMethod(N, q_ind=[x, y, z, phi, theta, psi], u_ind=[u, v, w, p, q, r], kd_eqs=kd) 126 | (fr, frstar) = KM.kanes_equations(BodyList, ForceList) 127 | 128 | # Equations of Motion 129 | # --------------------------- 130 | MM = KM.mass_matrix_full 131 | kdd = KM.kindiffdict() 132 | rhs = KM.forcing_full 133 | rhs = rhs.subs(kdd) 134 | 135 | MM.simplify() 136 | print('Mass Matrix') 137 | print('-----------') 138 | mprint(MM) 139 | print() 140 | 141 | rhs.simplify() 142 | print('Right Hand Side') 143 | print('---------------') 144 | mprint(rhs) 145 | print() 146 | 147 | # So, MM*x = rhs, where x is the State Derivatives 148 | # Solve for x 149 | stateDot = MM.inv()*rhs 150 | print('State Derivatives') 151 | print('-----------------------------------') 152 | mprint(stateDot) 153 | print() 154 | 155 | # POST-PROCESSING 156 | # --------------------------- 157 | 158 | # Useful Numbers 159 | # --------------------------- 160 | # p, q, r are the drone's angular velocities in the inertial frame, expressed in the drone's frame. 161 | # These calculations are not relevant to the ODE, but might be used for control. 162 | print('P, Q, R (Angular velocities in drone frame)') 163 | print('-------------------------------------------') 164 | mprint(dot(B.ang_vel_in(N), B.x)) 165 | print() 166 | mprint(dot(B.ang_vel_in(N), B.y)) 167 | print() 168 | mprint(dot(B.ang_vel_in(N), B.z)) 169 | print() 170 | 171 | # xdot, ydot, zdot are the drone's velocities in the inertial frame, expressed in the inertial frame. 172 | # These calculations are not relevant to the ODE, but might be used for control 173 | xdot = dot(Bcm.vel(N).subs(kdd), N.x) 174 | ydot = dot(Bcm.vel(N).subs(kdd), N.y) 175 | zdot = dot(Bcm.vel(N).subs(kdd), N.z) 176 | print('xdot, ydot, zdot (Velocities in inertial frame)') 177 | print('-----------------------------------') 178 | mprint(xdot) 179 | print() 180 | mprint(ydot) 181 | print() 182 | mprint(zdot) 183 | print() 184 | 185 | # uFlat, vFlat, wFlat are the drone's velocities in the inertial frame, expressed in a frame 186 | # parallel to the ground, but with the drone's heading (so only after the YAW rotation). 187 | # These calculations are not relevant to the ODE, but might be used for control. 188 | uFlat = dot(Bcm.vel(N).subs(kdd), D.x) 189 | vFlat = dot(Bcm.vel(N).subs(kdd), D.y) 190 | wFlat = dot(Bcm.vel(N).subs(kdd), D.z) 191 | print('uFlat, vFlat, wFlat (Velocities in drone frame (after Yaw))') 192 | print('-----------------------------------------------------------') 193 | mprint(uFlat) 194 | print() 195 | mprint(vFlat) 196 | print() 197 | mprint(wFlat) 198 | print() 199 | -------------------------------------------------------------------------------- /PyDy Scripts/00 - Basic/Quad_3D_uvw/Quad_3D_flu_ENU_Quat_uvw.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | author: John Bass 4 | email: john.bobzwik@gmail.com 5 | license: MIT 6 | Please feel free to use and modify this, but keep the above information. Thanks! 7 | """ 8 | 9 | """ 10 | Using PyDy and Sympy, this script generates the equations for the state derivatives 11 | of a quadcopter in a 3-dimensional space. The states in this particular script are: 12 | 13 | x, y, z : Position of the drone's center of mass in the inertial frame, 14 | expressed in the inertial frame 15 | u, v, w : Velocity of the drone's center of mass in the inertial frame, 16 | expressed in the drone's frame 17 | q0, q1, q2, q3 : Orientation of the drone in the inertial frame using quaternions 18 | p, q, r : Angular velocity of the drone in the inertial frame, 19 | expressed in the drone's frame 20 | 21 | Important note : This script uses a flu body orientation (front-left-up) and 22 | a ENU world orientation (East-North-Up) 23 | 24 | Other note : In the resulting state derivatives, there are still simplifications 25 | that can be made that SymPy cannot simplify (factoring). 26 | 27 | author: John Bass 28 | email: 29 | """ 30 | 31 | from sympy import symbols, Matrix 32 | from sympy.physics.mechanics import * 33 | 34 | # Reference frames and Points 35 | # --------------------------- 36 | N = ReferenceFrame('N') # Inertial Frame 37 | B = ReferenceFrame('B') # Drone after X (roll) rotation (Final rotation) 38 | 39 | No = Point('No') 40 | Bcm = Point('Bcm') # Drone's center of mass 41 | M1 = Point('M1') # Motor 1 is front left, then the rest increments CW (Drone is in X configuration, not +) 42 | M2 = Point('M2') 43 | M3 = Point('M3') 44 | M4 = Point('M4') 45 | 46 | # Variables 47 | # --------------------------- 48 | # x, y and z are the drone's coordinates in the inertial frame, expressed with the inertial frame 49 | # u, v and w are the drone's velocities in the inertial frame, expressed with the drone's frame 50 | # q0, q1, q2, q3 represents the drone's orientation in the inertial frame, using quaternions 51 | # p, q and r are the drone's angular velocities in the inertial frame, expressed with the drone's frame 52 | 53 | x, y, z, u, v, w = dynamicsymbols('x y z u v w') 54 | q0, q1, q2, q3, p, q, r = dynamicsymbols('q0 q1 q2 q3 p q r') 55 | 56 | # First derivatives of the variables 57 | xd, yd, zd, ud, vd, wd = dynamicsymbols('x y z u v w', 1) 58 | q0d, q1d, q2d, q3d, pd, qd, rd = dynamicsymbols('q0 q1 q2 q3 p q r', 1) 59 | 60 | # Constants 61 | # --------------------------- 62 | mB, g, dxm, dym, dzm, IBxx, IByy, IBzz = symbols('mB g dxm dym dzm IBxx IByy IBzz') 63 | ThrM1, ThrM2, ThrM3, ThrM4, TorM1, TorM2, TorM3, TorM4 = symbols('ThrM1 ThrM2 ThrM3 ThrM4 TorM1 TorM2 TorM3 TorM4') 64 | 65 | # Rotation Quaternion 66 | # --------------------------- 67 | B.orient(N, 'Quaternion', [q0, q1, q2, q3]) 68 | B.set_ang_vel(N, p*B.x + q*B.y + r*B.z) 69 | 70 | # Origin 71 | # --------------------------- 72 | No.set_vel(N, 0) 73 | 74 | # Translation 75 | # --------------------------- 76 | Bcm.set_pos(No, x*N.x + y*N.y + z*N.z) 77 | Bcm.set_vel(N, u*B.x + v*B.y + w*B.z) 78 | 79 | # Motor placement 80 | # M1 is front left, then clockwise numbering 81 | # dzm is positive for motors above center of mass 82 | # --------------------------- 83 | M1.set_pos(Bcm, dxm*B.x + dym*B.y + dzm*B.z) 84 | M2.set_pos(Bcm, dxm*B.x - dym*B.y + dzm*B.z) 85 | M3.set_pos(Bcm, -dxm*B.x - dym*B.y + dzm*B.z) 86 | M4.set_pos(Bcm, -dxm*B.x + dym*B.y + dzm*B.z) 87 | M1.v2pt_theory(Bcm, N, B) 88 | M2.v2pt_theory(Bcm, N, B) 89 | M3.v2pt_theory(Bcm, N, B) 90 | M4.v2pt_theory(Bcm, N, B) 91 | 92 | # Inertia Dyadic 93 | # --------------------------- 94 | IB = inertia(B, IBxx, IByy, IBzz) 95 | 96 | # Create Bodies 97 | # --------------------------- 98 | BodyB = RigidBody('BodyB', Bcm, B, mB, (IB, Bcm)) 99 | BodyList = [BodyB] 100 | 101 | # Forces and Torques 102 | # --------------------------- 103 | Grav_Force = (Bcm, -mB*g*N.z) 104 | FM1 = (M1, ThrM1*B.z) 105 | FM2 = (M2, ThrM2*B.z) 106 | FM3 = (M3, ThrM3*B.z) 107 | FM4 = (M4, ThrM4*B.z) 108 | 109 | TM1 = (B, TorM1*B.z) 110 | TM2 = (B, -TorM2*B.z) 111 | TM3 = (B, TorM3*B.z) 112 | TM4 = (B, -TorM4*B.z) 113 | ForceList = [Grav_Force, FM1, FM2, FM3, FM4, TM1, TM2, TM3, TM4] 114 | 115 | # Calculate Quaternion Derivative 116 | # --------------------------- 117 | Gquat = Matrix([[-q1, q0, q3, -q2], 118 | [-q2, -q3, q0, q1], 119 | [-q3, q2, -q1, q0]]) 120 | 121 | angVel = Matrix([[dot(B.ang_vel_in(N), B.x)],[dot(B.ang_vel_in(N), B.y)],[dot(B.ang_vel_in(N), B.z)]]) # Basically the same as: angVel = Matrix([[p],[q],[r]]) 122 | quat_dot = 1.0/2*Gquat.T*angVel 123 | 124 | # Kinematic Differential Equations 125 | # --------------------------- 126 | kd = [xd - dot(Bcm.vel(N), N.x), yd - dot(Bcm.vel(N), N.y), zd - dot(Bcm.vel(N), N.z), q0d - quat_dot[0], q1d - quat_dot[1], q2d - quat_dot[2], q3d - quat_dot[3]] 127 | 128 | # Kane's Method 129 | # --------------------------- 130 | KM = KanesMethod(N, q_ind=[x, y, z, q0, q1, q2, q3], u_ind=[u, v, w, p, q, r], kd_eqs=kd) 131 | (fr, frstar) = KM.kanes_equations(BodyList, ForceList) 132 | 133 | # Equations of Motion 134 | # --------------------------- 135 | MM = KM.mass_matrix_full 136 | kdd = KM.kindiffdict() 137 | rhs = KM.forcing_full 138 | MM = MM.subs(kdd) 139 | rhs = rhs.subs(kdd) 140 | 141 | MM.simplify() 142 | # MM = MM.subs(q0**2 + q1**2 + q2**2 + q3**2, 1) 143 | print() 144 | print('Mass Matrix') 145 | print('-----------') 146 | mprint(MM) 147 | 148 | rhs.simplify() 149 | # rhs = rhs.subs(q0**2 + q1**2 + q2**2 + q3**2, 1) 150 | print('Right Hand Side') 151 | print('---------------') 152 | mprint(rhs) 153 | print() 154 | 155 | # So, MM*x = rhs, where x is the State Derivatives 156 | # Solve for x 157 | stateDot = MM.inv()*rhs 158 | print('State Derivatives') 159 | print('-----------------------------------') 160 | mprint(stateDot) 161 | print() 162 | 163 | # POST-PROCESSING 164 | # --------------------------- 165 | 166 | # Useful Numbers 167 | # --------------------------- 168 | # p, q, r are the drone's angular velocities in the inertial frame, expressed in the drone's frame. 169 | # These calculations are not relevant to the ODE, but might be used for control. 170 | quat_dot_2 = Matrix([[q0d],[q1d],[q2d],[q3d]]) 171 | angVel_2 = 2.0*Gquat*quat_dot_2 172 | print('P, Q, R (Angular velocities in drone frame)') 173 | print('-------------------------------------------') 174 | mprint(angVel_2[0]) 175 | print() 176 | mprint(angVel_2[1]) 177 | print() 178 | mprint(angVel_2[2]) 179 | print() 180 | 181 | # xdot, ydot, zdot are the drone's velocities in the inertial frame, expressed in the inertial frame. 182 | # These calculations are not relevant to the ODE, but might be used for control 183 | xdot = dot(Bcm.vel(N).subs(kdd), N.x).simplify() 184 | ydot = dot(Bcm.vel(N).subs(kdd), N.y).simplify() 185 | zdot = dot(Bcm.vel(N).subs(kdd), N.z).simplify() 186 | print('xdot, ydot, zdot (Velocities in inertial frame)') 187 | print('-----------------------------------') 188 | mprint(xdot) 189 | print() 190 | mprint(ydot) 191 | print() 192 | mprint(zdot) 193 | print() 194 | 195 | # uFlat, vFlat, wFlat are the drone's velocities in the inertial frame, expressed in a frame 196 | # parallel to the ground, but with the drone's heading. 197 | # These calculations are not relevant to the ODE, but might be used for control. 198 | uFlat = dot(Bcm.vel(N).subs(kdd), cross(B.y, N.z)).simplify() 199 | vFlat = dot(Bcm.vel(N).subs(kdd), -cross(B.x, N.z)).simplify() 200 | wFlat = dot(Bcm.vel(N).subs(kdd), N.z).simplify() 201 | print('uFlat, vFlat, wFlat (Velocities in drone frame (after Yaw))') 202 | print('-----------------------------------------------------------') 203 | mprint(uFlat) 204 | print() 205 | mprint(vFlat) 206 | print() 207 | mprint(wFlat) 208 | print() 209 | -------------------------------------------------------------------------------- /PyDy Scripts/00 - Basic/Quad_3D_uvw/Quad_3D_frd_NED_Euler_uvw.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | author: John Bass 4 | email: john.bobzwik@gmail.com 5 | license: MIT 6 | Please feel free to use and modify this, but keep the above information. Thanks! 7 | """ 8 | 9 | """ 10 | Using PyDy and Sympy, this script generates the equations for the state derivatives 11 | of a quadcopter in a 3-dimensional space. The states in this particular script are: 12 | 13 | x, y, z : Position of the drone's center of mass in the inertial frame, 14 | expressed in the inertial frame 15 | u, v, w : Velocity of the drone's center of mass in the inertial frame, 16 | expressed in the drone's frame 17 | phi, theta, psi : Orientation (roll, pitch, yaw angles) of the drone in the 18 | inertial frame, following the order ZYX (yaw, pitch, roll) 19 | p, q, r : Angular velocity of the drone in the inertial frame, 20 | expressed in the drone's frame 21 | 22 | Important note : This script uses a frd body orientation (front-right-down) and 23 | a NED world orientation (North-East-Down). The drone's altitude is -z. 24 | 25 | Other note : In the resulting state derivatives, there are still simplifications 26 | that can be made that SymPy cannot simplify (factoring). 27 | 28 | author: John Bass 29 | email: 30 | """ 31 | 32 | from sympy import symbols 33 | from sympy.physics.mechanics import * 34 | 35 | # Reference frames and Points 36 | # --------------------------- 37 | N = ReferenceFrame('N') # Inertial Frame 38 | B = ReferenceFrame('B') # Drone after X (roll) rotation (Final rotation) 39 | C = ReferenceFrame('C') # Drone after Y (pitch) rotation 40 | D = ReferenceFrame('D') # Drone after Z (yaw) rotation (First rotation) 41 | 42 | No = Point('No') 43 | Bcm = Point('Bcm') # Drone's center of mass 44 | M1 = Point('M1') # Motor 1 is front left, then the rest increments CW (Drone is in X configuration, not +) 45 | M2 = Point('M2') 46 | M3 = Point('M3') 47 | M4 = Point('M4') 48 | 49 | # Variables 50 | # --------------------------- 51 | # x, y and z are the drone's coordinates in the inertial frame, expressed with the inertial frame 52 | # u, v and w are the drone's velocities in the inertial frame, expressed with the drone's frame 53 | # phi, theta and psi represents the drone's orientation in the inertial frame, expressed with a ZYX Body rotation 54 | # p, q and r are the drone's angular velocities in the inertial frame, expressed with the drone's frame 55 | 56 | x, y, z, u, v, w = dynamicsymbols('x y z u v w') 57 | phi, theta, psi, p, q, r = dynamicsymbols('phi theta psi p q r') 58 | 59 | # First derivatives of the variables 60 | xd, yd, zd, ud, vd, wd = dynamicsymbols('x y z u v w', 1) 61 | phid, thetad, psid, pd, qd, rd = dynamicsymbols('phi theta psi p q r', 1) 62 | 63 | # Constants 64 | # --------------------------- 65 | mB, g, dxm, dym, dzm, IBxx, IByy, IBzz = symbols('mB g dxm dym dzm IBxx IByy IBzz') 66 | ThrM1, ThrM2, ThrM3, ThrM4, TorM1, TorM2, TorM3, TorM4 = symbols('ThrM1 ThrM2 ThrM3 ThrM4 TorM1 TorM2 TorM3 TorM4') 67 | 68 | # Rotation ZYX Body 69 | # --------------------------- 70 | D.orient(N, 'Axis', [psi, N.z]) 71 | C.orient(D, 'Axis', [theta, D.y]) 72 | B.orient(C, 'Axis', [phi, C.x]) 73 | 74 | # Origin 75 | # --------------------------- 76 | No.set_vel(N, 0) 77 | 78 | # Translation 79 | # --------------------------- 80 | Bcm.set_pos(No, x*N.x + y*N.y + z*N.z) 81 | Bcm.set_vel(N, u*B.x + v*B.y + w*B.z) 82 | 83 | # Motor placement 84 | # M1 is front left, then clockwise numbering 85 | # dzm is positive for motors above center of mass 86 | # --------------------------- 87 | M1.set_pos(Bcm, dxm*B.x - dym*B.y - dzm*B.z) 88 | M2.set_pos(Bcm, dxm*B.x + dym*B.y - dzm*B.z) 89 | M3.set_pos(Bcm, -dxm*B.x + dym*B.y - dzm*B.z) 90 | M4.set_pos(Bcm, -dxm*B.x - dym*B.y - dzm*B.z) 91 | M1.v2pt_theory(Bcm, N, B) 92 | M2.v2pt_theory(Bcm, N, B) 93 | M3.v2pt_theory(Bcm, N, B) 94 | M4.v2pt_theory(Bcm, N, B) 95 | 96 | # Inertia Dyadic 97 | # --------------------------- 98 | IB = inertia(B, IBxx, IByy, IBzz) 99 | 100 | # Create Bodies 101 | # --------------------------- 102 | BodyB = RigidBody('BodyB', Bcm, B, mB, (IB, Bcm)) 103 | BodyList = [BodyB] 104 | 105 | # Forces and Torques 106 | # --------------------------- 107 | Grav_Force = (Bcm, mB*g*N.z) 108 | FM1 = (M1, -ThrM1*B.z) 109 | FM2 = (M2, -ThrM2*B.z) 110 | FM3 = (M3, -ThrM3*B.z) 111 | FM4 = (M4, -ThrM4*B.z) 112 | 113 | TM1 = (B, -TorM1*B.z) 114 | TM2 = (B, TorM2*B.z) 115 | TM3 = (B, -TorM3*B.z) 116 | TM4 = (B, TorM4*B.z) 117 | ForceList = [Grav_Force, FM1, FM2, FM3, FM4, TM1, TM2, TM3, TM4] 118 | 119 | # Kinematic Differential Equations 120 | # --------------------------- 121 | kd = [xd - dot(Bcm.vel(N), N.x), yd - dot(Bcm.vel(N), N.y), zd - dot(Bcm.vel(N), N.z), p - dot(B.ang_vel_in(N), B.x), q - dot(B.ang_vel_in(N), B.y), r - dot(B.ang_vel_in(N), B.z)] 122 | 123 | # Kane's Method 124 | # --------------------------- 125 | KM = KanesMethod(N, q_ind=[x, y, z, phi, theta, psi], u_ind=[u, v, w, p, q, r], kd_eqs=kd) 126 | (fr, frstar) = KM.kanes_equations(BodyList, ForceList) 127 | 128 | # Equations of Motion 129 | # --------------------------- 130 | MM = KM.mass_matrix_full 131 | kdd = KM.kindiffdict() 132 | rhs = KM.forcing_full 133 | rhs = rhs.subs(kdd) 134 | 135 | MM.simplify() 136 | print('Mass Matrix') 137 | print('-----------') 138 | mprint(MM) 139 | print() 140 | 141 | rhs.simplify() 142 | print('Right Hand Side') 143 | print('---------------') 144 | mprint(rhs) 145 | print() 146 | 147 | # So, MM*x = rhs, where x is the State Derivatives 148 | # Solve for x 149 | stateDot = MM.inv()*rhs 150 | print('State Derivatives') 151 | print('-----------------------------------') 152 | mprint(stateDot) 153 | print() 154 | 155 | # POST-PROCESSING 156 | # --------------------------- 157 | 158 | # Useful Numbers 159 | # --------------------------- 160 | # p, q, r are the drone's angular velocities in the inertial frame, expressed in the drone's frame. 161 | # These calculations are not relevant to the ODE, but might be used for control. 162 | print('P, Q, R (Angular velocities in drone frame)') 163 | print('-------------------------------------------') 164 | mprint(dot(B.ang_vel_in(N), B.x)) 165 | print() 166 | mprint(dot(B.ang_vel_in(N), B.y)) 167 | print() 168 | mprint(dot(B.ang_vel_in(N), B.z)) 169 | print() 170 | 171 | # xdot, ydot, zdot are the drone's velocities in the inertial frame, expressed in the inertial frame. 172 | # These calculations are not relevant to the ODE, but might be used for control 173 | xdot = dot(Bcm.vel(N).subs(kdd), N.x) 174 | ydot = dot(Bcm.vel(N).subs(kdd), N.y) 175 | zdot = dot(Bcm.vel(N).subs(kdd), N.z) 176 | print('xdot, ydot, zdot (Velocities in inertial frame)') 177 | print('-----------------------------------') 178 | mprint(xdot) 179 | print() 180 | mprint(ydot) 181 | print() 182 | mprint(zdot) 183 | print() 184 | 185 | # uFlat, vFlat, wFlat are the drone's velocities in the inertial frame, expressed in a frame 186 | # parallel to the ground, but with the drone's heading (so only after the YAW rotation). 187 | # These calculations are not relevant to the ODE, but might be used for control. 188 | uFlat = dot(Bcm.vel(N).subs(kdd), D.x) 189 | vFlat = dot(Bcm.vel(N).subs(kdd), D.y) 190 | wFlat = dot(Bcm.vel(N).subs(kdd), D.z) 191 | print('uFlat, vFlat, wFlat (Velocities in drone frame (after Yaw))') 192 | print('-----------------------------------------------------------') 193 | mprint(uFlat) 194 | print() 195 | mprint(vFlat) 196 | print() 197 | mprint(wFlat) 198 | print() 199 | -------------------------------------------------------------------------------- /PyDy Scripts/00 - Basic/Quad_3D_uvw/Quad_3D_frd_NED_Quat_uvw.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | author: John Bass 4 | email: john.bobzwik@gmail.com 5 | license: MIT 6 | Please feel free to use and modify this, but keep the above information. Thanks! 7 | """ 8 | 9 | """ 10 | Using PyDy and Sympy, this script generates the equations for the state derivatives 11 | of a quadcopter in a 3-dimensional space. The states in this particular script are: 12 | 13 | x, y, z : Position of the drone's center of mass in the inertial frame, 14 | expressed in the inertial frame 15 | u, v, w : Velocity of the drone's center of mass in the inertial frame, 16 | expressed in the drone's frame 17 | q0, q1, q2, q3 : Orientation of the drone in the inertial frame using quaternions 18 | p, q, r : Angular velocity of the drone in the inertial frame, 19 | expressed in the drone's frame 20 | 21 | Important note : This script uses a frd body orientation (front-right-down) and 22 | a NED world orientation (North-East-Down). The drone's altitude is -z. 23 | 24 | Other note : In the resulting state derivatives, there are still simplifications 25 | that can be made that SymPy cannot simplify (factoring). 26 | 27 | author: John Bass 28 | email: 29 | """ 30 | 31 | from sympy import symbols, Matrix 32 | from sympy.physics.mechanics import * 33 | 34 | # Reference frames and Points 35 | # --------------------------- 36 | N = ReferenceFrame('N') # Inertial Frame 37 | B = ReferenceFrame('B') # Drone after X (roll) rotation (Final rotation) 38 | 39 | No = Point('No') 40 | Bcm = Point('Bcm') # Drone's center of mass 41 | M1 = Point('M1') # Motor 1 is front left, then the rest increments CW (Drone is in X configuration, not +) 42 | M2 = Point('M2') 43 | M3 = Point('M3') 44 | M4 = Point('M4') 45 | 46 | # Variables 47 | # --------------------------- 48 | # x, y and z are the drone's coordinates in the inertial frame, expressed with the inertial frame 49 | # u, v and w are the drone's velocities in the inertial frame, expressed with the drone's frame 50 | # q0, q1, q2, q3 represents the drone's orientation in the inertial frame, using quaternions 51 | # p, q and r are the drone's angular velocities in the inertial frame, expressed with the drone's frame 52 | 53 | x, y, z, u, v, w = dynamicsymbols('x y z u v w') 54 | q0, q1, q2, q3, p, q, r = dynamicsymbols('q0 q1 q2 q3 p q r') 55 | 56 | # First derivatives of the variables 57 | xd, yd, zd, ud, vd, wd = dynamicsymbols('x y z u v w', 1) 58 | q0d, q1d, q2d, q3d, pd, qd, rd = dynamicsymbols('q0 q1 q2 q3 p q r', 1) 59 | 60 | # Constants 61 | # --------------------------- 62 | mB, g, dxm, dym, dzm, IBxx, IByy, IBzz = symbols('mB g dxm dym dzm IBxx IByy IBzz') 63 | ThrM1, ThrM2, ThrM3, ThrM4, TorM1, TorM2, TorM3, TorM4 = symbols('ThrM1 ThrM2 ThrM3 ThrM4 TorM1 TorM2 TorM3 TorM4') 64 | 65 | # Rotation Quaternion 66 | # --------------------------- 67 | B.orient(N, 'Quaternion', [q0, q1, q2, q3]) 68 | B.set_ang_vel(N, p*B.x + q*B.y + r*B.z) 69 | 70 | # Origin 71 | # --------------------------- 72 | No.set_vel(N, 0) 73 | 74 | # Translation 75 | # --------------------------- 76 | Bcm.set_pos(No, x*N.x + y*N.y + z*N.z) 77 | Bcm.set_vel(N, u*B.x + v*B.y + w*B.z) 78 | 79 | # Motor placement 80 | # M1 is front left, then clockwise numbering 81 | # dzm is positive for motors above center of mass 82 | # --------------------------- 83 | M1.set_pos(Bcm, dxm*B.x - dym*B.y - dzm*B.z) 84 | M2.set_pos(Bcm, dxm*B.x + dym*B.y - dzm*B.z) 85 | M3.set_pos(Bcm, -dxm*B.x + dym*B.y - dzm*B.z) 86 | M4.set_pos(Bcm, -dxm*B.x - dym*B.y - dzm*B.z) 87 | M1.v2pt_theory(Bcm, N, B) 88 | M2.v2pt_theory(Bcm, N, B) 89 | M3.v2pt_theory(Bcm, N, B) 90 | M4.v2pt_theory(Bcm, N, B) 91 | 92 | # Inertia Dyadic 93 | # --------------------------- 94 | IB = inertia(B, IBxx, IByy, IBzz) 95 | 96 | # Create Bodies 97 | # --------------------------- 98 | BodyB = RigidBody('BodyB', Bcm, B, mB, (IB, Bcm)) 99 | BodyList = [BodyB] 100 | 101 | # Forces and Torques 102 | # --------------------------- 103 | Grav_Force = (Bcm, mB*g*N.z) 104 | FM1 = (M1, -ThrM1*B.z) 105 | FM2 = (M2, -ThrM2*B.z) 106 | FM3 = (M3, -ThrM3*B.z) 107 | FM4 = (M4, -ThrM4*B.z) 108 | 109 | TM1 = (B, -TorM1*B.z) 110 | TM2 = (B, TorM2*B.z) 111 | TM3 = (B, -TorM3*B.z) 112 | TM4 = (B, TorM4*B.z) 113 | ForceList = [Grav_Force, FM1, FM2, FM3, FM4, TM1, TM2, TM3, TM4] 114 | 115 | # Calculate Quaternion Derivative 116 | # --------------------------- 117 | Gquat = Matrix([[-q1, q0, q3, -q2], 118 | [-q2, -q3, q0, q1], 119 | [-q3, q2, -q1, q0]]) 120 | 121 | angVel = Matrix([[dot(B.ang_vel_in(N), B.x)],[dot(B.ang_vel_in(N), B.y)],[dot(B.ang_vel_in(N), B.z)]]) # Basically the same as: angVel = Matrix([[p],[q],[r]]) 122 | quat_dot = 1.0/2*Gquat.T*angVel 123 | 124 | # Kinematic Differential Equations 125 | # --------------------------- 126 | kd = [xd - dot(Bcm.vel(N), N.x), yd - dot(Bcm.vel(N), N.y), zd - dot(Bcm.vel(N), N.z), q0d - quat_dot[0], q1d - quat_dot[1], q2d - quat_dot[2], q3d - quat_dot[3]] 127 | 128 | # Kane's Method 129 | # --------------------------- 130 | KM = KanesMethod(N, q_ind=[x, y, z, q0, q1, q2, q3], u_ind=[u, v, w, p, q, r], kd_eqs=kd) 131 | (fr, frstar) = KM.kanes_equations(BodyList, ForceList) 132 | 133 | # Equations of Motion 134 | # --------------------------- 135 | MM = KM.mass_matrix_full 136 | kdd = KM.kindiffdict() 137 | rhs = KM.forcing_full 138 | MM = MM.subs(kdd) 139 | rhs = rhs.subs(kdd) 140 | 141 | MM.simplify() 142 | # MM = MM.subs(q0**2 + q1**2 + q2**2 + q3**2, 1) 143 | print() 144 | print('Mass Matrix') 145 | print('-----------') 146 | mprint(MM) 147 | 148 | rhs.simplify() 149 | # rhs = rhs.subs(q0**2 + q1**2 + q2**2 + q3**2, 1) 150 | print('Right Hand Side') 151 | print('---------------') 152 | mprint(rhs) 153 | print() 154 | 155 | # So, MM*x = rhs, where x is the State Derivatives 156 | # Solve for x 157 | stateDot = MM.inv()*rhs 158 | print('State Derivatives') 159 | print('-----------------------------------') 160 | mprint(stateDot) 161 | print() 162 | 163 | # POST-PROCESSING 164 | # --------------------------- 165 | 166 | # Useful Numbers 167 | # --------------------------- 168 | # p, q, r are the drone's angular velocities in the inertial frame, expressed in the drone's frame. 169 | # These calculations are not relevant to the ODE, but might be used for control. 170 | quat_dot_2 = Matrix([[q0d],[q1d],[q2d],[q3d]]) 171 | angVel_2 = 2.0*Gquat*quat_dot_2 172 | print('P, Q, R (Angular velocities in drone frame)') 173 | print('-------------------------------------------') 174 | mprint(angVel_2[0]) 175 | print() 176 | mprint(angVel_2[1]) 177 | print() 178 | mprint(angVel_2[2]) 179 | print() 180 | 181 | # xdot, ydot, zdot are the drone's velocities in the inertial frame, expressed in the inertial frame. 182 | # These calculations are not relevant to the ODE, but might be used for control 183 | xdot = dot(Bcm.vel(N).subs(kdd), N.x).simplify() 184 | ydot = dot(Bcm.vel(N).subs(kdd), N.y).simplify() 185 | zdot = dot(Bcm.vel(N).subs(kdd), N.z).simplify() 186 | print('xdot, ydot, zdot (Velocities in inertial frame)') 187 | print('-----------------------------------') 188 | mprint(xdot) 189 | print() 190 | mprint(ydot) 191 | print() 192 | mprint(zdot) 193 | print() 194 | 195 | # uFlat, vFlat, wFlat are the drone's velocities in the inertial frame, expressed in a frame 196 | # parallel to the ground, but with the drone's heading. 197 | # These calculations are not relevant to the ODE, but might be used for control. 198 | uFlat = dot(Bcm.vel(N).subs(kdd), cross(B.y, N.z)).simplify() 199 | vFlat = dot(Bcm.vel(N).subs(kdd), -cross(B.x, N.z)).simplify() 200 | wFlat = dot(Bcm.vel(N).subs(kdd), N.z).simplify() 201 | print('uFlat, vFlat, wFlat (Velocities in drone frame (after Yaw))') 202 | print('-----------------------------------------------------------') 203 | mprint(uFlat) 204 | print() 205 | mprint(vFlat) 206 | print() 207 | mprint(wFlat) 208 | print() 209 | -------------------------------------------------------------------------------- /PyDy Scripts/01 - Added Gyroscopic Precession/Quad_3D_flu_ENU_Euler.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | author: John Bass 4 | email: john.bobzwik@gmail.com 5 | license: MIT 6 | Please feel free to use and modify this, but keep the above information. Thanks! 7 | """ 8 | 9 | """ 10 | Using PyDy and Sympy, this script generates the equations for the state derivatives 11 | of a quadcopter in a 3-dimensional space. The states in this particular script are: 12 | 13 | x, y, z : Position of the drone's center of mass in the inertial frame, 14 | expressed in the inertial frame 15 | xdot, ydot, zdot : Velocity of the drone's center of mass in the inertial frame, 16 | expressed in the inertial frame 17 | phi, theta, psi : Orientation (roll, pitch, yaw angles) of the drone in the 18 | inertial frame, following the order ZYX (yaw, pitch, roll) 19 | p, q, r : Angular velocity of the drone in the inertial frame, 20 | expressed in the drone's frame 21 | 22 | Important note : This script uses a flu body orientation (front-left-up) and 23 | a ENU world orientation (East-North-Up). 24 | 25 | Other note : In the resulting state derivatives, there are still simplifications 26 | that can be made that SymPy cannot simplify (factoring). 27 | 28 | author: John Bass 29 | email: 30 | """ 31 | 32 | from sympy import symbols 33 | from sympy.physics.mechanics import * 34 | 35 | # Reference frames and Points 36 | # --------------------------- 37 | N = ReferenceFrame('N') # Inertial Frame 38 | B = ReferenceFrame('B') # Drone after X (roll) rotation (Final rotation) 39 | C = ReferenceFrame('C') # Drone after Y (pitch) rotation 40 | D = ReferenceFrame('D') # Drone after Z (yaw) rotation (First rotation) 41 | 42 | No = Point('No') 43 | Bcm = Point('Bcm') # Drone's center of mass 44 | M1 = Point('M1') # Motor 1 is front left, then the rest increments CW (Drone is in X configuration, not +) 45 | M2 = Point('M2') 46 | M3 = Point('M3') 47 | M4 = Point('M4') 48 | 49 | # Variables 50 | # --------------------------- 51 | # x, y and z are the drone's coordinates in the inertial frame, expressed with the inertial frame 52 | # xdot, ydot and zdot are the drone's velocities in the inertial frame, expressed with the inertial frame 53 | # phi, theta and psi represents the drone's orientation in the inertial frame, expressed with a ZYX Body rotation 54 | # p, q and r are the drone's angular velocities in the inertial frame, expressed with the drone's frame 55 | 56 | x, y, z, xdot, ydot, zdot = dynamicsymbols('x y z xdot ydot zdot') 57 | phi, theta, psi, p, q, r = dynamicsymbols('phi theta psi p q r') 58 | 59 | # First derivatives of the variables 60 | xd, yd, zd, xdotd, ydotd, zdotd = dynamicsymbols('x y z xdot ydot zdot', 1) 61 | phid, thetad, psid, pd, qd, rd = dynamicsymbols('phi theta psi p q r', 1) 62 | 63 | # Constants 64 | # --------------------------- 65 | mB, g, dxm, dym, dzm, IBxx, IByy, IBzz, IRzz, wM1, wM2, wM3, wM4 = symbols('mB g dxm dym dzm IBxx IByy IBzz IRzz wM1 wM2 wM3 wM4') 66 | ThrM1, ThrM2, ThrM3, ThrM4, TorM1, TorM2, TorM3, TorM4 = symbols('ThrM1 ThrM2 ThrM3 ThrM4 TorM1 TorM2 TorM3 TorM4') 67 | 68 | # Rotation ZYX Body 69 | # --------------------------- 70 | D.orient(N, 'Axis', [psi, N.z]) 71 | C.orient(D, 'Axis', [theta, D.y]) 72 | B.orient(C, 'Axis', [phi, C.x]) 73 | 74 | # Origin 75 | # --------------------------- 76 | No.set_vel(N, 0) 77 | 78 | # Translation 79 | # --------------------------- 80 | Bcm.set_pos(No, x*N.x + y*N.y + z*N.z) 81 | Bcm.set_vel(N, Bcm.pos_from(No).dt(N)) 82 | 83 | # Motor placement 84 | # M1 is front left, then clockwise numbering 85 | # dzm is positive for motors above center of mass 86 | # --------------------------- 87 | M1.set_pos(Bcm, dxm*B.x + dym*B.y + dzm*B.z) 88 | M2.set_pos(Bcm, dxm*B.x - dym*B.y + dzm*B.z) 89 | M3.set_pos(Bcm, -dxm*B.x - dym*B.y + dzm*B.z) 90 | M4.set_pos(Bcm, -dxm*B.x + dym*B.y + dzm*B.z) 91 | M1.v2pt_theory(Bcm, N, B) 92 | M2.v2pt_theory(Bcm, N, B) 93 | M3.v2pt_theory(Bcm, N, B) 94 | M4.v2pt_theory(Bcm, N, B) 95 | 96 | # Inertia Dyadic 97 | # --------------------------- 98 | IB = inertia(B, IBxx, IByy, IBzz) 99 | 100 | # Create Bodies 101 | # --------------------------- 102 | BodyB = RigidBody('BodyB', Bcm, B, mB, (IB, Bcm)) 103 | BodyList = [BodyB] 104 | 105 | # Forces and Torques 106 | # --------------------------- 107 | Grav_Force = (Bcm, -mB*g*N.z) 108 | FM1 = (M1, ThrM1*B.z) 109 | FM2 = (M2, ThrM2*B.z) 110 | FM3 = (M3, ThrM3*B.z) 111 | FM4 = (M4, ThrM4*B.z) 112 | 113 | TM1 = (B, TorM1*B.z) 114 | TM2 = (B, -TorM2*B.z) 115 | TM3 = (B, TorM3*B.z) 116 | TM4 = (B, -TorM4*B.z) 117 | 118 | gyro = (B, -IRzz*cross(B.ang_vel_in(N), (-wM1 + wM2 - wM3 + wM4)*B.z)) 119 | 120 | ForceList = [Grav_Force, FM1, FM2, FM3, FM4, TM1, TM2, TM3, TM4, gyro] 121 | 122 | # Kinematic Differential Equations 123 | # --------------------------- 124 | kd = [xdot - xd, ydot - yd, zdot - zd, p - dot(B.ang_vel_in(N), B.x), q - dot(B.ang_vel_in(N), B.y), r - dot(B.ang_vel_in(N), B.z)] 125 | 126 | # Kane's Method 127 | # --------------------------- 128 | KM = KanesMethod(N, q_ind=[x, y, z, phi, theta, psi], u_ind=[xdot, ydot, zdot, p, q, r], kd_eqs=kd) 129 | (fr, frstar) = KM.kanes_equations(BodyList, ForceList) 130 | 131 | # Equations of Motion 132 | # --------------------------- 133 | MM = KM.mass_matrix_full 134 | kdd = KM.kindiffdict() 135 | rhs = KM.forcing_full 136 | rhs = rhs.subs(kdd) 137 | 138 | MM.simplify() 139 | print('Mass Matrix') 140 | print('-----------') 141 | mprint(MM) 142 | print() 143 | 144 | rhs.simplify() 145 | print('Right Hand Side') 146 | print('---------------') 147 | mprint(rhs) 148 | print() 149 | 150 | # So, MM*x = rhs, where x is the State Derivatives 151 | # Solve for x 152 | stateDot = MM.inv()*rhs 153 | print('State Derivatives') 154 | print('-----------------------------------') 155 | mprint(stateDot) 156 | print() 157 | 158 | # POST-PROCESSING 159 | # --------------------------- 160 | 161 | # Useful Numbers 162 | # --------------------------- 163 | # p, q, r are the drone's angular velocities in the inertial frame, expressed in the drone's frame. 164 | # These calculations are not relevant to the ODE, but might be used for control. 165 | print('P, Q, R (Angular velocities in drone frame)') 166 | print('-------------------------------------------') 167 | mprint(dot(B.ang_vel_in(N), B.x)) 168 | print() 169 | mprint(dot(B.ang_vel_in(N), B.y)) 170 | print() 171 | mprint(dot(B.ang_vel_in(N), B.z)) 172 | print() 173 | 174 | # u, v, w are the drone's velocities in the inertial frame, expressed in the drone's frame. 175 | # These calculations are not relevant to the ODE, but might be used for control. 176 | u = dot(Bcm.vel(N).subs(kdd), B.x) 177 | v = dot(Bcm.vel(N).subs(kdd), B.y) 178 | w = dot(Bcm.vel(N).subs(kdd), B.z) 179 | print('u, v, w (Velocities in drone frame)') 180 | print('-----------------------------------') 181 | mprint(u) 182 | print() 183 | mprint(v) 184 | print() 185 | mprint(w) 186 | print() 187 | 188 | # uFlat, vFlat, wFlat are the drone's velocities in the inertial frame, expressed in a frame 189 | # parallel to the ground, but with the drone's heading (so only after the YAW rotation). 190 | # These calculations are not relevant to the ODE, but might be used for control. 191 | uFlat = dot(Bcm.vel(N).subs(kdd), D.x) 192 | vFlat = dot(Bcm.vel(N).subs(kdd), D.y) 193 | wFlat = dot(Bcm.vel(N).subs(kdd), D.z) 194 | print('uFlat, vFlat, wFlat (Velocities in drone frame (after Yaw))') 195 | print('-----------------------------------------------------------') 196 | mprint(uFlat) 197 | print() 198 | mprint(vFlat) 199 | print() 200 | mprint(wFlat) 201 | print() 202 | -------------------------------------------------------------------------------- /PyDy Scripts/01 - Added Gyroscopic Precession/Quad_3D_flu_ENU_Quat.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | author: John Bass 4 | email: john.bobzwik@gmail.com 5 | license: MIT 6 | Please feel free to use and modify this, but keep the above information. Thanks! 7 | """ 8 | 9 | """ 10 | Using PyDy and Sympy, this script generates the equations for the state derivatives 11 | of a quadcopter in a 3-dimensional space. The states in this particular script are: 12 | 13 | x, y, z : Position of the drone's center of mass in the inertial frame, 14 | expressed in the inertial frame 15 | xdot, ydot, zdot : Velocity of the drone's center of mass in the inertial frame, 16 | expressed in the inertial frame 17 | q0, q1, q2, q3 : Orientation of the drone in the inertial frame using quaternions 18 | p, q, r : Angular velocity of the drone in the inertial frame, 19 | expressed in the drone's frame 20 | 21 | Important note : This script uses a flu body orientation (front-left-up) and 22 | a ENU world orientation (East-North-Up) 23 | 24 | Other note : In the resulting state derivatives, there are still simplifications 25 | that can be made that SymPy cannot simplify (factoring). 26 | 27 | author: John Bass 28 | email: 29 | """ 30 | 31 | from sympy import symbols, Matrix 32 | from sympy.physics.mechanics import * 33 | 34 | # Reference frames and Points 35 | # --------------------------- 36 | N = ReferenceFrame('N') # Inertial Frame 37 | B = ReferenceFrame('B') # Drone after X (roll) rotation (Final rotation) 38 | 39 | No = Point('No') 40 | Bcm = Point('Bcm') # Drone's center of mass 41 | M1 = Point('M1') # Motor 1 is front left, then the rest increments CW (Drone is in X configuration, not +) 42 | M2 = Point('M2') 43 | M3 = Point('M3') 44 | M4 = Point('M4') 45 | 46 | # Variables 47 | # --------------------------- 48 | # x, y and z are the drone's coordinates in the inertial frame, expressed with the inertial frame 49 | # xdot, ydot and zdot are the drone's speeds in the inertial frame, expressed with the inertial frame 50 | # q0, q1, q2, q3 represents the drone's orientation in the inertial frame, using quaternions 51 | # p, q and r are the drone's angular velocities in the inertial frame, expressed with the drone's frame 52 | 53 | x, y, z, xdot, ydot, zdot = dynamicsymbols('x y z xdot ydot zdot') 54 | q0, q1, q2, q3, p, q, r = dynamicsymbols('q0 q1 q2 q3 p q r') 55 | 56 | # First derivatives of the variables 57 | xd, yd, zd, xdotd, ydotd, zdotd = dynamicsymbols('x y z xdot ydot zdot', 1) 58 | q0d, q1d, q2d, q3d, pd, qd, rd = dynamicsymbols('q0 q1 q2 q3 p q r', 1) 59 | 60 | # Constants 61 | # --------------------------- 62 | mB, g, dxm, dym, dzm, IBxx, IByy, IBzz, IRzz, wM1, wM2, wM3, wM4 = symbols('mB g dxm dym dzm IBxx IByy IBzz IRzz wM1 wM2 wM3 wM4') 63 | ThrM1, ThrM2, ThrM3, ThrM4, TorM1, TorM2, TorM3, TorM4 = symbols('ThrM1 ThrM2 ThrM3 ThrM4 TorM1 TorM2 TorM3 TorM4') 64 | 65 | # Rotation Quaternion 66 | # --------------------------- 67 | B.orient(N, 'Quaternion', [q0, q1, q2, q3]) 68 | B.set_ang_vel(N, p*B.x + q*B.y + r*B.z) 69 | 70 | # Origin 71 | # --------------------------- 72 | No.set_vel(N, 0) 73 | 74 | # Translation 75 | # --------------------------- 76 | Bcm.set_pos(No, x*N.x + y*N.y + z*N.z) 77 | Bcm.set_vel(N, Bcm.pos_from(No).dt(N)) 78 | 79 | # Motor placement 80 | # M1 is front left, then clockwise numbering 81 | # dzm is positive for motors above center of mass 82 | # --------------------------- 83 | M1.set_pos(Bcm, dxm*B.x + dym*B.y + dzm*B.z) 84 | M2.set_pos(Bcm, dxm*B.x - dym*B.y + dzm*B.z) 85 | M3.set_pos(Bcm, -dxm*B.x - dym*B.y + dzm*B.z) 86 | M4.set_pos(Bcm, -dxm*B.x + dym*B.y + dzm*B.z) 87 | M1.v2pt_theory(Bcm, N, B) 88 | M2.v2pt_theory(Bcm, N, B) 89 | M3.v2pt_theory(Bcm, N, B) 90 | M4.v2pt_theory(Bcm, N, B) 91 | 92 | # Inertia Dyadic 93 | # --------------------------- 94 | IB = inertia(B, IBxx, IByy, IBzz) 95 | 96 | # Create Bodies 97 | # --------------------------- 98 | BodyB = RigidBody('BodyB', Bcm, B, mB, (IB, Bcm)) 99 | BodyList = [BodyB] 100 | 101 | # Forces and Torques 102 | # --------------------------- 103 | Grav_Force = (Bcm, -mB*g*N.z) 104 | FM1 = (M1, ThrM1*B.z) 105 | FM2 = (M2, ThrM2*B.z) 106 | FM3 = (M3, ThrM3*B.z) 107 | FM4 = (M4, ThrM4*B.z) 108 | 109 | TM1 = (B, TorM1*B.z) 110 | TM2 = (B, -TorM2*B.z) 111 | TM3 = (B, TorM3*B.z) 112 | TM4 = (B, -TorM4*B.z) 113 | 114 | gyro = (B, -IRzz*cross(B.ang_vel_in(N), (-wM1 + wM2 - wM3 + wM4)*B.z)) 115 | 116 | ForceList = [Grav_Force, FM1, FM2, FM3, FM4, TM1, TM2, TM3, TM4, gyro] 117 | 118 | # Calculate Quaternion Derivative 119 | # --------------------------- 120 | Gquat = Matrix([[-q1, q0, q3, -q2], 121 | [-q2, -q3, q0, q1], 122 | [-q3, q2, -q1, q0]]) 123 | 124 | angVel = Matrix([[dot(B.ang_vel_in(N), B.x)],[dot(B.ang_vel_in(N), B.y)],[dot(B.ang_vel_in(N), B.z)]]) # Basically the same as: angVel = Matrix([[p],[q],[r]]) 125 | quat_dot = 1.0/2*Gquat.T*angVel 126 | 127 | # Kinematic Differential Equations 128 | # --------------------------- 129 | kd = [xdot - xd, ydot - yd, zdot - zd, q0d - quat_dot[0], q1d - quat_dot[1], q2d - quat_dot[2], q3d - quat_dot[3]] 130 | 131 | # Kane's Method 132 | # --------------------------- 133 | KM = KanesMethod(N, q_ind=[x, y, z, q0, q1, q2, q3], u_ind=[xdot, ydot, zdot, p, q, r], kd_eqs=kd) 134 | (fr, frstar) = KM.kanes_equations(BodyList, ForceList) 135 | 136 | # Equations of Motion 137 | # --------------------------- 138 | MM = KM.mass_matrix_full 139 | kdd = KM.kindiffdict() 140 | rhs = KM.forcing_full 141 | MM = MM.subs(kdd) 142 | rhs = rhs.subs(kdd) 143 | 144 | MM.simplify() 145 | # MM = MM.subs(q0**2 + q1**2 + q2**2 + q3**2, 1) 146 | print() 147 | print('Mass Matrix') 148 | print('-----------') 149 | mprint(MM) 150 | 151 | rhs.simplify() 152 | # rhs = rhs.subs(q0**2 + q1**2 + q2**2 + q3**2, 1) 153 | print('Right Hand Side') 154 | print('---------------') 155 | mprint(rhs) 156 | print() 157 | 158 | # So, MM*x = rhs, where x is the State Derivatives 159 | # Solve for x 160 | stateDot = MM.inv()*rhs 161 | print('State Derivatives') 162 | print('-----------------------------------') 163 | mprint(stateDot) 164 | print() 165 | 166 | # POST-PROCESSING 167 | # --------------------------- 168 | 169 | # Useful Numbers 170 | # --------------------------- 171 | # p, q, r are the drone's angular velocities in the inertial frame, expressed in the drone's frame. 172 | # These calculations are not relevant to the ODE, but might be used for control. 173 | quat_dot_2 = Matrix([[q0d],[q1d],[q2d],[q3d]]) 174 | angVel_2 = 2.0*Gquat*quat_dot_2 175 | print('P, Q, R (Angular velocities in drone frame)') 176 | print('-------------------------------------------') 177 | mprint(angVel_2[0]) 178 | print() 179 | mprint(angVel_2[1]) 180 | print() 181 | mprint(angVel_2[2]) 182 | print() 183 | 184 | # u, v, w are the drone's velocities in the inertial frame, expressed in the drone's frame. 185 | # These calculations are not relevant to the ODE, but might be used for control. 186 | u = dot(Bcm.vel(N).subs(kdd), B.x).simplify() 187 | v = dot(Bcm.vel(N).subs(kdd), B.y).simplify() 188 | w = dot(Bcm.vel(N).subs(kdd), B.z).simplify() 189 | print('u, v, w (Velocities in drone frame)') 190 | print('-----------------------------------') 191 | mprint(u) 192 | print() 193 | mprint(v) 194 | print() 195 | mprint(w) 196 | print() 197 | 198 | # uFlat, vFlat, wFlat are the drone's velocities in the inertial frame, expressed in a frame 199 | # parallel to the ground, but with the drone's heading. 200 | # These calculations are not relevant to the ODE, but might be used for control. 201 | uFlat = dot(Bcm.vel(N).subs(kdd), cross(B.y, N.z)).simplify() 202 | vFlat = dot(Bcm.vel(N).subs(kdd), -cross(B.x, N.z)).simplify() 203 | wFlat = dot(Bcm.vel(N).subs(kdd), N.z).simplify() 204 | print('uFlat, vFlat, wFlat (Velocities in drone frame (after Yaw))') 205 | print('-----------------------------------------------------------') 206 | mprint(uFlat) 207 | print() 208 | mprint(vFlat) 209 | print() 210 | mprint(wFlat) 211 | print() 212 | -------------------------------------------------------------------------------- /PyDy Scripts/01 - Added Gyroscopic Precession/Quad_3D_frd_NED_Euler.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | author: John Bass 4 | email: john.bobzwik@gmail.com 5 | license: MIT 6 | Please feel free to use and modify this, but keep the above information. Thanks! 7 | """ 8 | 9 | """ 10 | Using PyDy and Sympy, this script generates the equations for the state derivatives 11 | of a quadcopter in a 3-dimensional space. The states in this particular script are: 12 | 13 | x, y, z : Position of the drone's center of mass in the inertial frame, 14 | expressed in the inertial frame 15 | xdot, ydot, zdot : Velocity of the drone's center of mass in the inertial frame, 16 | expressed in the inertial frame 17 | phi, theta, psi : Orientation (roll, pitch, yaw angles) of the drone in the 18 | inertial frame, following the order ZYX (yaw, pitch, roll) 19 | p, q, r : Angular velocity of the drone in the inertial frame, 20 | expressed in the drone's frame 21 | 22 | Important note : This script uses a frd body orientation (front-right-down) and 23 | a NED world orientation (North-East-Down). The drone's altitude is -z. 24 | 25 | Other note : In the resulting state derivatives, there are still simplifications 26 | that can be made that SymPy cannot simplify (factoring). 27 | 28 | author: John Bass 29 | email: 30 | """ 31 | 32 | from sympy import symbols 33 | from sympy.physics.mechanics import * 34 | 35 | # Reference frames and Points 36 | # --------------------------- 37 | N = ReferenceFrame('N') # Inertial Frame 38 | B = ReferenceFrame('B') # Drone after X (roll) rotation (Final rotation) 39 | C = ReferenceFrame('C') # Drone after Y (pitch) rotation 40 | D = ReferenceFrame('D') # Drone after Z (yaw) rotation (First rotation) 41 | 42 | No = Point('No') 43 | Bcm = Point('Bcm') # Drone's center of mass 44 | M1 = Point('M1') # Motor 1 is front left, then the rest increments CW (Drone is in X configuration, not +) 45 | M2 = Point('M2') 46 | M3 = Point('M3') 47 | M4 = Point('M4') 48 | 49 | # Variables 50 | # --------------------------- 51 | # x, y and z are the drone's coordinates in the inertial frame, expressed with the inertial frame 52 | # xdot, ydot and zdot are the drone's velocities in the inertial frame, expressed with the inertial frame 53 | # phi, theta and psi represents the drone's orientation in the inertial frame, expressed with a ZYX Body rotation 54 | # p, q and r are the drone's angular velocities in the inertial frame, expressed with the drone's frame 55 | 56 | x, y, z, xdot, ydot, zdot = dynamicsymbols('x y z xdot ydot zdot') 57 | phi, theta, psi, p, q, r = dynamicsymbols('phi theta psi p q r') 58 | 59 | # First derivatives of the variables 60 | xd, yd, zd, xdotd, ydotd, zdotd = dynamicsymbols('x y z xdot ydot zdot', 1) 61 | phid, thetad, psid, pd, qd, rd = dynamicsymbols('phi theta psi p q r', 1) 62 | 63 | # Constants 64 | # --------------------------- 65 | mB, g, dxm, dym, dzm, IBxx, IByy, IBzz, IRzz, wM1, wM2, wM3, wM4 = symbols('mB g dxm dym dzm IBxx IByy IBzz IRzz wM1 wM2 wM3 wM4') 66 | ThrM1, ThrM2, ThrM3, ThrM4, TorM1, TorM2, TorM3, TorM4 = symbols('ThrM1 ThrM2 ThrM3 ThrM4 TorM1 TorM2 TorM3 TorM4') 67 | 68 | # Rotation ZYX Body 69 | # --------------------------- 70 | D.orient(N, 'Axis', [psi, N.z]) 71 | C.orient(D, 'Axis', [theta, D.y]) 72 | B.orient(C, 'Axis', [phi, C.x]) 73 | 74 | # Origin 75 | # --------------------------- 76 | No.set_vel(N, 0) 77 | 78 | # Translation 79 | # --------------------------- 80 | Bcm.set_pos(No, x*N.x + y*N.y + z*N.z) 81 | Bcm.set_vel(N, Bcm.pos_from(No).dt(N)) 82 | 83 | # Motor placement 84 | # M1 is front left, then clockwise numbering 85 | # dzm is positive for motors above center of mass 86 | # --------------------------- 87 | M1.set_pos(Bcm, dxm*B.x - dym*B.y - dzm*B.z) 88 | M2.set_pos(Bcm, dxm*B.x + dym*B.y - dzm*B.z) 89 | M3.set_pos(Bcm, -dxm*B.x + dym*B.y - dzm*B.z) 90 | M4.set_pos(Bcm, -dxm*B.x - dym*B.y - dzm*B.z) 91 | M1.v2pt_theory(Bcm, N, B) 92 | M2.v2pt_theory(Bcm, N, B) 93 | M3.v2pt_theory(Bcm, N, B) 94 | M4.v2pt_theory(Bcm, N, B) 95 | 96 | # Inertia Dyadic 97 | # --------------------------- 98 | IB = inertia(B, IBxx, IByy, IBzz) 99 | 100 | # Create Bodies 101 | # --------------------------- 102 | BodyB = RigidBody('BodyB', Bcm, B, mB, (IB, Bcm)) 103 | BodyList = [BodyB] 104 | 105 | # Forces and Torques 106 | # --------------------------- 107 | Grav_Force = (Bcm, mB*g*N.z) 108 | FM1 = (M1, -ThrM1*B.z) 109 | FM2 = (M2, -ThrM2*B.z) 110 | FM3 = (M3, -ThrM3*B.z) 111 | FM4 = (M4, -ThrM4*B.z) 112 | 113 | TM1 = (B, -TorM1*B.z) 114 | TM2 = (B, TorM2*B.z) 115 | TM3 = (B, -TorM3*B.z) 116 | TM4 = (B, TorM4*B.z) 117 | 118 | gyro = (B, -IRzz*cross(B.ang_vel_in(N), (wM1 - wM2 + wM3 - wM4)*B.z)) 119 | 120 | ForceList = [Grav_Force, FM1, FM2, FM3, FM4, TM1, TM2, TM3, TM4, gyro] 121 | 122 | # Kinematic Differential Equations 123 | # --------------------------- 124 | kd = [xdot - xd, ydot - yd, zdot - zd, p - dot(B.ang_vel_in(N), B.x), q - dot(B.ang_vel_in(N), B.y), r - dot(B.ang_vel_in(N), B.z)] 125 | 126 | # Kane's Method 127 | # --------------------------- 128 | KM = KanesMethod(N, q_ind=[x, y, z, phi, theta, psi], u_ind=[xdot, ydot, zdot, p, q, r], kd_eqs=kd) 129 | (fr, frstar) = KM.kanes_equations(BodyList, ForceList) 130 | 131 | # Equations of Motion 132 | # --------------------------- 133 | MM = KM.mass_matrix_full 134 | kdd = KM.kindiffdict() 135 | rhs = KM.forcing_full 136 | rhs = rhs.subs(kdd) 137 | 138 | MM.simplify() 139 | print('Mass Matrix') 140 | print('-----------') 141 | mprint(MM) 142 | print() 143 | 144 | rhs.simplify() 145 | print('Right Hand Side') 146 | print('---------------') 147 | mprint(rhs) 148 | print() 149 | 150 | # So, MM*x = rhs, where x is the State Derivatives 151 | # Solve for x 152 | stateDot = MM.inv()*rhs 153 | print('State Derivatives') 154 | print('-----------------------------------') 155 | mprint(stateDot) 156 | print() 157 | 158 | # POST-PROCESSING 159 | # --------------------------- 160 | 161 | # Useful Numbers 162 | # --------------------------- 163 | # p, q, r are the drone's angular velocities in the inertial frame, expressed in the drone's frame. 164 | # These calculations are not relevant to the ODE, but might be used for control. 165 | print('P, Q, R (Angular velocities in drone frame)') 166 | print('-------------------------------------------') 167 | mprint(dot(B.ang_vel_in(N), B.x)) 168 | print() 169 | mprint(dot(B.ang_vel_in(N), B.y)) 170 | print() 171 | mprint(dot(B.ang_vel_in(N), B.z)) 172 | print() 173 | 174 | # u, v, w are the drone's velocities in the inertial frame, expressed in the drone's frame. 175 | # These calculations are not relevant to the ODE, but might be used for control. 176 | u = dot(Bcm.vel(N).subs(kdd), B.x) 177 | v = dot(Bcm.vel(N).subs(kdd), B.y) 178 | w = dot(Bcm.vel(N).subs(kdd), B.z) 179 | print('u, v, w (Velocities in drone frame)') 180 | print('-----------------------------------') 181 | mprint(u) 182 | print() 183 | mprint(v) 184 | print() 185 | mprint(w) 186 | print() 187 | 188 | # uFlat, vFlat, wFlat are the drone's velocities in the inertial frame, expressed in a frame 189 | # parallel to the ground, but with the drone's heading (so only after the YAW rotation). 190 | # These calculations are not relevant to the ODE, but might be used for control. 191 | uFlat = dot(Bcm.vel(N).subs(kdd), D.x) 192 | vFlat = dot(Bcm.vel(N).subs(kdd), D.y) 193 | wFlat = dot(Bcm.vel(N).subs(kdd), D.z) 194 | print('uFlat, vFlat, wFlat (Velocities in drone frame (after Yaw))') 195 | print('-----------------------------------------------------------') 196 | mprint(uFlat) 197 | print() 198 | mprint(vFlat) 199 | print() 200 | mprint(wFlat) 201 | print() 202 | -------------------------------------------------------------------------------- /PyDy Scripts/01 - Added Gyroscopic Precession/Quad_3D_frd_NED_Quat.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | author: John Bass 4 | email: john.bobzwik@gmail.com 5 | license: MIT 6 | Please feel free to use and modify this, but keep the above information. Thanks! 7 | """ 8 | 9 | """ 10 | Using PyDy and Sympy, this script generates the equations for the state derivatives 11 | of a quadcopter in a 3-dimensional space. The states in this particular script are: 12 | 13 | x, y, z : Position of the drone's center of mass in the inertial frame, 14 | expressed in the inertial frame 15 | xdot, ydot, zdot : Velocity of the drone's center of mass in the inertial frame, 16 | expressed in the inertial frame 17 | q0, q1, q2, q3 : Orientation of the drone in the inertial frame using quaternions 18 | p, q, r : Angular velocity of the drone in the inertial frame, 19 | expressed in the drone's frame 20 | 21 | Important note : This script uses a frd body orientation (front-right-down) and 22 | a NED world orientation (North-East-Down). The drone's altitude is -z. 23 | 24 | Other note : In the resulting state derivatives, there are still simplifications 25 | that can be made that SymPy cannot simplify (factoring). 26 | 27 | author: John Bass 28 | email: 29 | """ 30 | 31 | from sympy import symbols, Matrix 32 | from sympy.physics.mechanics import * 33 | 34 | # Reference frames and Points 35 | # --------------------------- 36 | N = ReferenceFrame('N') # Inertial Frame 37 | B = ReferenceFrame('B') # Drone after X (roll) rotation (Final rotation) 38 | 39 | No = Point('No') 40 | Bcm = Point('Bcm') # Drone's center of mass 41 | M1 = Point('M1') # Motor 1 is front left, then the rest increments CW (Drone is in X configuration, not +) 42 | M2 = Point('M2') 43 | M3 = Point('M3') 44 | M4 = Point('M4') 45 | 46 | # Variables 47 | # --------------------------- 48 | # x, y and z are the drone's coordinates in the inertial frame, expressed with the inertial frame 49 | # xdot, ydot and zdot are the drone's speeds in the inertial frame, expressed with the inertial frame 50 | # q0, q1, q2, q3 represents the drone's orientation in the inertial frame, using quaternions 51 | # p, q and r are the drone's angular velocities in the inertial frame, expressed with the drone's frame 52 | 53 | x, y, z, xdot, ydot, zdot = dynamicsymbols('x y z xdot ydot zdot') 54 | q0, q1, q2, q3, p, q, r = dynamicsymbols('q0 q1 q2 q3 p q r') 55 | 56 | # First derivatives of the variables 57 | xd, yd, zd, xdotd, ydotd, zdotd = dynamicsymbols('x y z xdot ydot zdot', 1) 58 | q0d, q1d, q2d, q3d, pd, qd, rd = dynamicsymbols('q0 q1 q2 q3 p q r', 1) 59 | 60 | # Constants 61 | # --------------------------- 62 | mB, g, dxm, dym, dzm, IBxx, IByy, IBzz, IRzz, wM1, wM2, wM3, wM4 = symbols('mB g dxm dym dzm IBxx IByy IBzz IRzz wM1 wM2 wM3 wM4') 63 | ThrM1, ThrM2, ThrM3, ThrM4, TorM1, TorM2, TorM3, TorM4 = symbols('ThrM1 ThrM2 ThrM3 ThrM4 TorM1 TorM2 TorM3 TorM4') 64 | 65 | # Rotation Quaternion 66 | # --------------------------- 67 | B.orient(N, 'Quaternion', [q0, q1, q2, q3]) 68 | B.set_ang_vel(N, p*B.x + q*B.y + r*B.z) 69 | 70 | # Origin 71 | # --------------------------- 72 | No.set_vel(N, 0) 73 | 74 | # Translation 75 | # --------------------------- 76 | Bcm.set_pos(No, x*N.x + y*N.y + z*N.z) 77 | Bcm.set_vel(N, Bcm.pos_from(No).dt(N)) 78 | 79 | # Motor placement 80 | # M1 is front left, then clockwise numbering 81 | # dzm is positive for motors above center of mass 82 | # --------------------------- 83 | M1.set_pos(Bcm, dxm*B.x - dym*B.y - dzm*B.z) 84 | M2.set_pos(Bcm, dxm*B.x + dym*B.y - dzm*B.z) 85 | M3.set_pos(Bcm, -dxm*B.x + dym*B.y - dzm*B.z) 86 | M4.set_pos(Bcm, -dxm*B.x - dym*B.y - dzm*B.z) 87 | M1.v2pt_theory(Bcm, N, B) 88 | M2.v2pt_theory(Bcm, N, B) 89 | M3.v2pt_theory(Bcm, N, B) 90 | M4.v2pt_theory(Bcm, N, B) 91 | 92 | # Inertia Dyadic 93 | # --------------------------- 94 | IB = inertia(B, IBxx, IByy, IBzz) 95 | 96 | # Create Bodies 97 | # --------------------------- 98 | BodyB = RigidBody('BodyB', Bcm, B, mB, (IB, Bcm)) 99 | BodyList = [BodyB] 100 | 101 | # Forces and Torques 102 | # --------------------------- 103 | Grav_Force = (Bcm, mB*g*N.z) 104 | FM1 = (M1, -ThrM1*B.z) 105 | FM2 = (M2, -ThrM2*B.z) 106 | FM3 = (M3, -ThrM3*B.z) 107 | FM4 = (M4, -ThrM4*B.z) 108 | 109 | TM1 = (B, -TorM1*B.z) 110 | TM2 = (B, TorM2*B.z) 111 | TM3 = (B, -TorM3*B.z) 112 | TM4 = (B, TorM4*B.z) 113 | 114 | gyro = (B, -IRzz*cross(B.ang_vel_in(N), (wM1 - wM2 + wM3 - wM4)*B.z)) 115 | 116 | ForceList = [Grav_Force, FM1, FM2, FM3, FM4, TM1, TM2, TM3, TM4, gyro] 117 | 118 | # Calculate Quaternion Derivative 119 | # --------------------------- 120 | Gquat = Matrix([[-q1, q0, q3, -q2], 121 | [-q2, -q3, q0, q1], 122 | [-q3, q2, -q1, q0]]) 123 | 124 | angVel = Matrix([[dot(B.ang_vel_in(N), B.x)],[dot(B.ang_vel_in(N), B.y)],[dot(B.ang_vel_in(N), B.z)]]) # Basically the same as: angVel = Matrix([[p],[q],[r]]) 125 | quat_dot = 1.0/2*Gquat.T*angVel 126 | 127 | # Kinematic Differential Equations 128 | # --------------------------- 129 | kd = [xdot - xd, ydot - yd, zdot - zd, q0d - quat_dot[0], q1d - quat_dot[1], q2d - quat_dot[2], q3d - quat_dot[3]] 130 | 131 | # Kane's Method 132 | # --------------------------- 133 | KM = KanesMethod(N, q_ind=[x, y, z, q0, q1, q2, q3], u_ind=[xdot, ydot, zdot, p, q, r], kd_eqs=kd) 134 | (fr, frstar) = KM.kanes_equations(BodyList, ForceList) 135 | 136 | # Equations of Motion 137 | # --------------------------- 138 | MM = KM.mass_matrix_full 139 | kdd = KM.kindiffdict() 140 | rhs = KM.forcing_full 141 | MM = MM.subs(kdd) 142 | rhs = rhs.subs(kdd) 143 | 144 | MM.simplify() 145 | # MM = MM.subs(q0**2 + q1**2 + q2**2 + q3**2, 1) 146 | print() 147 | print('Mass Matrix') 148 | print('-----------') 149 | mprint(MM) 150 | 151 | rhs.simplify() 152 | # rhs = rhs.subs(q0**2 + q1**2 + q2**2 + q3**2, 1) 153 | print('Right Hand Side') 154 | print('---------------') 155 | mprint(rhs) 156 | print() 157 | 158 | # So, MM*x = rhs, where x is the State Derivatives 159 | # Solve for x 160 | stateDot = MM.inv()*rhs 161 | print('State Derivatives') 162 | print('-----------------------------------') 163 | mprint(stateDot) 164 | print() 165 | 166 | # POST-PROCESSING 167 | # --------------------------- 168 | 169 | # Useful Numbers 170 | # --------------------------- 171 | # p, q, r are the drone's angular velocities in the inertial frame, expressed in the drone's frame. 172 | # These calculations are not relevant to the ODE, but might be used for control. 173 | quat_dot_2 = Matrix([[q0d],[q1d],[q2d],[q3d]]) 174 | angVel_2 = 2.0*Gquat*quat_dot_2 175 | print('P, Q, R (Angular velocities in drone frame)') 176 | print('-------------------------------------------') 177 | mprint(angVel_2[0]) 178 | print() 179 | mprint(angVel_2[1]) 180 | print() 181 | mprint(angVel_2[2]) 182 | print() 183 | 184 | # u, v, w are the drone's velocities in the inertial frame, expressed in the drone's frame. 185 | # These calculations are not relevant to the ODE, but might be used for control. 186 | u = dot(Bcm.vel(N).subs(kdd), B.x).simplify() 187 | v = dot(Bcm.vel(N).subs(kdd), B.y).simplify() 188 | w = dot(Bcm.vel(N).subs(kdd), B.z).simplify() 189 | print('u, v, w (Velocities in drone frame)') 190 | print('-----------------------------------') 191 | mprint(u) 192 | print() 193 | mprint(v) 194 | print() 195 | mprint(w) 196 | print() 197 | 198 | # uFlat, vFlat, wFlat are the drone's velocities in the inertial frame, expressed in a frame 199 | # parallel to the ground, but with the drone's heading. 200 | # These calculations are not relevant to the ODE, but might be used for control. 201 | uFlat = dot(Bcm.vel(N).subs(kdd), cross(B.y, N.z)).simplify() 202 | vFlat = dot(Bcm.vel(N).subs(kdd), -cross(B.x, N.z)).simplify() 203 | wFlat = dot(Bcm.vel(N).subs(kdd), N.z).simplify() 204 | print('uFlat, vFlat, wFlat (Velocities in drone frame (after Yaw))') 205 | print('-----------------------------------------------------------') 206 | mprint(uFlat) 207 | print() 208 | mprint(vFlat) 209 | print() 210 | mprint(wFlat) 211 | print() 212 | -------------------------------------------------------------------------------- /PyDy Scripts/01 - Added Gyroscopic Precession/Quad_3D_frd_NED_Quat_RotorBodies.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | author: John Bass 4 | email: john.bobzwik@gmail.com 5 | license: MIT 6 | Please feel free to use and modify this, but keep the above information. Thanks! 7 | """ 8 | 9 | """ 10 | SCRIPT NOT FUNCTIONAL 11 | 12 | 13 | Using PyDy and Sympy, this script generates the equations for the state derivatives 14 | of a quadcopter in a 3-dimensional space. The states in this particular script are: 15 | 16 | x, y, z : Position of the drone's center of mass in the inertial frame, 17 | expressed in the inertial frame 18 | xdot, ydot, zdot : Velocity of the drone's center of mass in the inertial frame, 19 | expressed in the inertial frame 20 | q0, q1, q2, q3 : Orientation of the drone in the inertial frame using quaternions 21 | p, q, r : Angular velocity of the drone in the inertial frame, 22 | expressed in the drone's frame 23 | 24 | Important note : This script uses a frd body orientation (front-right-down) and 25 | a NED world orientation (North-East-Down). The drone's altitude is -z. 26 | 27 | Other note : In the resulting state derivatives, there are still simplifications 28 | that can be made that SymPy cannot simplify (factoring). 29 | 30 | author: John Bass 31 | email: 32 | """ 33 | 34 | from sympy import symbols, Matrix 35 | from sympy.physics.mechanics import * 36 | 37 | # Reference frames and Points 38 | # --------------------------- 39 | N = ReferenceFrame('N') # Inertial Frame 40 | B = ReferenceFrame('B') # Drone after X (roll) rotation (Final rotation) 41 | R1 = ReferenceFrame('R1') 42 | R2 = ReferenceFrame('R2') 43 | R3 = ReferenceFrame('R3') 44 | R4 = ReferenceFrame('R4') 45 | 46 | No = Point('No') 47 | Bcm = Point('Bcm') # Drone's center of mass 48 | M1 = Point('M1') # Motor 1 is front left, then the rest increments CW (Drone is in X configuration, not +) 49 | M2 = Point('M2') 50 | M3 = Point('M3') 51 | M4 = Point('M4') 52 | 53 | # Variables 54 | # --------------------------- 55 | # x, y and z are the drone's coordinates in the inertial frame, expressed with the inertial frame 56 | # xdot, ydot and zdot are the drone's speeds in the inertial frame, expressed with the inertial frame 57 | # q0, q1, q2, q3 represents the drone's orientation in the inertial frame, using quaternions 58 | # p, q and r are the drone's angular velocities in the inertial frame, expressed with the drone's frame 59 | 60 | x, y, z, xdot, ydot, zdot = dynamicsymbols('x y z xdot ydot zdot') 61 | q0, q1, q2, q3, p, q, r = dynamicsymbols('q0 q1 q2 q3 p q r') 62 | 63 | # First derivatives of the variables 64 | xd, yd, zd, xdotd, ydotd, zdotd = dynamicsymbols('x y z xdot ydot zdot', 1) 65 | q0d, q1d, q2d, q3d, pd, qd, rd = dynamicsymbols('q0 q1 q2 q3 p q r', 1) 66 | 67 | qR1, qR2, qR3, qR4 = dynamicsymbols('qR1 qR2 qR3 qR4') 68 | 69 | # Constants 70 | # --------------------------- 71 | mB, g, dxm, dym, dzm, IBxx, IByy, IBzz, mR, IRzz, wM1, wM2, wM3, wM4 = symbols('mB g dxm dym dzm IBxx IByy mR IBzz IRzz wM1 wM2 wM3 wM4') 72 | ThrM1, ThrM2, ThrM3, ThrM4, TorM1, TorM2, TorM3, TorM4 = symbols('ThrM1 ThrM2 ThrM3 ThrM4 TorM1 TorM2 TorM3 TorM4') 73 | 74 | # Rotation Quaternion 75 | # --------------------------- 76 | B.orient(N, 'Quaternion', [q0, q1, q2, q3]) 77 | B.set_ang_vel(N, p*B.x + q*B.y + r*B.z) 78 | 79 | R1.orient(B, 'Axis', [qR1, B.z]) 80 | R2.orient(B, 'Axis', [qR2, B.z]) 81 | R3.orient(B, 'Axis', [qR3, B.z]) 82 | R4.orient(B, 'Axis', [qR4, B.z]) 83 | 84 | R1.set_ang_vel(B, wM1*B.z) 85 | R2.set_ang_vel(B, -wM2*B.z) 86 | R3.set_ang_vel(B, wM3*B.z) 87 | R4.set_ang_vel(B, -wM4*B.z) 88 | 89 | # Origin 90 | # --------------------------- 91 | No.set_vel(N, 0) 92 | 93 | # Translation 94 | # --------------------------- 95 | Bcm.set_pos(No, x*N.x + y*N.y + z*N.z) 96 | Bcm.set_vel(N, Bcm.pos_from(No).dt(N)) 97 | 98 | # Motor placement 99 | # M1 is front left, then clockwise numbering 100 | # dzm is positive for motors above center of mass 101 | # --------------------------- 102 | M1.set_pos(Bcm, dxm*B.x - dym*B.y - dzm*B.z) 103 | M2.set_pos(Bcm, dxm*B.x + dym*B.y - dzm*B.z) 104 | M3.set_pos(Bcm, -dxm*B.x + dym*B.y - dzm*B.z) 105 | M4.set_pos(Bcm, -dxm*B.x - dym*B.y - dzm*B.z) 106 | M1.v2pt_theory(Bcm, N, B) 107 | M2.v2pt_theory(Bcm, N, B) 108 | M3.v2pt_theory(Bcm, N, B) 109 | M4.v2pt_theory(Bcm, N, B) 110 | 111 | # Inertia Dyadic 112 | # --------------------------- 113 | IB = inertia(B, IBxx, IByy, IBzz) 114 | 115 | IR1 = inertia(R1, 0, 0, IRzz) 116 | IR2 = inertia(R2, 0, 0, IRzz) 117 | IR3 = inertia(R3, 0, 0, IRzz) 118 | IR4 = inertia(R4, 0, 0, IRzz) 119 | 120 | # Create Bodies 121 | # --------------------------- 122 | BodyB = RigidBody('BodyB', Bcm, B, mB, (IB, Bcm)) 123 | BodyR1 = RigidBody('BodyR1', M1, R1, mR, (IR1, M1)) 124 | BodyR2 = RigidBody('BodyR2', M2, R1, mR, (IR2, M2)) 125 | BodyR3 = RigidBody('BodyR3', M3, R1, mR, (IR3, M3)) 126 | BodyR4 = RigidBody('BodyR4', M4, R1, mR, (IR4, M4)) 127 | BodyList = [BodyB, BodyR1, BodyR2, BodyR3, BodyR4] 128 | 129 | # Forces and Torques 130 | # --------------------------- 131 | Grav_Force = (Bcm, mB*g*N.z) 132 | FM1 = (M1, -ThrM1*B.z) 133 | FM2 = (M2, -ThrM2*B.z) 134 | FM3 = (M3, -ThrM3*B.z) 135 | FM4 = (M4, -ThrM4*B.z) 136 | 137 | TM1 = (B, -TorM1*B.z) 138 | TM2 = (B, TorM2*B.z) 139 | TM3 = (B, -TorM3*B.z) 140 | TM4 = (B, TorM4*B.z) 141 | 142 | # Gyro = (B, -IRzz*cross(B.ang_vel_in(N),(wM1 - wM2 + wM3 - wM4)*B.z)) 143 | 144 | ForceList = [Grav_Force, FM1, FM2, FM3, FM4, TM1, TM2, TM3, TM4] 145 | 146 | # Calculate Quaternion Derivative 147 | # --------------------------- 148 | Gquat = Matrix([[-q1, q0, q3, -q2], 149 | [-q2, -q3, q0, q1], 150 | [-q3, q2, -q1, q0]]) 151 | 152 | angVel = Matrix([[dot(B.ang_vel_in(N), B.x)],[dot(B.ang_vel_in(N), B.y)],[dot(B.ang_vel_in(N), B.z)]]) # Basically the same as: angVel = Matrix([[p],[q],[r]]) 153 | quat_dot = 1.0/2*Gquat.T*angVel 154 | 155 | # Kinematic Differential Equations 156 | # --------------------------- 157 | kd = [xdot - xd, ydot - yd, zdot - zd, q0d - quat_dot[0], q1d - quat_dot[1], q2d - quat_dot[2], q3d - quat_dot[3]] 158 | 159 | # Kane's Method 160 | # --------------------------- 161 | KM = KanesMethod(N, q_ind=[x, y, z, q0, q1, q2, q3], u_ind=[xdot, ydot, zdot, p, q, r], kd_eqs=kd) 162 | (fr, frstar) = KM.kanes_equations(BodyList, ForceList) 163 | 164 | # Equations of Motion 165 | # --------------------------- 166 | MM = KM.mass_matrix_full 167 | kdd = KM.kindiffdict() 168 | rhs = KM.forcing_full 169 | MM = MM.subs(kdd) 170 | rhs = rhs.subs(kdd) 171 | 172 | MM.simplify() 173 | # MM = MM.subs(q0**2 + q1**2 + q2**2 + q3**2, 1) 174 | print() 175 | print('Mass Matrix') 176 | print('-----------') 177 | mprint(MM) 178 | 179 | rhs.simplify() 180 | # rhs = rhs.subs(q0**2 + q1**2 + q2**2 + q3**2, 1) 181 | print('Right Hand Side') 182 | print('---------------') 183 | mprint(rhs) 184 | print() 185 | 186 | # So, MM*x = rhs, where x is the State Derivatives 187 | # Solve for x 188 | stateDot = MM.inv()*rhs 189 | print('State Derivatives') 190 | print('-----------------------------------') 191 | mprint(stateDot) 192 | print() 193 | 194 | # POST-PROCESSING 195 | # --------------------------- 196 | 197 | # Useful Numbers 198 | # --------------------------- 199 | # p, q, r are the drone's angular velocities in the inertial frame, expressed in the drone's frame. 200 | # These calculations are not relevant to the ODE, but might be used for control. 201 | quat_dot_2 = Matrix([[q0d],[q1d],[q2d],[q3d]]) 202 | angVel_2 = 2.0*Gquat*quat_dot_2 203 | print('P, Q, R (Angular velocities in drone frame)') 204 | print('-------------------------------------------') 205 | mprint(angVel_2[0]) 206 | print() 207 | mprint(angVel_2[1]) 208 | print() 209 | mprint(angVel_2[2]) 210 | print() 211 | 212 | # u, v, w are the drone's velocities in the inertial frame, expressed in the drone's frame. 213 | # These calculations are not relevant to the ODE, but might be used for control. 214 | u = dot(Bcm.vel(N).subs(kdd), B.x).simplify() 215 | v = dot(Bcm.vel(N).subs(kdd), B.y).simplify() 216 | w = dot(Bcm.vel(N).subs(kdd), B.z).simplify() 217 | print('u, v, w (Velocities in drone frame)') 218 | print('-----------------------------------') 219 | mprint(u) 220 | print() 221 | mprint(v) 222 | print() 223 | mprint(w) 224 | print() 225 | 226 | # uFlat, vFlat, wFlat are the drone's velocities in the inertial frame, expressed in a frame 227 | # parallel to the ground, but with the drone's heading. 228 | # These calculations are not relevant to the ODE, but might be used for control. 229 | uFlat = dot(Bcm.vel(N).subs(kdd), cross(B.y, N.z)).simplify() 230 | vFlat = dot(Bcm.vel(N).subs(kdd), -cross(B.x, N.z)).simplify() 231 | wFlat = dot(Bcm.vel(N).subs(kdd), N.z).simplify() 232 | print('uFlat, vFlat, wFlat (Velocities in drone frame (after Yaw))') 233 | print('-----------------------------------------------------------') 234 | mprint(uFlat) 235 | print() 236 | mprint(vFlat) 237 | print() 238 | mprint(wFlat) 239 | print() 240 | -------------------------------------------------------------------------------- /PyDy Scripts/02 - Added Wind and Aero Drag/Quad_3D_flu_ENU_Quat.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | author: John Bass 4 | email: john.bobzwik@gmail.com 5 | license: MIT 6 | Please feel free to use and modify this, but keep the above information. Thanks! 7 | """ 8 | 9 | """ 10 | Using PyDy and Sympy, this script generates the equations for the state derivatives 11 | of a quadcopter in a 3-dimensional space. The states in this particular script are: 12 | 13 | x, y, z : Position of the drone's center of mass in the inertial frame, 14 | expressed in the inertial frame 15 | xdot, ydot, zdot : Velocity of the drone's center of mass in the inertial frame, 16 | expressed in the inertial frame 17 | q0, q1, q2, q3 : Orientation of the drone in the inertial frame using quaternions 18 | p, q, r : Angular velocity of the drone in the inertial frame, 19 | expressed in the drone's frame 20 | 21 | Important note : This script uses a flu body orientation (front-left-up) and 22 | a ENU world orientation (East-North-Up) 23 | 24 | Other note : In the resulting state derivatives, there are still simplifications 25 | that can be made that SymPy cannot simplify (factoring). 26 | 27 | author: John Bass 28 | email: 29 | """ 30 | 31 | from sympy import symbols, Matrix, sign 32 | from sympy.physics.mechanics import * 33 | 34 | # Reference frames and Points 35 | # --------------------------- 36 | N = ReferenceFrame('N') # Inertial Frame 37 | B = ReferenceFrame('B') # Drone after X (roll) rotation (Final rotation) 38 | W = ReferenceFrame('W') # Wind reference frame 39 | 40 | No = Point('No') 41 | Bcm = Point('Bcm') # Drone's center of mass 42 | M1 = Point('M1') # Motor 1 is front left, then the rest increments CW (Drone is in X configuration, not +) 43 | M2 = Point('M2') 44 | M3 = Point('M3') 45 | M4 = Point('M4') 46 | Wo = Point('Wo') # Arbitrary point in the Wind Frame 47 | 48 | # Variables 49 | # --------------------------- 50 | # x, y and z are the drone's coordinates in the inertial frame, expressed with the inertial frame 51 | # xdot, ydot and zdot are the drone's speeds in the inertial frame, expressed with the inertial frame 52 | # q0, q1, q2, q3 represents the drone's orientation in the inertial frame, using quaternions 53 | # p, q and r are the drone's angular velocities in the inertial frame, expressed with the drone's frame 54 | 55 | x, y, z, xdot, ydot, zdot = dynamicsymbols('x y z xdot ydot zdot') 56 | q0, q1, q2, q3, p, q, r = dynamicsymbols('q0 q1 q2 q3 p q r') 57 | 58 | # First derivatives of the variables 59 | xd, yd, zd, xdotd, ydotd, zdotd = dynamicsymbols('x y z xdot ydot zdot', 1) 60 | q0d, q1d, q2d, q3d, pd, qd, rd = dynamicsymbols('q0 q1 q2 q3 p q r', 1) 61 | 62 | # Constants 63 | # --------------------------- 64 | mB, g, dxm, dym, dzm, IBxx, IByy, IBzz, IRzz, wM1, wM2, wM3, wM4 = symbols('mB g dxm dym dzm IBxx IByy IBzz IRzz wM1 wM2 wM3 wM4') 65 | ThrM1, ThrM2, ThrM3, ThrM4, TorM1, TorM2, TorM3, TorM4 = symbols('ThrM1 ThrM2 ThrM3 ThrM4 TorM1 TorM2 TorM3 TorM4') 66 | qW1, qW2, velW, Cd = symbols('qW1 qW2 velW Cd') 67 | 68 | # Rotation Quaternion 69 | # --------------------------- 70 | B.orient(N, 'Quaternion', [q0, q1, q2, q3]) 71 | B.set_ang_vel(N, p*B.x + q*B.y + r*B.z) 72 | 73 | # Origin 74 | # --------------------------- 75 | No.set_vel(N, 0) 76 | 77 | # Translation 78 | # --------------------------- 79 | Bcm.set_pos(No, x*N.x + y*N.y + z*N.z) 80 | Bcm.set_vel(N, Bcm.pos_from(No).dt(N)) 81 | 82 | # Motor placement 83 | # M1 is front left, then clockwise numbering 84 | # dzm is positive for motors above center of mass 85 | # --------------------------- 86 | M1.set_pos(Bcm, dxm*B.x + dym*B.y + dzm*B.z) 87 | M2.set_pos(Bcm, dxm*B.x - dym*B.y + dzm*B.z) 88 | M3.set_pos(Bcm, -dxm*B.x - dym*B.y + dzm*B.z) 89 | M4.set_pos(Bcm, -dxm*B.x + dym*B.y + dzm*B.z) 90 | M1.v2pt_theory(Bcm, N, B) 91 | M2.v2pt_theory(Bcm, N, B) 92 | M3.v2pt_theory(Bcm, N, B) 93 | M4.v2pt_theory(Bcm, N, B) 94 | 95 | # Inertia Dyadic 96 | # --------------------------- 97 | IB = inertia(B, IBxx, IByy, IBzz) 98 | 99 | # Create Bodies 100 | # --------------------------- 101 | BodyB = RigidBody('BodyB', Bcm, B, mB, (IB, Bcm)) 102 | BodyList = [BodyB] 103 | 104 | # Wind 105 | # --------------------------- 106 | W.orient(N, 'Body', [qW1, qW2, 0], 'ZYX') 107 | # W.orient(N, 'Quaternion', [qW0, qW1, qW2, qW3]) 108 | Wo.set_vel(N, velW*W.x) 109 | airVel = Bcm.vel(N) - Wo.vel(N) 110 | 111 | # Forces and Torques 112 | # --------------------------- 113 | Grav_Force = (Bcm, -mB*g*N.z) 114 | FM1 = (M1, ThrM1*B.z) 115 | FM2 = (M2, ThrM2*B.z) 116 | FM3 = (M3, ThrM3*B.z) 117 | FM4 = (M4, ThrM4*B.z) 118 | 119 | TM1 = (B, TorM1*B.z) 120 | TM2 = (B, -TorM2*B.z) 121 | TM3 = (B, TorM3*B.z) 122 | TM4 = (B, -TorM4*B.z) 123 | 124 | # drag = (Bcm, -Cd*airVel.normalize()*(airVel.magnitude())**2) 125 | dragX = (Bcm, -Cd*sign(dot(airVel, N.x))*(dot(airVel, N.x)**2)*N.x) 126 | dragY = (Bcm, -Cd*sign(dot(airVel, N.y))*(dot(airVel, N.y)**2)*N.y) 127 | dragZ = (Bcm, -Cd*sign(dot(airVel, N.z))*(dot(airVel, N.z)**2)*N.z) 128 | 129 | gyro = (B, -IRzz*cross(B.ang_vel_in(N), (-wM1 + wM2 - wM3 + wM4)*B.z)) 130 | 131 | ForceList = [Grav_Force, FM1, FM2, FM3, FM4, TM1, TM2, TM3, TM4, dragX, dragY, dragZ, gyro] 132 | 133 | # Calculate Quaternion Derivative 134 | # --------------------------- 135 | Gquat = Matrix([[-q1, q0, q3, -q2], 136 | [-q2, -q3, q0, q1], 137 | [-q3, q2, -q1, q0]]) 138 | 139 | angVel = Matrix([[dot(B.ang_vel_in(N), B.x)],[dot(B.ang_vel_in(N), B.y)],[dot(B.ang_vel_in(N), B.z)]]) # Basically the same as: angVel = Matrix([[p],[q],[r]]) 140 | quat_dot = 1.0/2*Gquat.T*angVel 141 | 142 | # Kinematic Differential Equations 143 | # --------------------------- 144 | kd = [xdot - xd, ydot - yd, zdot - zd, q0d - quat_dot[0], q1d - quat_dot[1], q2d - quat_dot[2], q3d - quat_dot[3]] 145 | 146 | # Kane's Method 147 | # --------------------------- 148 | KM = KanesMethod(N, q_ind=[x, y, z, q0, q1, q2, q3], u_ind=[xdot, ydot, zdot, p, q, r], kd_eqs=kd) 149 | (fr, frstar) = KM.kanes_equations(BodyList, ForceList) 150 | 151 | # Equations of Motion 152 | # --------------------------- 153 | MM = KM.mass_matrix_full 154 | kdd = KM.kindiffdict() 155 | rhs = KM.forcing_full 156 | MM = MM.subs(kdd) 157 | rhs = rhs.subs(kdd) 158 | 159 | MM.simplify() 160 | # MM = MM.subs(q0**2 + q1**2 + q2**2 + q3**2, 1) 161 | print() 162 | print('Mass Matrix') 163 | print('-----------') 164 | mprint(MM) 165 | 166 | rhs.simplify() 167 | # rhs = rhs.subs(q0**2 + q1**2 + q2**2 + q3**2, 1) 168 | print('Right Hand Side') 169 | print('---------------') 170 | mprint(rhs) 171 | print() 172 | 173 | # So, MM*x = rhs, where x is the State Derivatives 174 | # Solve for x 175 | stateDot = MM.inv()*rhs 176 | print('State Derivatives') 177 | print('-----------------------------------') 178 | mprint(stateDot) 179 | print() 180 | 181 | # POST-PROCESSING 182 | # --------------------------- 183 | 184 | # Useful Numbers 185 | # --------------------------- 186 | # p, q, r are the drone's angular velocities in the inertial frame, expressed in the drone's frame. 187 | # These calculations are not relevant to the ODE, but might be used for control. 188 | quat_dot_2 = Matrix([[q0d],[q1d],[q2d],[q3d]]) 189 | angVel_2 = 2.0*Gquat*quat_dot_2 190 | print('P, Q, R (Angular velocities in drone frame)') 191 | print('-------------------------------------------') 192 | mprint(angVel_2[0]) 193 | print() 194 | mprint(angVel_2[1]) 195 | print() 196 | mprint(angVel_2[2]) 197 | print() 198 | 199 | # u, v, w are the drone's velocities in the inertial frame, expressed in the drone's frame. 200 | # These calculations are not relevant to the ODE, but might be used for control. 201 | u = dot(Bcm.vel(N).subs(kdd), B.x).simplify() 202 | v = dot(Bcm.vel(N).subs(kdd), B.y).simplify() 203 | w = dot(Bcm.vel(N).subs(kdd), B.z).simplify() 204 | print('u, v, w (Velocities in drone frame)') 205 | print('-----------------------------------') 206 | mprint(u) 207 | print() 208 | mprint(v) 209 | print() 210 | mprint(w) 211 | print() 212 | 213 | # uFlat, vFlat, wFlat are the drone's velocities in the inertial frame, expressed in a frame 214 | # parallel to the ground, but with the drone's heading. 215 | # These calculations are not relevant to the ODE, but might be used for control. 216 | uFlat = dot(Bcm.vel(N).subs(kdd), cross(B.y, N.z)).simplify() 217 | vFlat = dot(Bcm.vel(N).subs(kdd), -cross(B.x, N.z)).simplify() 218 | wFlat = dot(Bcm.vel(N).subs(kdd), N.z).simplify() 219 | print('uFlat, vFlat, wFlat (Velocities in drone frame (after Yaw))') 220 | print('-----------------------------------------------------------') 221 | mprint(uFlat) 222 | print() 223 | mprint(vFlat) 224 | print() 225 | mprint(wFlat) 226 | print() 227 | -------------------------------------------------------------------------------- /PyDy Scripts/02 - Added Wind and Aero Drag/Quad_3D_frd_NED_Quat.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | author: John Bass 4 | email: john.bobzwik@gmail.com 5 | license: MIT 6 | Please feel free to use and modify this, but keep the above information. Thanks! 7 | """ 8 | 9 | """ 10 | Using PyDy and Sympy, this script generates the equations for the state derivatives 11 | of a quadcopter in a 3-dimensional space. The states in this particular script are: 12 | 13 | x, y, z : Position of the drone's center of mass in the inertial frame, 14 | expressed in the inertial frame 15 | xdot, ydot, zdot : Velocity of the drone's center of mass in the inertial frame, 16 | expressed in the inertial frame 17 | q0, q1, q2, q3 : Orientation of the drone in the inertial frame using quaternions 18 | p, q, r : Angular velocity of the drone in the inertial frame, 19 | expressed in the drone's frame 20 | 21 | Important note : This script uses a frd body orientation (front-right-down) and 22 | a NED world orientation (North-East-Down). The drone's altitude is -z. 23 | 24 | Other note : In the resulting state derivatives, there are still simplifications 25 | that can be made that SymPy cannot simplify (factoring). 26 | 27 | author: John Bass 28 | email: 29 | """ 30 | 31 | from sympy import symbols, Matrix, sign 32 | from sympy.physics.mechanics import * 33 | 34 | # Reference frames and Points 35 | # --------------------------- 36 | N = ReferenceFrame('N') # Inertial Frame 37 | B = ReferenceFrame('B') # Drone after X (roll) rotation (Final rotation) 38 | W = ReferenceFrame('W') # Wind reference frame 39 | 40 | No = Point('No') 41 | Bcm = Point('Bcm') # Drone's center of mass 42 | M1 = Point('M1') # Motor 1 is front left, then the rest increments CW (Drone is in X configuration, not +) 43 | M2 = Point('M2') 44 | M3 = Point('M3') 45 | M4 = Point('M4') 46 | Wo = Point('Wo') # Arbitrary point in the Wind Frame 47 | 48 | # Variables 49 | # --------------------------- 50 | # x, y and z are the drone's coordinates in the inertial frame, expressed with the inertial frame 51 | # xdot, ydot and zdot are the drone's speeds in the inertial frame, expressed with the inertial frame 52 | # q0, q1, q2, q3 represents the drone's orientation in the inertial frame, using quaternions 53 | # p, q and r are the drone's angular velocities in the inertial frame, expressed with the drone's frame 54 | 55 | x, y, z, xdot, ydot, zdot = dynamicsymbols('x y z xdot ydot zdot') 56 | q0, q1, q2, q3, p, q, r = dynamicsymbols('q0 q1 q2 q3 p q r') 57 | 58 | # First derivatives of the variables 59 | xd, yd, zd, xdotd, ydotd, zdotd = dynamicsymbols('x y z xdot ydot zdot', 1) 60 | q0d, q1d, q2d, q3d, pd, qd, rd = dynamicsymbols('q0 q1 q2 q3 p q r', 1) 61 | 62 | # Constants 63 | # --------------------------- 64 | mB, g, dxm, dym, dzm, IBxx, IByy, IBzz, IRzz, wM1, wM2, wM3, wM4 = symbols('mB g dxm dym dzm IBxx IByy IBzz IRzz wM1 wM2 wM3 wM4') 65 | ThrM1, ThrM2, ThrM3, ThrM4, TorM1, TorM2, TorM3, TorM4 = symbols('ThrM1 ThrM2 ThrM3 ThrM4 TorM1 TorM2 TorM3 TorM4') 66 | qW1, qW2, velW, Cd = symbols('qW1 qW2 velW Cd') 67 | 68 | # Rotation Quaternion 69 | # --------------------------- 70 | B.orient(N, 'Quaternion', [q0, q1, q2, q3]) 71 | B.set_ang_vel(N, p*B.x + q*B.y + r*B.z) 72 | 73 | # Origin 74 | # --------------------------- 75 | No.set_vel(N, 0) 76 | 77 | # Translation 78 | # --------------------------- 79 | Bcm.set_pos(No, x*N.x + y*N.y + z*N.z) 80 | Bcm.set_vel(N, Bcm.pos_from(No).dt(N)) 81 | 82 | # Motor placement 83 | # M1 is front left, then clockwise numbering 84 | # dzm is positive for motors above center of mass 85 | # --------------------------- 86 | M1.set_pos(Bcm, dxm*B.x - dym*B.y - dzm*B.z) 87 | M2.set_pos(Bcm, dxm*B.x + dym*B.y - dzm*B.z) 88 | M3.set_pos(Bcm, -dxm*B.x + dym*B.y - dzm*B.z) 89 | M4.set_pos(Bcm, -dxm*B.x - dym*B.y - dzm*B.z) 90 | M1.v2pt_theory(Bcm, N, B) 91 | M2.v2pt_theory(Bcm, N, B) 92 | M3.v2pt_theory(Bcm, N, B) 93 | M4.v2pt_theory(Bcm, N, B) 94 | 95 | # Inertia Dyadic 96 | # --------------------------- 97 | IB = inertia(B, IBxx, IByy, IBzz) 98 | 99 | # Create Bodies 100 | # --------------------------- 101 | BodyB = RigidBody('BodyB', Bcm, B, mB, (IB, Bcm)) 102 | BodyList = [BodyB] 103 | 104 | # Wind 105 | # --------------------------- 106 | W.orient(N, 'Body', [qW1, qW2, 0], 'ZYX') 107 | # W.orient(N, 'Quaternion', [qW0, qW1, qW2, qW3]) 108 | Wo.set_vel(N, velW*W.x) 109 | airVel = Bcm.vel(N) - Wo.vel(N) 110 | 111 | # Forces and Torques 112 | # --------------------------- 113 | Grav_Force = (Bcm, mB*g*N.z) 114 | FM1 = (M1, -ThrM1*B.z) 115 | FM2 = (M2, -ThrM2*B.z) 116 | FM3 = (M3, -ThrM3*B.z) 117 | FM4 = (M4, -ThrM4*B.z) 118 | 119 | TM1 = (B, -TorM1*B.z) 120 | TM2 = (B, TorM2*B.z) 121 | TM3 = (B, -TorM3*B.z) 122 | TM4 = (B, TorM4*B.z) 123 | 124 | # drag = (Bcm, -Cd*airVel.normalize()*(airVel.magnitude())**2) 125 | dragX = (Bcm, -Cd*sign(dot(airVel, N.x))*(dot(airVel, N.x)**2)*N.x) 126 | dragY = (Bcm, -Cd*sign(dot(airVel, N.y))*(dot(airVel, N.y)**2)*N.y) 127 | dragZ = (Bcm, -Cd*sign(dot(airVel, N.z))*(dot(airVel, N.z)**2)*N.z) 128 | 129 | gyro = (B, -IRzz*cross(B.ang_vel_in(N), (wM1 - wM2 + wM3 - wM4)*B.z)) 130 | 131 | ForceList = [Grav_Force, FM1, FM2, FM3, FM4, TM1, TM2, TM3, TM4, dragX, dragY, dragZ, gyro] 132 | 133 | # Calculate Quaternion Derivative 134 | # --------------------------- 135 | Gquat = Matrix([[-q1, q0, q3, -q2], 136 | [-q2, -q3, q0, q1], 137 | [-q3, q2, -q1, q0]]) 138 | 139 | angVel = Matrix([[dot(B.ang_vel_in(N), B.x)],[dot(B.ang_vel_in(N), B.y)],[dot(B.ang_vel_in(N), B.z)]]) # Basically the same as: angVel = Matrix([[p],[q],[r]]) 140 | quat_dot = 1.0/2*Gquat.T*angVel 141 | 142 | # Kinematic Differential Equations 143 | # --------------------------- 144 | kd = [xdot - xd, ydot - yd, zdot - zd, q0d - quat_dot[0], q1d - quat_dot[1], q2d - quat_dot[2], q3d - quat_dot[3]] 145 | 146 | # Kane's Method 147 | # --------------------------- 148 | KM = KanesMethod(N, q_ind=[x, y, z, q0, q1, q2, q3], u_ind=[xdot, ydot, zdot, p, q, r], kd_eqs=kd) 149 | (fr, frstar) = KM.kanes_equations(BodyList, ForceList) 150 | 151 | # Equations of Motion 152 | # --------------------------- 153 | MM = KM.mass_matrix_full 154 | kdd = KM.kindiffdict() 155 | rhs = KM.forcing_full 156 | MM = MM.subs(kdd) 157 | rhs = rhs.subs(kdd) 158 | 159 | MM.simplify() 160 | # MM = MM.subs(q0**2 + q1**2 + q2**2 + q3**2, 1) 161 | print() 162 | print('Mass Matrix') 163 | print('-----------') 164 | mprint(MM) 165 | 166 | rhs.simplify() 167 | # rhs = rhs.subs(q0**2 + q1**2 + q2**2 + q3**2, 1) 168 | print('Right Hand Side') 169 | print('---------------') 170 | mprint(rhs) 171 | print() 172 | 173 | # So, MM*x = rhs, where x is the State Derivatives 174 | # Solve for x 175 | stateDot = MM.inv()*rhs 176 | print('State Derivatives') 177 | print('-----------------------------------') 178 | mprint(stateDot) 179 | print() 180 | 181 | # POST-PROCESSING 182 | # --------------------------- 183 | 184 | # Useful Numbers 185 | # --------------------------- 186 | # p, q, r are the drone's angular velocities in the inertial frame, expressed in the drone's frame. 187 | # These calculations are not relevant to the ODE, but might be used for control. 188 | quat_dot_2 = Matrix([[q0d],[q1d],[q2d],[q3d]]) 189 | angVel_2 = 2.0*Gquat*quat_dot_2 190 | print('P, Q, R (Angular velocities in drone frame)') 191 | print('-------------------------------------------') 192 | mprint(angVel_2[0]) 193 | print() 194 | mprint(angVel_2[1]) 195 | print() 196 | mprint(angVel_2[2]) 197 | print() 198 | 199 | # u, v, w are the drone's velocities in the inertial frame, expressed in the drone's frame. 200 | # These calculations are not relevant to the ODE, but might be used for control. 201 | u = dot(Bcm.vel(N).subs(kdd), B.x).simplify() 202 | v = dot(Bcm.vel(N).subs(kdd), B.y).simplify() 203 | w = dot(Bcm.vel(N).subs(kdd), B.z).simplify() 204 | print('u, v, w (Velocities in drone frame)') 205 | print('-----------------------------------') 206 | mprint(u) 207 | print() 208 | mprint(v) 209 | print() 210 | mprint(w) 211 | print() 212 | 213 | # uFlat, vFlat, wFlat are the drone's velocities in the inertial frame, expressed in a frame 214 | # parallel to the ground, but with the drone's heading. 215 | # These calculations are not relevant to the ODE, but might be used for control. 216 | uFlat = dot(Bcm.vel(N).subs(kdd), cross(B.y, N.z)).simplify() 217 | vFlat = dot(Bcm.vel(N).subs(kdd), -cross(B.x, N.z)).simplify() 218 | wFlat = dot(Bcm.vel(N).subs(kdd), N.z).simplify() 219 | print('uFlat, vFlat, wFlat (Velocities in drone frame (after Yaw))') 220 | print('-----------------------------------------------------------') 221 | mprint(uFlat) 222 | print() 223 | mprint(vFlat) 224 | print() 225 | mprint(wFlat) 226 | print() 227 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quadcopter Simulation and Control (Quad_SimCon) 2 | 3 | [Quadcopter Exploration Project](https://github.com/bobzwik/Quad_Exploration) 4 | 5 | This project serves 2 purposes. The first is to provide a PyDy template for creating quadcopter dynamics and outputing the relevant equations of motion. 6 | 7 | The second purpose is to provide a simple working simulation of the quadcopter's dynamics and a simple controller that can handle position control and supports minimum snap (but also minimum velocity, acceleration and jerk) trajectory generation. 8 | 9 |
10 |
11 |
89 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |