From 2a0e0481713ca44802d74e9d3ba4a92f384d138f Mon Sep 17 00:00:00 2001 From: Gennaro Guidone <99129829+gguidone@users.noreply.github.com> Date: Fri, 26 Jun 2026 10:57:48 +0200 Subject: [PATCH] feat(mc_att_control): feedforward filtered q_d derivative on rate sp (#27255) Add a 2nd-order critically-damped attitude reference model whose angular-velocity output is fed forward onto the rate setpoint, removing the steady-state ramp-tracking lag of the pure-P attitude law. The model uses an exact closed-form discretisation, so it is unconditionally stable. Parameters: - MC_REF_W_N reference-model bandwidth [rad/s] - MC_REF_FF feedforward gain [0..1]; 0 disables the anticipation - MC_REF_FF_MAX per-axis feedforward saturation [deg/s] The commanded yaw rate is always fed forward at unity (it is a setpoint, not a model prediction), so MC_REF_FF scales only the error-driven anticipation and MC_REF_FF=0 is the exact legacy pure-P law. Shipped disabled by default to avoid changing behaviour on existing platforms; enable per-airframe. Includes docs and the updated controller diagram. --- docs/assets/diagrams/mc_angle_diagram.jpg | Bin 19872 -> 113097 bytes docs/assets/diagrams/mc_control_arch_tikz.tex | 31 ++- docs/en/flight_stack/controller_diagrams.md | 4 +- .../AttitudeControl/AttitudeControl.cpp | 120 +++++++- .../AttitudeControl/AttitudeControl.hpp | 48 +++- .../AttitudeControl/AttitudeControlTest.cpp | 261 ++++++++++++++++++ src/modules/mc_att_control/mc_att_control.hpp | 4 + .../mc_att_control/mc_att_control_main.cpp | 10 +- .../mc_att_control/mc_att_control_params.yaml | 37 +++ 9 files changed, 479 insertions(+), 36 deletions(-) diff --git a/docs/assets/diagrams/mc_angle_diagram.jpg b/docs/assets/diagrams/mc_angle_diagram.jpg index ac0f27d109130f1bb8cc25f546370a6a4ddd0c4b..6636999e05b1e8891777c9f589cf067d9edc03b3 100644 GIT binary patch literal 113097 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 St$`(}qW(vu^^N&)_5TCV9eA(+ diff --git a/docs/assets/diagrams/mc_control_arch_tikz.tex b/docs/assets/diagrams/mc_control_arch_tikz.tex index 548b88078e..9206a09137 100644 --- a/docs/assets/diagrams/mc_control_arch_tikz.tex +++ b/docs/assets/diagrams/mc_control_arch_tikz.tex @@ -248,29 +248,46 @@ \hspace{-0.7cm} %=============================================================================== - \node[text centered, yshift=0.25*\blockheight] (ref) {}; + \node[text centered] (ref) {}; + + % Reference model: 2nd-order critically-damped model (natural frequency MC_REF_W_N) + % that smooths the setpoint q_sp into the reference attitude q_ref tracked by the + % P law, and outputs the reference body rate omega_ref used as feedforward. + \path(ref)+(0.9*\nodesep,0) node (ref_model) [text_block] {Reference model}; % Controller - \path(ref)+(0.75*\nodesep,-0.25*\blockheight) node (err_quat) [text_block] {Error quaternion}; + \path(ref_model)+(1.5*\nodesep,0) node (err_quat) [text_block] {Error quaternion}; \path(err_quat)+(1.5*\nodesep,0) node (select_axis) [text_block] {Extract component}; \path(select_axis)+(0, 1.5*\blockheight) node (select_mag) [text_block] {Extract magnitude}; \path(select_mag)+(1.1*\nodesep,0) node (sgn) [simple_block] {sgn}; \path(select_axis)+(1.9*\nodesep, 0.25*\blockheight) node (mult) [simple_block] {$\times$}; \path(mult)+(0.65*\nodesep, 0) node (p_gain) [gain] {\scriptsize $2 P$}; - \path(p_gain)+(0.75*\nodesep, 0) node (sat_ctrl) [sat_block] {}; - \path(sat_ctrl)+(0.75*\nodesep, 0) node (output) [] {}; + % Summing junction: P-law rate + reference-model rate feedforward, BEFORE the final rate saturation + \path(p_gain)+(0.7*\nodesep, 0) node (sum) [draw, circle, inner sep=1.5pt] {$+$}; + \path(sum)+(0.7*\nodesep, 0) node (sat_ctrl) [sat_block] {}; + \path(sat_ctrl)+(0.7*\nodesep, 0) node (output) [] {}; + % omega_ref feedforward chain: gain (MC_REF_FF) then per-axis saturation (MC_REF_FF_MAX) + \path(sum)+(-0.7*\nodesep, -2.7*\blockheight) node (ff_sat) [sat_block] {}; + \path(ff_sat)+(-1.1*\nodesep, 0) node (ff_gain) [gain] {\scriptsize $k_\text{ff}$}; %=============================================================================== - \path[draw,->] (ref.east) -- node[text centered, anchor=south, pos=0.1] {$\bm{q}_\text{sp}$} ([yshift=0.25*\blockheight]err_quat.west); - \path[draw,->] ([yshift=-0.25*\blockheight]err_quat.west -| ref.east) -- node[text centered, anchor=north, pos=0.1] {$\bm{q}$} ([yshift=-0.25*\blockheight]err_quat.west); + \path[draw,->] (ref.east) -- node[text centered, anchor=south, pos=0.3] {$\bm{q}_\text{sp}$} (ref_model.west); + \path[draw,->] (ref_model.east) -- node[text centered, anchor=south] {$\bm{q}_\text{ref}$} (err_quat.west); + \path[draw,->] ([yshift=-0.75*\blockheight]err_quat.south) -- node[text centered, anchor=east, pos=0] {$\bm{q}$} (err_quat.south); \path[draw,->] (err_quat.east) -- node[text centered, anchor=south,pos=0.4] {$\bm{q}_\text{e}$} (select_axis.west); \path[draw,->]([xshift=0.3*\nodesep]err_quat.east) |- (select_mag.west); \path[draw,->] (select_mag.east) -- node[text centered, anchor=south] {$q_{0_\text{e}}$} (sgn.west); \path[draw,->] (sgn.east) -- ([xshift=0.15*\nodesep]sgn.east) |- ([yshift=0.25*\blockheight]mult.west); \path[draw,->] (select_axis.east) -- node[text centered, anchor=south, pos=0.45]{$q_{j_\text{e}}$} ([yshift=-0.25*\blockheight]mult.west); \path[draw,->] (mult.east) -- (p_gain.west); - \path[draw,->] (p_gain.east) -- (sat_ctrl.west); + \path[draw,->] (p_gain.east) -- (sum.west); + \path[draw,->] (sum.east) -- (sat_ctrl.west); \path[draw,->] (sat_ctrl.east) -- node[text centered, anchor=south] {$\Omega_\text{sp}$} (output.west); + % omega_ref feedforward: reference model -> gain -> saturation -> summing junction + \path[draw,->] (ref_model.south) |- (ff_gain.west); + \node[text centered, anchor=south] at (select_axis |- ff_gain) {$\bm{\omega}_\text{ref}$}; + \path[draw,->] (ff_gain.east) -- (ff_sat.west); + \path[draw,->] (ff_sat.east) -| (sum.south); \end{tikzpicture} \end{figure} diff --git a/docs/en/flight_stack/controller_diagrams.md b/docs/en/flight_stack/controller_diagrams.md index 6c3822f4bf..b8a52bd044 100644 --- a/docs/en/flight_stack/controller_diagrams.md +++ b/docs/en/flight_stack/controller_diagrams.md @@ -43,7 +43,9 @@ The diagrams use the standard [PX4 notation](../contribute/notation.md) (and eac - The attitude controller makes use of [quaternions](https://en.wikipedia.org/wiki/Quaternion). - The controller is implemented from this [article](https://www.research-collection.ethz.ch/bitstream/handle/20.500.11850/154099/eth-7387-01.pdf). -- When tuning this controller, the only parameter of concern is the P gain. +- The attitude setpoint is first smoothed by a 2nd-order critically-damped reference model ([MC_REF_W_N](../advanced_config/parameter_reference#MC_REF_W_N)); the P-law tracks its reference attitude, and the model's reference body rate is fed forward to the rate setpoint, removing the pure-P law's steady-state tracking lag. +- The feedforward is scaled by `MC_REF_FF` (`0` disables it), clipped per axis by `MC_REF_FF_MAX`, and suppressed during autotuning. +- Higher `MC_REF_W_N` or `MC_REF_FF` tracks more aggressively but demands more peak rate; the P gain remains the main tuning parameter. - The rate command is saturated. ### Multicopter Acceleration to Thrust and Attitude Setpoint Conversion diff --git a/src/modules/mc_att_control/AttitudeControl/AttitudeControl.cpp b/src/modules/mc_att_control/AttitudeControl/AttitudeControl.cpp index 02f3894f0e..54e7605038 100644 --- a/src/modules/mc_att_control/AttitudeControl/AttitudeControl.cpp +++ b/src/modules/mc_att_control/AttitudeControl/AttitudeControl.cpp @@ -41,6 +41,10 @@ using namespace matrix; +static __attribute__((noinline)) Quatf qmul(const Quatf &a, const Quatf &b) { return a * b; } +static __attribute__((noinline)) Quatf qinv(const Quatf &q) { return q.inversed(); } +static __attribute__((noinline)) Vector3f qzaxis(const Quatf &q) { return q.dcm_z(); } + void AttitudeControl::setProportionalGain(const matrix::Vector3f &proportional_gain, const float yaw_weight) { _proportional_gain = proportional_gain; @@ -52,13 +56,94 @@ void AttitudeControl::setProportionalGain(const matrix::Vector3f &proportional_g } } +void AttitudeControl::setRefModelFrequency(float omega_n) +{ + _omega_n = math::max(omega_n, 0.1f); + _kq = _omega_n * _omega_n; +} + +void AttitudeControl::setAttitudeSetpoint(const Quatf &qd, const float yawspeed_setpoint, const float dt) +{ + Quatf qd_normalized = qd; + qd_normalized.normalize(); + + if (_ref_initialized && dt > 0.f) { + propagateReferenceModel(qd_normalized, yawspeed_setpoint, dt); + + } else { + // First call (or dt out of range): snap reference to the current setpoint. + _q_ref = qd_normalized; + _omega_correction.zero(); + _omega_command.zero(); + _ref_initialized = true; + } +} + +void AttitudeControl::propagateReferenceModel(const Quatf &qd, const float yawspeed_setpoint, const float dt) +{ + // 2nd-order critically damped ref model with exact (ZOH) discretisation. + // Repeated eigenvalue at s = -_omega_n; unconditionally stable for any dt. + + // Tangent-space inputs: rotate the analytical yaw rate into q_ref's body + // frame, and form the small-angle error vector from q_ref to q_d. + const Quatf q_ref_inv = qinv(_q_ref); + const Vector3f yaw_axis_body = qzaxis(q_ref_inv); // world yaw axis expressed in q_ref's body frame + const Vector3f omega_command = PX4_ISFINITE(yawspeed_setpoint) + ? yaw_axis_body * yawspeed_setpoint + : Vector3f{}; + + Quatf q_err = qmul(q_ref_inv, qd); + q_err.canonicalize(); + const Vector3f e = 2.f * q_err.imag(); + + // Entries of exp(A*dt) for A = [0 -1; _kq -2*_omega_n]. A has the repeated eigenvalue lambda = -_omega_n. + // The matrix N = A - lambda*I is then nilpotent (a matrix is nilpotent when + // N^k = 0 for some k, and here N^2 = 0). Writing exp(A*dt) = e^(lambda*dt) * exp(N*dt) and expanding the + // series for exp(N*dt) = I + N*dt + (N*dt)^2/2! + ... , every term from (N*dt)^2 + // onward vanishes, so the exponential truncates to the exact closed form + // exp(A*dt) = e^(-_omega_n*dt) * [ (1 + _omega_n*dt) I + dt*A ] + // = emt * [ a -b ; gamma delta ]. + const float w_dt = _omega_n * dt; + const float emt = expf(-w_dt); + const float a = (1.f + w_dt) * emt; + const float b = dt * emt; + const float gamma = _kq * dt * emt; + const float delta = (1.f - w_dt) * emt; + + // Propagate the error-driven correction in tangent space (the 2nd-order state). delta_phi is the integral + // of omega over [0, dt]; the correction part collapses to e(0) - e(dt) since e_dot = -correction. + const Vector3f delta_phi = (1.f - a) * e + b * _omega_correction + omega_command * dt; + _omega_correction = gamma * e + delta * _omega_correction; + + // Yaw-rate command: the heading setpoint just follows the measured yaw, so feeding the error-driven + // rate forward closes a positive-feedback loop. Keep only the commanded rate (omega_command) on the yaw axis. + if (PX4_ISFINITE(yawspeed_setpoint) && (fabsf(yawspeed_setpoint) > FLT_EPSILON)) { + _omega_correction -= _omega_correction.dot(yaw_axis_body) * yaw_axis_body; + } + + // Commanded (analytical) reference rate, kept separate so update() can exempt it from the feedforward limit. + _omega_command = omega_command; + + _q_ref = qmul(_q_ref, Quatf(AxisAnglef(delta_phi))); + _q_ref.normalize(); +} + +void AttitudeControl::adaptAttitudeSetpoint(const Quatf &q_delta) +{ + // Apply the world-frame delta to the reference attitude. _omega_correction and _omega_command are + // in the reference body frame and physically invariant under a world relabeling. + _q_ref = qmul(q_delta, _q_ref); + _q_ref.normalize(); +} + matrix::Vector3f AttitudeControl::update(const Quatf &q) const { - Quatf qd = _attitude_setpoint_q; + // The P controller always tracks the reference-model attitude. + Quatf qd = _q_ref; // calculate reduced desired attitude neglecting vehicle's yaw to prioritize roll and pitch - const Vector3f e_z = q.dcm_z(); - const Vector3f e_z_d = qd.dcm_z(); + const Vector3f e_z = qzaxis(q); + const Vector3f e_z_d = qzaxis(qd); Quatf qd_red(e_z, e_z_d); if (fabsf(qd_red(1)) > (1.f - 1e-5f) || fabsf(qd_red(2)) > (1.f - 1e-5f)) { @@ -75,7 +160,7 @@ matrix::Vector3f AttitudeControl::update(const Quatf &q) const // With a full desired attitude given by: qd = qd_red * qd_dyaw, extract the delta yaw component. // By definition, the delta yaw quaternion has the form (cos(angle/2), 0, 0, sin(angle/2)) - Quatf qd_dyaw = qd_red.inversed() * qd; + Quatf qd_dyaw = qmul(qinv(qd_red), qd); qd_dyaw.canonicalize(); // catch numerical problems with the domain of acosf and asinf qd_dyaw(0) = math::constrain(qd_dyaw(0), -1.f, 1.f); @@ -85,7 +170,7 @@ matrix::Vector3f AttitudeControl::update(const Quatf &q) const qd = qd_red * Quatf(cosf(_yaw_w * acosf(qd_dyaw(0))), 0.f, 0.f, sinf(_yaw_w * asinf(qd_dyaw(3)))); // quaternion attitude control law, qe is rotation from q to qd - const Quatf qe = q.inversed() * qd; + const Quatf qe = qmul(qinv(q), qd); // using sin(alpha/2) scaled rotation axis as attitude error (see quaternion definition by axis angle) // also taking care of the antipodal unit quaternion ambiguity @@ -94,17 +179,24 @@ matrix::Vector3f AttitudeControl::update(const Quatf &q) const // calculate angular rates setpoint Vector3f rate_setpoint = eq.emult(_proportional_gain); - // Feed forward the yaw setpoint rate. - // yawspeed_setpoint is the feed forward commanded rotation around the world z-axis, - // but we need to apply it in the body frame (because _rates_sp is expressed in the body frame). - // Therefore we infer the world z-axis (expressed in the body frame) by taking the last column of R.transposed (== q.inversed) - // and multiply it by the yaw setpoint rate (yawspeed_setpoint). - // This yields a vector representing the commanded rotatation around the world z-axis expressed in the body frame - // such that it can be added to the rates setpoint. - if (std::isfinite(_yawspeed_setpoint)) { - rate_setpoint += q.inversed().dcm_z() * _yawspeed_setpoint; + // Map reference-frame rates into the current body frame. + const Quatf q_rel = qmul(qinv(q), _q_ref); + + // The commanded reference rate (e.g. manual/auto yaw rate) is a setpoint, not a model prediction, so it + // bypasses the reference model: it is fed forward at unity regardless of the feedforward gain and limit. + rate_setpoint += q_rel.rotateVector(_omega_command); + + // the gain scales and the limit caps the model's error-driven anticipation (zero at gain 0) + Vector3f omega_ff = _ff_gain * q_rel.rotateVector(_omega_correction); + + if (_ff_max > 0.f) { + for (int i = 0; i < 3; i++) { + omega_ff(i) = math::constrain(omega_ff(i), -_ff_max, _ff_max); + } } + rate_setpoint += omega_ff; + // limit rates for (int i = 0; i < 3; i++) { rate_setpoint(i) = math::constrain(rate_setpoint(i), -_rate_limit(i), _rate_limit(i)); diff --git a/src/modules/mc_att_control/AttitudeControl/AttitudeControl.hpp b/src/modules/mc_att_control/AttitudeControl/AttitudeControl.hpp index d90c623081..4124719435 100644 --- a/src/modules/mc_att_control/AttitudeControl/AttitudeControl.hpp +++ b/src/modules/mc_att_control/AttitudeControl/AttitudeControl.hpp @@ -64,6 +64,15 @@ public: */ void setProportionalGain(const matrix::Vector3f &proportional_gain, const float yaw_weight); + /// Set the critically damped reference-model natural frequency [rad/s]. + void setRefModelFrequency(float omega_n); + + /// Set the FF magnitude scaling [0..1] + void setFeedForwardGain(float gain) { _ff_gain = math::constrain(gain, 0.f, 1.f); } + + /// Set per-axis saturation on the FF angular-velocity contribution [rad/s]; 0 = disabled. + void setFeedForwardLimit(float limit) { _ff_max = math::max(limit, 0.f); } + /** * Set hard limit for output rate setpoints * @param rate_limit [rad/s] 3D vector containing limits for roll, pitch, yaw @@ -74,24 +83,16 @@ public: * Set a new attitude setpoint replacing the one tracked before * @param qd desired vehicle attitude setpoint * @param yawspeed_setpoint [rad/s] yaw feed forward angular rate in world frame + * @param dt [s] time since previous setpoint */ - void setAttitudeSetpoint(const matrix::Quatf &qd, const float yawspeed_setpoint) - { - _attitude_setpoint_q = qd; - _attitude_setpoint_q.normalize(); - _yawspeed_setpoint = yawspeed_setpoint; - } + void setAttitudeSetpoint(const matrix::Quatf &qd, const float yawspeed_setpoint, const float dt = -1.f); /** * Adjust last known attitude setpoint by a delta rotation * Optional use to avoid glitches when attitude estimate reference e.g. heading changes. * @param q_delta delta rotation to apply */ - void adaptAttitudeSetpoint(const matrix::Quatf &q_delta) - { - _attitude_setpoint_q = q_delta * _attitude_setpoint_q; - _attitude_setpoint_q.normalize(); - } + void adaptAttitudeSetpoint(const matrix::Quatf &q_delta); /** * Run one control loop cycle calculation @@ -100,11 +101,32 @@ public: */ matrix::Vector3f update(const matrix::Quatf &q) const; + /** + * Attitude state of the reference model + */ + const matrix::Quatf &getReferenceAttitude() const { return _q_ref; } + private: + /** + * Advance the 2nd-order reference model by one step toward the desired attitude + * @param qd normalized desired attitude setpoint + * @param yawspeed_setpoint [rad/s] yaw feed forward angular rate in world frame + * @param dt [s] time since previous setpoint (> 0) + */ + void propagateReferenceModel(const matrix::Quatf &qd, const float yawspeed_setpoint, const float dt); + matrix::Vector3f _proportional_gain; matrix::Vector3f _rate_limit; float _yaw_w{0.f}; ///< yaw weight [0,1] to deprioritize compared to roll and pitch - matrix::Quatf _attitude_setpoint_q; ///< latest known attitude setpoint e.g. from position control - float _yawspeed_setpoint{0.f}; ///< latest known yawspeed feed-forward setpoint + matrix::Quatf _q_ref; ///< reference attitude tracked by the 2nd-order ref model + matrix::Vector3f _omega_correction; ///< error-driven correction (2nd-order state); reference rate = _omega_correction + _omega_command + matrix::Vector3f _omega_command; ///< commanded (analytical) reference rate; exempt from the feedforward limit + bool _ref_initialized{false}; + + float _omega_n{50.f}; ///< ref-model natural frequency [rad/s] + float _kq{_omega_n * _omega_n}; ///< stiffness coefficient, kept in sync with _omega_n + + float _ff_gain{1.f}; + float _ff_max{0.f}; }; diff --git a/src/modules/mc_att_control/AttitudeControl/AttitudeControlTest.cpp b/src/modules/mc_att_control/AttitudeControl/AttitudeControlTest.cpp index 2803686c12..103e45b167 100644 --- a/src/modules/mc_att_control/AttitudeControl/AttitudeControlTest.cpp +++ b/src/modules/mc_att_control/AttitudeControl/AttitudeControlTest.cpp @@ -138,3 +138,264 @@ TEST(AttitudeControlTest, YawWeightScaling) // THEN: no actuation (also no NAN) EXPECT_EQ(rate_setpoint, Vector3f()); } + +class AttitudeControlFeedforwardTest : public ::testing::Test +{ +public: + AttitudeControlFeedforwardTest() + { + _attitude_control.setProportionalGain(Vector3f(6.5f, 6.5f, 2.8f), 0.4f); + _attitude_control.setRateLimit(Vector3f(10.f, 10.f, 10.f)); + _attitude_control.setRefModelFrequency(10.f); + } + + // Push a constant-rate ramp around the given body axis until the reference model is settled. + // First call uses dt < 0 to reset the model to the current sample (matches the wrapper's + // behaviour on the very first setpoint after boot). + Quatf rampSetpoint(const Vector3f &body_rate, float yawspeed_sp, int steps) + { + Quatf q_d; + + for (int i = 0; i < steps; i++) { + q_d = q_d * Quatf(AxisAnglef(body_rate * kDt)); + _attitude_control.setAttitudeSetpoint(q_d, yawspeed_sp, (i == 0) ? -1.f : kDt); + } + + return q_d; + } + + AttitudeControl _attitude_control; + + static constexpr float kDt = 0.004f; // 250 Hz setpoint rate + static constexpr int kSettleSteps = 500; // generous settling window for any default omega_n +}; + +TEST_F(AttitudeControlFeedforwardTest, ConstantSetpointGivesNoFeedforward) +{ + // GIVEN: a constant tilted setpoint repeated with valid dt + const Quatf q_d(AxisAnglef(Vector3f(0.1f, 0.f, 0.f))); + + for (int i = 0; i < kSettleSteps; i++) { + _attitude_control.setAttitudeSetpoint(q_d, 0.f, (i == 0) ? -1.f : kDt); + } + + // WHEN: vehicle is at the setpoint (no error) + const Vector3f rate_setpoint = _attitude_control.update(q_d); + + // THEN: rate setpoint is zero — non-moving SP gives a zero model rate output + EXPECT_NEAR(rate_setpoint.norm(), 0.f, 1e-3f); +} + +TEST_F(AttitudeControlFeedforwardTest, RollRampProducesRollFeedforward) +{ + // GIVEN: a steady roll ramp, reference model settled + const float omega = 0.5f; + rampSetpoint(Vector3f(omega, 0.f, 0.f), 0.f, kSettleSteps); + + // WHEN: vehicle is at the reference (no P error → P term = 0) + const Vector3f rate_setpoint = _attitude_control.update(_attitude_control.getReferenceAttitude()); + + // THEN: rate setpoint is the FF from omega_ref, which has converged to the ramp rate + EXPECT_NEAR(rate_setpoint(0), omega, 1e-3f); + EXPECT_NEAR(rate_setpoint(1), 0.f, 1e-3f); + EXPECT_NEAR(rate_setpoint(2), 0.f, 1e-3f); +} + +TEST_F(AttitudeControlFeedforwardTest, PitchRampProducesPitchFeedforward) +{ + // GIVEN: a steady pitch ramp, reference model settled + const float omega = 0.5f; + rampSetpoint(Vector3f(0.f, omega, 0.f), 0.f, kSettleSteps); + + const Vector3f rate_setpoint = _attitude_control.update(_attitude_control.getReferenceAttitude()); + + EXPECT_NEAR(rate_setpoint(0), 0.f, 1e-3f); + EXPECT_NEAR(rate_setpoint(1), omega, 1e-3f); + EXPECT_NEAR(rate_setpoint(2), 0.f, 1e-3f); +} + +TEST_F(AttitudeControlFeedforwardTest, HighRateRampStillExact) +{ + // GIVEN: a high-rate pitch ramp (~90 dps). Reference model steady-state holds at + // the ramp rate independent of magnitude (the model's equilibrium tracking is + // rate-invariant for any constant-rate command). + const float omega = 1.5708f; // ~90 dps + rampSetpoint(Vector3f(0.f, omega, 0.f), 0.f, kSettleSteps); + + const Vector3f rate_setpoint = _attitude_control.update(_attitude_control.getReferenceAttitude()); + + EXPECT_NEAR(rate_setpoint(0), 0.f, 1e-3f); + EXPECT_NEAR(rate_setpoint(1), omega, 1e-3f); + EXPECT_NEAR(rate_setpoint(2), 0.f, 1e-3f); +} + +TEST_F(AttitudeControlFeedforwardTest, YawRampOnlyAnalyticalFeedforwardContributes) +{ + // GIVEN: a yaw ramp with the analytical yawspeed setpoint matching. The reference + // model's damping is biased toward this known rate, so omega_ref settles to + // (0,0,omega) in q_ref's body frame and the FF reads out the body-z component. + const float omega = 0.5f; + rampSetpoint(Vector3f(0.f, 0.f, omega), omega, kSettleSteps); + + const Vector3f rate_setpoint = _attitude_control.update(_attitude_control.getReferenceAttitude()); + + EXPECT_NEAR(rate_setpoint(0), 0.f, 1e-3f); + EXPECT_NEAR(rate_setpoint(1), 0.f, 1e-3f); + EXPECT_NEAR(rate_setpoint(2), omega, 1e-3f); +} + +TEST_F(AttitudeControlFeedforwardTest, TiltedYawDoesNotDoubleCount) +{ + // GIVEN: body locked at constant tilt, yawing around world-z at constant rate. + // Truth body angular velocity: ω_body = R_BW · (0, 0, yaw_rate) + // = (-sin(tilt)·yaw_rate, 0, cos(tilt)·yaw_rate) + // The reference model bakes yaw_sp_move_rate into omega_ref via its damping bias, + // so the FF reproduces the body-frame projection of the world-z rotation — + // no separate analytical path, no double-count possible. + const float tilt = 0.5f; // ~28.6° pitch + const float yaw_rate = 0.5f; // ~28.6 dps + const Quatf q_pitch(AxisAnglef(Vector3f(0.f, tilt, 0.f))); + Quatf q_d; + + for (int i = 0; i < kSettleSteps; i++) { + const Quatf q_yaw(AxisAnglef(Vector3f(0.f, 0.f, yaw_rate * kDt * i))); + q_d = q_yaw * q_pitch; + _attitude_control.setAttitudeSetpoint(q_d, yaw_rate, (i == 0) ? -1.f : kDt); + } + + // WHEN: vehicle is at the reference attitude + const Vector3f rate_setpoint = _attitude_control.update(_attitude_control.getReferenceAttitude()); + + // THEN: rate setpoint matches the analytical world-z rotation in body frame + EXPECT_NEAR(rate_setpoint(0), -sinf(tilt) * yaw_rate, 1e-3f); + EXPECT_NEAR(rate_setpoint(1), 0.f, 1e-3f); + EXPECT_NEAR(rate_setpoint(2), cosf(tilt) * yaw_rate, 1e-3f); +} + +TEST_F(AttitudeControlFeedforwardTest, FeedForwardDisabledSuppressesContribution) +{ + // GIVEN: a settled roll-ramp reference + const float omega = 0.5f; + rampSetpoint(Vector3f(omega, 0.f, 0.f), 0.f, kSettleSteps); + + // WHEN: the feedforward gain is 0, evaluated at the reference attitude so the P term is zero + _attitude_control.setFeedForwardGain(0.f); + Vector3f rate_setpoint = _attitude_control.update(_attitude_control.getReferenceAttitude()); + + // THEN: the anticipation feedforward is fully suppressed + EXPECT_NEAR(rate_setpoint.norm(), 0.f, 1e-3f); + + // AND WHEN: the gain is restored, the anticipation returns (reference model preserved) + _attitude_control.setFeedForwardGain(1.f); + rate_setpoint = _attitude_control.update(_attitude_control.getReferenceAttitude()); + + EXPECT_NEAR(rate_setpoint(0), omega, 1e-3f); + EXPECT_NEAR(rate_setpoint(1), 0.f, 1e-3f); + EXPECT_NEAR(rate_setpoint(2), 0.f, 1e-3f); +} + +TEST_F(AttitudeControlFeedforwardTest, UnlockedYawDoesNotFeedBackSlavedHeading) +{ + // GIVEN: the manual yaw-rate regime. StickYaw slaves the setpoint heading to the measured yaw, + // so q_d.yaw ramps at the (large) vehicle yaw rate while the commanded analytical rate is only + // the small decaying filter tail. Differentiating the slaved heading would latch the FF onto the + // measured rate (the uncommanded-yaw runaway). Reproduce: q_d.yaw ramps fast, yawspeed_sp small. + const float ramp_rate = 0.8f; // slaved-heading rate (≈ measured yaw rate) + const float commanded = 0.05f; // small but > FLT_EPSILON: yaw stays "unlocked" + rampSetpoint(Vector3f(0.f, 0.f, ramp_rate), commanded, kSettleSteps); + + const Vector3f rate_setpoint = _attitude_control.update(_attitude_control.getReferenceAttitude()); + + // THEN: yaw FF tracks only the analytical commanded rate, NOT the slaved-heading ramp (no latch) + EXPECT_NEAR(rate_setpoint(2), commanded, 1e-3f); + EXPECT_LT(fabsf(rate_setpoint(2)), 0.2f); + EXPECT_NEAR(rate_setpoint(0), 0.f, 1e-3f); + EXPECT_NEAR(rate_setpoint(1), 0.f, 1e-3f); +} + +TEST_F(AttitudeControlFeedforwardTest, LockedYawRampStillFeedsForward) +{ + // GIVEN: a genuine yaw-angle target slewed with no commanded rate (heading-hold / auto yaw). + // yawspeed_sp = 0 → the heading is exogenous, not slaved, so the full reference-model FF must + // still differentiate it. This guards against over-gating killing the legitimate yaw FF. + const float omega = 0.5f; + rampSetpoint(Vector3f(0.f, 0.f, omega), 0.f, kSettleSteps); + + const Vector3f rate_setpoint = _attitude_control.update(_attitude_control.getReferenceAttitude()); + + EXPECT_NEAR(rate_setpoint(2), omega, 1e-3f); + EXPECT_NEAR(rate_setpoint(0), 0.f, 1e-3f); + EXPECT_NEAR(rate_setpoint(1), 0.f, 1e-3f); +} + +TEST_F(AttitudeControlFeedforwardTest, TiltedUnlockedYawUsesCommandedRateOnly) +{ + // GIVEN: tilted while the slaved heading ramps fast (q_d.yaw := measured) but the commanded + // analytical rate is small — the tilted form of the uncommanded-yaw runaway. + const float tilt = 0.5f; + const float slaved_rate = 0.8f; // q_d.yaw ramp (≈ measured yaw rate) + const float commanded = 0.05f; // small but > FLT_EPSILON: yaw stays "unlocked" + const Quatf q_pitch(AxisAnglef(Vector3f(0.f, tilt, 0.f))); + Quatf q_d; + + for (int i = 0; i < kSettleSteps; i++) { + const Quatf q_yaw(AxisAnglef(Vector3f(0.f, 0.f, slaved_rate * kDt * i))); + q_d = q_yaw * q_pitch; + _attitude_control.setAttitudeSetpoint(q_d, commanded, (i == 0) ? -1.f : kDt); + } + + const Vector3f rate_setpoint = _attitude_control.update(_attitude_control.getReferenceAttitude()); + + // THEN: FF is the commanded world-z rate projected into the body frame (not the slaved ramp), + // with no roll/pitch leak from the stripped heading error. + EXPECT_NEAR(rate_setpoint(0), -sinf(tilt) * commanded, 1e-3f); + EXPECT_NEAR(rate_setpoint(1), 0.f, 1e-3f); + EXPECT_NEAR(rate_setpoint(2), cosf(tilt) * commanded, 1e-3f); +} + +TEST_F(AttitudeControlFeedforwardTest, CommandedYawRateFedForwardWhenFFDisabled) +{ + // GIVEN: a settled manual yaw-rate command (heading slaved to measurement, small commanded rate) + const float commanded = 0.05f; + rampSetpoint(Vector3f(0.f, 0.f, 0.8f), commanded, kSettleSteps); + + // WHEN: the feedforward gain is 0, evaluated at the reference attitude so the P term is zero + _attitude_control.setFeedForwardGain(0.f); + const Vector3f rate_setpoint = _attitude_control.update(_attitude_control.getReferenceAttitude()); + + // THEN: the commanded yaw rate still bypasses the ref model and is fed forward at unity + EXPECT_NEAR(rate_setpoint(2), commanded, 1e-3f); + EXPECT_NEAR(rate_setpoint(0), 0.f, 1e-3f); + EXPECT_NEAR(rate_setpoint(1), 0.f, 1e-3f); +} + +TEST_F(AttitudeControlFeedforwardTest, FractionalGainDoesNotWeakenCommandedYaw) +{ + // GIVEN: a manual yaw-rate command with the anticipation gain detuned to 0.1 + const float commanded = 0.05f; + _attitude_control.setFeedForwardGain(0.1f); + rampSetpoint(Vector3f(0.f, 0.f, 0.8f), commanded, kSettleSteps); + + // Evaluate at the reference attitude so the P term is zero; the only yaw contribution is the FF. + const Vector3f rate_setpoint = _attitude_control.update(_attitude_control.getReferenceAttitude()); + + // THEN: commanded yaw authority is preserved at full strength (NOT scaled to 0.1 x commanded). + EXPECT_NEAR(rate_setpoint(2), commanded, 1e-3f); + EXPECT_NEAR(rate_setpoint(0), 0.f, 1e-3f); + EXPECT_NEAR(rate_setpoint(1), 0.f, 1e-3f); +} + +TEST_F(AttitudeControlFeedforwardTest, FractionalGainScalesAnticipation) +{ + // GIVEN: a settled roll-ramp reference (pure error-driven anticipation, no commanded rate), gain 0.1 + const float omega = 0.5f; + _attitude_control.setFeedForwardGain(0.1f); + rampSetpoint(Vector3f(omega, 0.f, 0.f), 0.f, kSettleSteps); + + const Vector3f rate_setpoint = _attitude_control.update(_attitude_control.getReferenceAttitude()); + + // THEN: the anticipation IS scaled by the gain (0.1 x the settled reference rate), unlike the command. + EXPECT_NEAR(rate_setpoint(0), 0.1f * omega, 1e-3f); + EXPECT_NEAR(rate_setpoint(1), 0.f, 1e-3f); + EXPECT_NEAR(rate_setpoint(2), 0.f, 1e-3f); +} diff --git a/src/modules/mc_att_control/mc_att_control.hpp b/src/modules/mc_att_control/mc_att_control.hpp index 3f5e6e17f8..b6e9b066d9 100644 --- a/src/modules/mc_att_control/mc_att_control.hpp +++ b/src/modules/mc_att_control/mc_att_control.hpp @@ -161,6 +161,10 @@ private: (ParamFloat) _param_mc_pitchrate_max, (ParamFloat) _param_mc_yawrate_max, + (ParamFloat) _param_mc_ref_w_n, + (ParamFloat) _param_mc_ref_ff, + (ParamFloat) _param_mc_ref_ff_max, + /* Stabilized mode params */ (ParamFloat) _param_man_deadzone, (ParamFloat) _param_mpc_man_tilt_max, diff --git a/src/modules/mc_att_control/mc_att_control_main.cpp b/src/modules/mc_att_control/mc_att_control_main.cpp index 1b7b2ec2fa..b573de9d3a 100644 --- a/src/modules/mc_att_control/mc_att_control_main.cpp +++ b/src/modules/mc_att_control/mc_att_control_main.cpp @@ -101,6 +101,10 @@ MulticopterAttitudeControl::parameters_updated() _attitude_control.setRateLimit(Vector3f(radians(_param_mc_rollrate_max.get()), radians(_param_mc_pitchrate_max.get()), radians(_param_mc_yawrate_max.get()))); + _attitude_control.setRefModelFrequency(_param_mc_ref_w_n.get()); + _attitude_control.setFeedForwardGain(_param_mc_ref_ff.get()); + _attitude_control.setFeedForwardLimit(math::radians(_param_mc_ref_ff_max.get())); + // Update from hover thrust parameter if there's no valid estimate in use if (!PX4_ISFINITE(_hover_thrust_estimate)) { _hover_thrust_slew_rate.setForcedValue(_param_mpc_thr_hover.get()); @@ -316,7 +320,11 @@ MulticopterAttitudeControl::Run() if (_vehicle_attitude_setpoint_sub.copy(&vehicle_attitude_setpoint) && (vehicle_attitude_setpoint.timestamp > _last_attitude_setpoint)) { - _attitude_control.setAttitudeSetpoint(Quatf(vehicle_attitude_setpoint.q_d), vehicle_attitude_setpoint.yaw_sp_move_rate); + const float setpoint_dt = (_last_attitude_setpoint > 0) + ? (vehicle_attitude_setpoint.timestamp - _last_attitude_setpoint) * 1e-6f + : -1.f; + _attitude_control.setAttitudeSetpoint(Quatf(vehicle_attitude_setpoint.q_d), + vehicle_attitude_setpoint.yaw_sp_move_rate, setpoint_dt); _thrust_setpoint_body = Vector3f(vehicle_attitude_setpoint.thrust_body); _last_attitude_setpoint = vehicle_attitude_setpoint.timestamp; } diff --git a/src/modules/mc_att_control/mc_att_control_params.yaml b/src/modules/mc_att_control/mc_att_control_params.yaml index 00def08bc9..e9e43018a6 100644 --- a/src/modules/mc_att_control/mc_att_control_params.yaml +++ b/src/modules/mc_att_control/mc_att_control_params.yaml @@ -95,6 +95,43 @@ parameters: max: 1800.0 decimal: 1 increment: 5 + MC_REF_W_N: + description: + short: Attitude reference-model natural frequency + long: |- + Bandwidth of the reference model that smooths the attitude setpoint + and generates the rate feed-forward. Higher = less lag, more peak + rate demand. + type: float + default: 50.0 + unit: rad/s + min: 1.0 + max: 200.0 + decimal: 0 + increment: 1 + MC_REF_FF: + description: + short: Attitude reference-model feed-forward gain + long: |- + Scale on the reference-model rate feed-forward. 0 disables it. + type: float + default: 0.0 + min: 0.0 + max: 1.0 + decimal: 2 + increment: 0.01 + MC_REF_FF_MAX: + description: + short: Feed-forward angular-rate cap; 0 = disabled + long: |- + Per-axis cap on the rate feed-forward. + type: float + default: 100.0 + unit: deg/s + min: 0.0 + max: 1800.0 + decimal: 0 + increment: 5 - group: Multicopter Position Control definitions: MC_MAN_TILT_TAU: