Skip to content

Commit 42669d9

Browse files
Zafferclaude
andcommitted
Fix gyroscopic precession sign: roll term, not pitch
The propeller gyroscopic torque had the wrong sign on the ROLL axis (row 0 of torque_inertia), in both dynamics (numeric) and symbolic_dynamics. The gyroscopic torque is -ω × h with net rotor angular momentum h = h_z·ẑ, giving (-q·h_z, +p·h_z, 0): the roll and pitch rows must have OPPOSITE signs. The subtlety is that S = Σ mixing_matrix[-1]·ω_rotor uses the yaw-torque-direction row, i.e. the NEGATIVE of the physical spin, so h_z = -prop_inertia·S. Hence: roll (x) = -q·h_z = +prop_inertia·ang_vel[1]·S (code had a leading '-', the bug) pitch (y) = +p·h_z = -prop_inertia·ang_vel[0]·S (code was already correct) The z (spin-up/down reaction) row is already correct, and is in fact what pins the convention: it is consistent only with spin = -mixing_matrix[-1]. Verified against -ω × h to machine precision on all axes; numeric == symbolic still holds for ang_vel_dot. Only matters for non-negligible prop_inertia / larger quads during yaw-type maneuvers; negligible for a Crazyflie. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent d60abc7 commit 42669d9

1 file changed

Lines changed: 13 additions & 2 deletions

File tree

crazyflow/dynamics/first_principles/dynamics.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,17 @@ def dynamics(
139139
rotor_vel_dot_rads = (
140140
rotor_vel_dot * rpm_to_rad if rotor_vel_dot is not None else xp.zeros_like(rotor_vel)
141141
)
142+
# Gyroscopic torque -ω × h, with net rotor angular momentum h = h_z·ẑ.
143+
# -ω × (0,0,h_z) = (-q·h_z, +p·h_z, 0): the roll and pitch rows have OPPOSITE signs.
144+
# Here S = Σ mixing_matrix[-1]·ω_rotor is the yaw-torque-direction sum, i.e. the NEGATIVE of
145+
# the physical spin direction (same row used, correctly, for the drag reaction torque and the
146+
# z row below). Hence h_z = -prop_inertia·S, and substituting:
147+
# roll (x) = -q·h_z = +prop_inertia·ang_vel[1]·S
148+
# pitch (y) = +p·h_z = -prop_inertia·ang_vel[0]·S
149+
# The z row is the spin-up/down reaction torque.
142150
torque_inertia = prop_inertia * xp.stack(
143151
[
144-
-ang_vel[..., 1] * xp.sum(mixing_matrix[..., -1, :] * rotor_vel_rads, axis=-1),
152+
ang_vel[..., 1] * xp.sum(mixing_matrix[..., -1, :] * rotor_vel_rads, axis=-1),
145153
-ang_vel[..., 0] * xp.sum(mixing_matrix[..., -1, :] * rotor_vel_rads, axis=-1),
146154
xp.sum(mixing_matrix[..., -1, :] * rotor_vel_dot_rads, axis=-1),
147155
],
@@ -260,8 +268,11 @@ def symbolic_dynamics(
260268
rpm_to_rad = 2 * cs.pi / 60
261269
rotor_vel_rads = symbols.rotor_vel * rpm_to_rad
262270
rotor_vel_dot_rads = rotor_vel_dot * rpm_to_rad if model_rotor_vel else symbols.rotor_vel * 0.0
271+
# Gyroscopic torque -ω × h (see dynamics() for the full derivation): the roll row is
272+
# +ang_vel[1]·S and the pitch row is -ang_vel[0]·S (opposite signs), where S uses the
273+
# yaw-torque-direction row (= -physical spin), so h_z = -prop_inertia·S.
263274
torque_inertia = prop_inertia * cs.vertcat(
264-
-symbols.ang_vel[1] * cs.sum(mixing_matrix[-1, :] * rotor_vel_rads),
275+
symbols.ang_vel[1] * cs.sum(mixing_matrix[-1, :] * rotor_vel_rads),
265276
-symbols.ang_vel[0] * cs.sum(mixing_matrix[-1, :] * rotor_vel_rads),
266277
cs.sum(mixing_matrix[-1, :] * rotor_vel_dot_rads),
267278
)

0 commit comments

Comments
 (0)