LCOV - code coverage report
Current view: top level - AC_AttitudeControl - AC_AttitudeControl.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 489 608 80.4 %
Date: 2026-09-09 11:02:08 Functions: 48 53 90.6 %

          Line data    Source code
       1             : #include "AC_AttitudeControl.h"
       2             : #include <AP_HAL/AP_HAL.h>
       3             : #include <AP_Vehicle/AP_Vehicle.h>
       4             : #include <AP_Scheduler/AP_Scheduler.h>
       5             : #include <AP_Vehicle/AP_Vehicle_Type.h>
       6             : 
       7             : extern const AP_HAL::HAL& hal;
       8             : 
       9             : #if APM_BUILD_TYPE(APM_BUILD_ArduPlane)
      10             :  // default gains for Plane
      11             :  # define AC_ATTITUDE_CONTROL_INPUT_TC_DEFAULT  0.15f    // Medium
      12             :  #define AC_ATTITUDE_CONTROL_ANGLE_LIMIT_MIN     5.0     // Min lean angle so that vehicle can maintain limited control
      13             :  #define AC_ATTITUDE_CONTROL_AFTER_RATE_CONTROL 0
      14             : #else
      15             :  // default gains for Copter and Sub
      16             :  # define AC_ATTITUDE_CONTROL_INPUT_TC_DEFAULT  0.10f   // Crisp
      17             :  #define AC_ATTITUDE_CONTROL_ANGLE_LIMIT_MIN     10.0   // Min lean angle so that vehicle can maintain limited control
      18             :  #define AC_ATTITUDE_CONTROL_AFTER_RATE_CONTROL 1
      19             : #endif
      20             : 
      21             : // lean angle max (in degrees) for all vehicles
      22             : #define AC_ATTITUDE_CONTROL_ANGLE_LIMIT_MAX 80.0        // lean angle max in degrees
      23             : 
      24             : // default angle max for all vehicles
      25             : #ifndef AC_ATTITUDE_CONTROL_ANGLE_MAX_DEFAULT
      26             :  # define AC_ATTITUDE_CONTROL_ANGLE_MAX_DEFAULT 30.0f   // default max lean angle in degrees
      27             : #endif
      28             : 
      29             : AC_AttitudeControl *AC_AttitudeControl::_singleton;
      30             : 
      31             : // table of user settable parameters
      32             : const AP_Param::GroupInfo AC_AttitudeControl::var_info[] = {
      33             : 
      34             :     // 0, 1 were RATE_RP_MAX, RATE_Y_MAX
      35             : 
      36             :     // 2 was SLEW_YAW (in cdeg/s) - moved to RATE_WPY_MAX in deg/s
      37             : 
      38             :     // 3 was for ACCEL_RP_MAX
      39             : 
      40             :     // 4 was ACCEL_Y_MAX (in cdeg/s/s) - moved to ACC_Y_MAX in deg/s/s
      41             : 
      42             :     // @Param: RATE_FF_ENAB
      43             :     // @DisplayName: Rate Feedforward Enable
      44             :     // @Description: Controls whether body-frame rate feedforward is enabled or disabled
      45             :     // @Values: 0:Disabled, 1:Enabled
      46             :     // @User: Advanced
      47             :     AP_GROUPINFO("RATE_FF_ENAB", 5, AC_AttitudeControl, _rate_bf_ff_enabled, AC_ATTITUDE_CONTROL_RATE_BF_FF_DEFAULT),
      48             : 
      49             :     // 6 was ACCEL_R_MAX (in cdeg/s/s) - moved to ACC_R_MAX in deg/s/s
      50             : 
      51             :     // 7 was ACCEL_P_MAX (in cdeg/s/s) - moved to ACC_P_MAX in deg/s/s
      52             : 
      53             :     // IDs 8,9,10,11 RESERVED (in use on Solo)
      54             : 
      55             :     // @Param: ANGLE_BOOST
      56             :     // @DisplayName: Angle Boost
      57             :     // @Description: Angle Boost increases output throttle as the vehicle leans to reduce loss of altitude
      58             :     // @Values: 0:Disabled, 1:Enabled
      59             :     // @User: Advanced
      60             :     AP_GROUPINFO("ANGLE_BOOST", 12, AC_AttitudeControl, _angle_boost_enabled, 1),
      61             : 
      62             :     // @Param: ANG_RLL_P
      63             :     // @DisplayName: Roll axis angle controller P gain
      64             :     // @Description: Roll axis angle controller P gain.  Converts the error between the desired roll angle and actual angle to a desired roll rate
      65             :     // @Range: 3.000 12.000
      66             :     // @Range{Sub}: 0.0 12.000
      67             :     // @Increment: 0.01
      68             :     // @User: Standard
      69             :     AP_SUBGROUPINFO(_p_angle_roll, "ANG_RLL_", 13, AC_AttitudeControl, AC_P),
      70             : 
      71             :     // @Param: ANG_PIT_P
      72             :     // @DisplayName: Pitch axis angle controller P gain
      73             :     // @Description: Pitch axis angle controller P gain.  Converts the error between the desired pitch angle and actual angle to a desired pitch rate
      74             :     // @Range: 3.000 12.000
      75             :     // @Range{Sub}: 0.0 12.000
      76             :     // @Increment: 0.01
      77             :     // @User: Standard
      78             :     AP_SUBGROUPINFO(_p_angle_pitch, "ANG_PIT_", 14, AC_AttitudeControl, AC_P),
      79             : 
      80             :     // @Param: ANG_YAW_P
      81             :     // @DisplayName: Yaw axis angle controller P gain
      82             :     // @Description: Yaw axis angle controller P gain.  Converts the error between the desired yaw angle and actual angle to a desired yaw rate
      83             :     // @Range: 3.000 12.000
      84             :     // @Range{Sub}: 0.0 6.000
      85             :     // @Increment: 0.01
      86             :     // @User: Standard
      87             :     AP_SUBGROUPINFO(_p_angle_yaw, "ANG_YAW_", 15, AC_AttitudeControl, AC_P),
      88             : 
      89             :     // @Param: ANG_LIM_TC
      90             :     // @DisplayName: Angle Limit (to maintain altitude) Time Constant
      91             :     // @Description: Angle Limit (to maintain altitude) Time Constant
      92             :     // @Range: 0.5 10.0
      93             :     // @User: Advanced
      94             :     AP_GROUPINFO("ANG_LIM_TC", 16, AC_AttitudeControl, _angle_limit_tc, AC_ATTITUDE_CONTROL_ANGLE_LIMIT_TC_DEFAULT),
      95             : 
      96             :     // @Param: RATE_R_MAX
      97             :     // @DisplayName: Angular Velocity Max for Roll
      98             :     // @Description: Maximum angular velocity in roll axis
      99             :     // @Units: deg/s
     100             :     // @Range: 0 1080
     101             :     // @Increment: 1
     102             :     // @Values: 0:Disabled, 60:Slow, 180:Medium, 360:Fast
     103             :     // @User: Advanced
     104             :     AP_GROUPINFO("RATE_R_MAX", 17, AC_AttitudeControl, _ang_vel_roll_max_degs, 0.0f),
     105             : 
     106             :     // @Param: RATE_P_MAX
     107             :     // @DisplayName: Angular Velocity Max for Pitch
     108             :     // @Description: Maximum angular velocity in pitch axis
     109             :     // @Units: deg/s
     110             :     // @Range: 0 1080
     111             :     // @Increment: 1
     112             :     // @Values: 0:Disabled, 60:Slow, 180:Medium, 360:Fast
     113             :     // @User: Advanced
     114             :     AP_GROUPINFO("RATE_P_MAX", 18, AC_AttitudeControl, _ang_vel_pitch_max_degs, 0.0f),
     115             : 
     116             :     // @Param: RATE_Y_MAX
     117             :     // @DisplayName: Angular Velocity Max for Yaw
     118             :     // @Description: Maximum angular velocity in yaw axis
     119             :     // @Units: deg/s
     120             :     // @Range: 0 1080
     121             :     // @Increment: 1
     122             :     // @Values: 0:Disabled, 60:Slow, 180:Medium, 360:Fast
     123             :     // @User: Advanced
     124             :     AP_GROUPINFO("RATE_Y_MAX", 19, AC_AttitudeControl, _ang_vel_yaw_max_degs, 0.0f),
     125             : 
     126             :     // @Param: INPUT_TC
     127             :     // @DisplayName: Attitude control input time constant
     128             :     // @Description: Attitude control input time constant.  Low numbers lead to sharper response, higher numbers to softer response
     129             :     // @Units: s
     130             :     // @Range: 0 1
     131             :     // @Increment: 0.01
     132             :     // @Values: 0.5:Very Soft, 0.2:Soft, 0.15:Medium, 0.1:Crisp, 0.05:Very Crisp
     133             :     // @User: Standard
     134             :     AP_GROUPINFO("INPUT_TC", 20, AC_AttitudeControl, _input_tc, AC_ATTITUDE_CONTROL_INPUT_TC_DEFAULT),
     135             : 
     136             :     // @Param: LAND_R_MULT
     137             :     // @DisplayName: Landed roll gain multiplier
     138             :     // @Description: Roll gain multiplier active when landed. A factor of 1.0 means no reduction in gain while landed. Reduce this factor to reduce ground oscitation in the roll axis. 
     139             :     // @Range: 0.25 1.0
     140             :     // @User: Advanced
     141             :     AP_GROUPINFO("LAND_R_MULT", 21, AC_AttitudeControl, _land_roll_mult, 1.0),
     142             : 
     143             :     // @Param: LAND_P_MULT
     144             :     // @DisplayName: Landed pitch gain multiplier
     145             :     // @Description: Pitch gain multiplier active when landed. A factor of 1.0 means no reduction in gain while landed. Reduce this factor to reduce ground oscitation in the pitch axis. 
     146             :     // @Range: 0.25 1.0
     147             :     // @User: Advanced
     148             :     AP_GROUPINFO("LAND_P_MULT", 22, AC_AttitudeControl, _land_pitch_mult, 1.0),
     149             : 
     150             :     // @Param: LAND_Y_MULT
     151             :     // @DisplayName: Landed yaw gain multiplier
     152             :     // @Description: Yaw gain multiplier active when landed. A factor of 1.0 means no reduction in gain while landed. Reduce this factor to reduce ground oscitation in the yaw axis. 
     153             :     // @Range: 0.25 1.0
     154             :     // @User: Advanced
     155             :     AP_GROUPINFO("LAND_Y_MULT", 23, AC_AttitudeControl, _land_yaw_mult, 1.0),
     156             : 
     157             :     // @Param: ANGLE_MAX
     158             :     // @DisplayName: Angle Max
     159             :     // @Description: Maximum lean angle in all flight modes
     160             :     // @Units: deg
     161             :     // @Increment: 0.1
     162             :     // @Range: 10.0 80.0
     163             :     // @User: Standard
     164             :     AP_GROUPINFO("ANGLE_MAX", 24, AC_AttitudeControl, _angle_max_deg, AC_ATTITUDE_CONTROL_ANGLE_MAX_DEFAULT),
     165             : 
     166             :     // @Param: RATE_WPY_MAX
     167             :     // @DisplayName: Yaw target slew rate
     168             :     // @Description: Maximum rate the yaw target can be updated in Auto, Guided, Circle, Follow, RTL, SmartRTL, Throw and ZigZag flight modes
     169             :     // @Units: deg/s
     170             :     // @Range: 5 180
     171             :     // @Increment: 1
     172             :     // @User: Advanced
     173             :     AP_GROUPINFO("RATE_WPY_MAX", 25, AC_AttitudeControl, _rate_wp_yaw_max_degs, AC_ATTITUDE_CONTROL_RATE_WPY_MAX_DEFAULT),
     174             : 
     175             :     // @Param: ACC_Y_MAX
     176             :     // @DisplayName: Acceleration Max for Yaw
     177             :     // @Description: Maximum acceleration in yaw axis
     178             :     // @Units: deg/s/s
     179             :     // @Range: 0 720
     180             :     // @Values: 0:Disabled, 90:VerySlow, 180:Slow, 360:Medium, 540:Fast
     181             :     // @Increment: 10
     182             :     // @User: Advanced
     183             :     AP_GROUPINFO("ACC_Y_MAX", 26, AC_AttitudeControl, _accel_yaw_max_degss, AC_ATTITUDE_CONTROL_ACCEL_Y_MAX_DEFAULT_DEGSS),
     184             : 
     185             :     // @Param: ACC_R_MAX
     186             :     // @DisplayName: Acceleration Max for Roll
     187             :     // @Description: Maximum acceleration in roll axis
     188             :     // @Units: deg/s/s
     189             :     // @Range: 0 1800
     190             :     // @Increment: 10
     191             :     // @Values: 0:Disabled, 300:VerySlow, 720:Slow, 1080:Medium, 1620:Fast
     192             :     // @User: Advanced
     193             :     AP_GROUPINFO("ACC_R_MAX", 27, AC_AttitudeControl, _accel_roll_max_degss, AC_ATTITUDE_CONTROL_ACCEL_RP_MAX_DEFAULT_DEGSS),
     194             : 
     195             :     // @Param: ACC_P_MAX
     196             :     // @DisplayName: Acceleration Max for Pitch
     197             :     // @Description: Maximum acceleration in pitch axis
     198             :     // @Units: deg/s/s
     199             :     // @Range: 0 1800
     200             :     // @Increment: 10
     201             :     // @Values: 0:Disabled, 300:VerySlow, 720:Slow, 1080:Medium, 1620:Fast
     202             :     // @User: Advanced
     203             :     AP_GROUPINFO("ACC_P_MAX", 28, AC_AttitudeControl, _accel_pitch_max_degss, AC_ATTITUDE_CONTROL_ACCEL_RP_MAX_DEFAULT_DEGSS),
     204             : 
     205             :     AP_GROUPEND
     206             : };
     207             : 
     208             : constexpr Vector3f AC_AttitudeControl::VECTORF_111;
     209             : 
     210             : // get the slew yaw rate limit in rad/s
     211    14797186 : float AC_AttitudeControl::get_slew_yaw_max_rads() const
     212             : {
     213    14797186 :     if (!is_positive(_ang_vel_yaw_max_degs)) {
     214    13668447 :         return radians(_rate_wp_yaw_max_degs);
     215             :     }
     216     1128739 :     return MIN(radians(_ang_vel_yaw_max_degs), radians(_rate_wp_yaw_max_degs));
     217             : }
     218             : 
     219             : // get the latest gyro for the purposes of attitude control
     220             : // Counter-inuitively the lowest latency for rate control is achieved by running rate control
     221             : // *before* attitude control. This is because you want rate control to run as close as possible
     222             : // to the time that a gyro sample was read to minimise jitter and control errors. Running rate
     223             : // control after attitude control might makes sense logically, but the overhead of attitude
     224             : // control calculations (and other updates) compromises the actual rate control.
     225             : //
     226             : // In the case of running rate control in a separate thread, the ordering between rate and attitude
     227             : // updates is less important, except that gyro sample used should be the latest
     228             : //
     229             : // Currently quadplane runs rate control after attitude control, necessitating the following code
     230             : // to minimise latency.
     231             : // However this code can be removed once quadplane updates it's structure to run the rate loops before
     232             : // the Attitude controller.
     233    35665694 : const Vector3f AC_AttitudeControl::get_latest_gyro() const
     234             : {
     235             : #if AC_ATTITUDE_CONTROL_AFTER_RATE_CONTROL
     236             :     // rate updates happen before attitude updates so the last gyro value is the last rate gyro value
     237             :     // this also allows a separate rate thread to be the source of gyro data
     238    31619119 :     return _rate_gyro_rads;
     239             : #else
     240             :     // rate updates happen after attitude updates so the AHRS must be consulted for the last gyro value
     241     4046575 :     return _ahrs.get_gyro_latest();
     242             : #endif
     243             : }
     244             : 
     245             : // Ensure attitude controller have zero errors to relax rate controller output
     246     2709520 : void AC_AttitudeControl::relax_attitude_controllers()
     247             : {
     248             :     // take a copy of the last gyro used by the rate controller before using it
     249     2709520 :     Vector3f gyro = get_latest_gyro();
     250             :     // Initialize the attitude variables to the current attitude
     251     2709520 :     _ahrs.get_quat_body_to_ned(_attitude_target);
     252     2709520 :     _attitude_target.to_euler(_euler_angle_target_rad);
     253     2709520 :     _attitude_ang_error.initialise();
     254             : 
     255             :     // Initialize the angular rate variables to the current rate
     256     2709520 :     _ang_vel_target_rads = gyro;
     257     2709520 :     body_to_euler_derivative(_attitude_target, _ang_vel_target_rads, _euler_rate_target_rads);
     258             : 
     259             :     // Initialize remaining variables
     260     2709520 :     _thrust_error_angle_rad = 0.0f;
     261             : 
     262             :     // Reset the PID filters
     263     2709520 :     get_rate_roll_pid().reset_filter();
     264     2709520 :     get_rate_pitch_pid().reset_filter();
     265     2709520 :     get_rate_yaw_pid().reset_filter();
     266             : 
     267             :     // Reset the I terms
     268     2709520 :     reset_rate_controller_I_terms();
     269             :     // finally update the attitude target
     270     2709520 :     _ang_vel_body_rads = gyro;
     271     2709520 : }
     272             : 
     273    11544323 : void AC_AttitudeControl::reset_rate_controller_I_terms()
     274             : {
     275    11544323 :     get_rate_roll_pid().reset_I();
     276    11544323 :     get_rate_pitch_pid().reset_I();
     277    11544323 :     get_rate_yaw_pid().reset_I();
     278    11544323 : }
     279             : 
     280             : // reset rate controller I terms smoothly to zero in 0.5 seconds
     281     2502156 : void AC_AttitudeControl::reset_rate_controller_I_terms_smoothly()
     282             : {
     283     2502156 :     get_rate_roll_pid().relax_integrator(0.0, _dt_s, AC_ATTITUDE_RATE_RELAX_TC);
     284     2502156 :     get_rate_pitch_pid().relax_integrator(0.0, _dt_s, AC_ATTITUDE_RATE_RELAX_TC);
     285     2502156 :     get_rate_yaw_pid().relax_integrator(0.0, _dt_s, AC_ATTITUDE_RATE_RELAX_TC);
     286     2502156 : }
     287             : 
     288             : // reset the rate controller target loop updates
     289    33074393 : void AC_AttitudeControl::rate_controller_target_reset()
     290             : {
     291    33074393 :     _sysid_ang_vel_body_rads.zero();
     292    33074393 :     _actuator_sysid.zero();
     293    33074393 :     _pd_scale = VECTORF_111;
     294    33074393 :     _i_scale = VECTORF_111;
     295    33074393 :     _angle_P_scale = VECTORF_111;
     296    33074393 : }
     297             : 
     298             : // Reduce attitude control gains while landed to stop ground resonance
     299    30412448 : void AC_AttitudeControl::landed_gain_reduction(bool landed)
     300             : {
     301    30412448 :     if (is_positive(_input_tc)) {
     302             :         // use 2.0 x tc to match the response time to 86% commanded
     303    30412448 :         const float spool_step = _dt_s / (2.0 * _input_tc);
     304    30412448 :         if (landed) {
     305    12010588 :             _landed_gain_ratio = MIN(1.0, _landed_gain_ratio + spool_step);
     306             :         } else {
     307    18401860 :             _landed_gain_ratio = MAX(0.0, _landed_gain_ratio - spool_step);
     308             :         }
     309             :     } else {
     310           0 :         _landed_gain_ratio = landed ? 1.0 : 0.0;
     311             :     }
     312    30412448 :     Vector3f scale_mult = VECTORF_111 * (1.0 - _landed_gain_ratio) + Vector3f{_land_roll_mult, _land_pitch_mult, _land_yaw_mult} * _landed_gain_ratio;
     313    30412448 :     set_PD_scale_mult(scale_mult);
     314    30412448 :     set_angle_P_scale_mult(scale_mult);
     315    30412448 : }
     316             : 
     317             : // The attitude controller works around the concept of the desired attitude, target attitude
     318             : // and measured attitude. The desired attitude is the attitude input into the attitude controller
     319             : // that expresses where the higher level code would like the aircraft to move to. The target attitude is moved
     320             : // to the desired attitude with jerk, acceleration, and velocity limits. The target angular velocities are fed
     321             : // directly into the rate controllers. The angular error between the measured attitude and the target attitude is
     322             : // fed into the angle controller and the output of the angle controller summed at the input of the rate controllers.
     323             : // By feeding the target angular velocity directly into the rate controllers the measured and target attitudes
     324             : // remain very close together.
     325             : //
     326             : // All input functions below follow the same procedure
     327             : // 1. define the desired attitude or attitude change based on the input variables
     328             : // 2. update the target attitude based on the angular velocity target and the time since the last loop
     329             : // 3. using the desired attitude and input variables, define the target angular velocity so that it should
     330             : //    move the target attitude towards the desired attitude
     331             : // 4. if _rate_bf_ff_enabled is not being used then make the target attitude
     332             : //    and target angular velocities equal to the desired attitude and desired angular velocities.
     333             : // 5. ensure _attitude_target, _euler_angle_target_rad, _euler_rate_target_rads and
     334             : //    _ang_vel_target_rads have been defined. This ensures input modes can be changed without discontinuity.
     335             : // 6. attitude_controller_run_quat is then run to pass the target angular velocities to the rate controllers and
     336             : //    integrate them into the target attitude. Any errors between the target attitude and the measured attitude are
     337             : //    corrected by first correcting the thrust vector until the angle between the target thrust vector measured
     338             : //    trust vector drops below 2*AC_ATTITUDE_THRUST_ERROR_ANGLE_RAD. At this point the heading is also corrected.
     339             : 
     340             : // Sets an attitude target using a quaternion and a body-frame angular velocity input (rad/s).
     341             : // The desired quaternion is incrementally advanced each timestep using the (limited) angular velocity input.
     342             : // If body-frame rate feedforward shaping is enabled, rate/accel targets are generated with acceleration limits
     343             : // and input time constants; otherwise the targets are set directly.
     344       98513 : void AC_AttitudeControl::input_quaternion(Quaternion& attitude_desired_quat, Vector3f ang_vel_body_rads)
     345             : {
     346             :     // Update internal attitude target state
     347       98513 :     update_attitude_target();
     348             : 
     349             :     // Limit the body-frame angular velocity input
     350       98513 :     ang_vel_limit(ang_vel_body_rads, radians(_ang_vel_roll_max_degs), radians(_ang_vel_pitch_max_degs), radians(_ang_vel_yaw_max_degs));
     351             : 
     352             :     // Convert the limited body-frame angular velocity input into the frame used for quaternion integration
     353       98513 :     Vector3f ang_vel_target = attitude_desired_quat * ang_vel_body_rads;
     354             : 
     355       98513 :     if (_rate_bf_ff_enabled) {
     356             :         // Compute attitude error (target -> desired) and express as a small-axis-angle vector
     357       98513 :         Quaternion attitude_error_quat = _attitude_target.inverse() * attitude_desired_quat;
     358       98513 :         Vector3f attitude_error_angle;
     359       98513 :         attitude_error_quat.to_axis_angle(attitude_error_angle);
     360             : 
     361             :         // Shape the attitude error into angular velocity and acceleration targets with configured
     362             :         // rate and acceleration limits and time constants (roll/pitch use _input_tc, yaw uses _rate_y_tc).
     363       98513 :         attitude_command_model(wrap_PI(attitude_error_angle.x), 0.0, _ang_vel_target_rads.x, _ang_accel_target_rads.x, radians(_ang_vel_roll_max_degs), get_accel_roll_max_radss(), _input_tc, _dt_s);
     364       98513 :         attitude_command_model(wrap_PI(attitude_error_angle.y), 0.0, _ang_vel_target_rads.y, _ang_accel_target_rads.y, radians(_ang_vel_pitch_max_degs), get_accel_pitch_max_radss(), _input_tc, _dt_s);
     365       98513 :         attitude_command_model(wrap_PI(attitude_error_angle.z), 0.0, _ang_vel_target_rads.z, _ang_accel_target_rads.z, radians(_ang_vel_yaw_max_degs), get_accel_yaw_max_radss(), _rate_y_tc, _dt_s);
     366             :     } else {
     367             :         // No shaping: directly set attitude and angular velocity targets
     368           0 :         _attitude_target = attitude_desired_quat;
     369           0 :         _ang_vel_target_rads = ang_vel_target;
     370             :     }
     371             : 
     372             :     // Update stored Euler-angle representation of the attitude target
     373       98513 :     _attitude_target.to_euler(_euler_angle_target_rad);
     374             : 
     375             :     // Convert body-frame angular velocity into euler angle derivative of desired attitude
     376       98513 :     body_to_euler_derivative(_attitude_target, _ang_vel_target_rads, _euler_rate_target_rads);
     377             : 
     378             :     // Incrementally advance the desired quaternion using the (limited) angular velocity input
     379       98513 :     Quaternion attitude_desired_update;
     380       98513 :     attitude_desired_update.from_axis_angle(ang_vel_target * _dt_s);
     381       98513 :     attitude_desired_quat = attitude_desired_quat * attitude_desired_update;
     382       98513 :     attitude_desired_quat.normalize();
     383             : 
     384             :     // Run quaternion attitude controller
     385       98513 :     attitude_controller_run_quat();
     386       98513 : }
     387             : 
     388             : // Sets the desired roll and pitch angles (in centidegrees) and yaw rate (in centidegrees/s).
     389             : // See input_euler_angle_roll_pitch_euler_rate_yaw_rad() for full details.
     390     2228903 : void AC_AttitudeControl::input_euler_angle_roll_pitch_euler_rate_yaw_cd(float euler_roll_angle_cd, float euler_pitch_angle_cd, float euler_yaw_rate_cds)
     391             : {
     392             :     // Convert from centidegrees on public interface to radians
     393     2228903 :     const float euler_roll_angle_rad = cd_to_rad(euler_roll_angle_cd);
     394     2228903 :     const float euler_pitch_angle_rad = cd_to_rad(euler_pitch_angle_cd);
     395     2228903 :     const float euler_yaw_rate_rads = cd_to_rad(euler_yaw_rate_cds);
     396     2228903 :     input_euler_angle_roll_pitch_euler_rate_yaw_rad(euler_roll_angle_rad, euler_pitch_angle_rad, euler_yaw_rate_rads);
     397     2228903 : }
     398             : 
     399             : // Sets the desired roll and pitch angle inputs (radians) and a yaw rate input (radians/s).
     400             : // Used when roll/pitch stabilization is required while yaw is controlled as a rate.
     401             : // If body-frame rate feedforward shaping is enabled, shapes Euler rate/acceleration targets
     402             : // with configured limits and time constants and converts them back to body-frame targets.
     403             : // Otherwise, updates the attitude target directly and zeros rate/accel feedforward targets.
     404    15121138 : void AC_AttitudeControl::input_euler_angle_roll_pitch_euler_rate_yaw_rad(float euler_roll_angle_rad, float euler_pitch_angle_rad, float euler_yaw_rate_rads)
     405             : {
     406             :     // update attitude target
     407    15121138 :     update_attitude_target();
     408             : 
     409             :     // calculate the attitude target euler angles
     410    15121138 :     _attitude_target.to_euler(_euler_angle_target_rad);
     411             : 
     412             :     // Add roll trim to compensate tail rotor thrust in heli (will return zero on multirotors)
     413    15121138 :     euler_roll_angle_rad += get_roll_trim_rad();
     414             : 
     415    15121138 :     if (_rate_bf_ff_enabled) {
     416             :         // Convert body-frame angular acceleration limits (roll, pitch, yaw) into equivalent
     417             :         // Euler-angle acceleration limits for the current attitude target.
     418    14875815 :         const Vector3f euler_accel_radss = body_to_euler_limit(_attitude_target, Vector3f{get_accel_roll_max_radss(), get_accel_pitch_max_radss(), get_accel_yaw_max_radss()});
     419    14875815 :         const Vector3f euler_rate_max_rads = body_to_euler_limit(_attitude_target, Vector3f{radians(_ang_vel_roll_max_degs), radians(_ang_vel_pitch_max_degs), radians(_ang_vel_yaw_max_degs)});
     420             : 
     421             :         // Convert the body-frame angular acceleration target into an equivalent Euler-angle
     422             :         // acceleration target for the current attitude target.
     423    14875815 :         Vector3f euler_accel_target_rads;
     424    14875815 :         body_to_euler_derivative(_attitude_target, _ang_accel_target_rads, euler_accel_target_rads);
     425             : 
     426             :         // Shape Euler roll/pitch angle error into Euler rate/acceleration targets.
     427             :         // The shaper applies Euler rate/acceleration limits and the configured input time constant.
     428    14875815 :         attitude_command_model(wrap_PI(euler_roll_angle_rad - _euler_angle_target_rad.x), 0.0, _euler_rate_target_rads.x, euler_accel_target_rads.x, fabsf(euler_rate_max_rads.x), euler_accel_radss.x, _input_tc, _dt_s);
     429    14875815 :         attitude_command_model(wrap_PI(euler_pitch_angle_rad - _euler_angle_target_rad.y), 0.0, _euler_rate_target_rads.y, euler_accel_target_rads.y, fabsf(euler_rate_max_rads.y), euler_accel_radss.y, _input_tc, _dt_s);
     430             : 
     431             :         // Shape yaw rate input into Euler yaw rate/acceleration targets, applying the configured yaw rate time constant
     432             :         // and limiting Euler acceleration about the yaw axis.
     433    14875815 :         attitude_command_model(0.0, euler_yaw_rate_rads, _euler_rate_target_rads.z, euler_accel_target_rads.z, fabsf(euler_rate_max_rads.z), euler_accel_radss.z, _rate_y_tc, _dt_s);
     434             : 
     435             :         // Convert euler angle derivative of desired attitude into a body-frame angular velocity vector for feedforward
     436    14875815 :         euler_derivative_to_body(_attitude_target, _euler_rate_target_rads, _ang_vel_target_rads);
     437             : 
     438             :         // Convert euler angle derivative of desired attitude into a body-frame angular acceleration vector for feedforward
     439    14875815 :         euler_derivative_to_body(_attitude_target, euler_accel_target_rads, _ang_accel_target_rads);
     440             :     } else {
     441             :         // No shaping/feedforward: directly update roll/pitch attitude targets and integrate yaw target from yaw rate.
     442      245323 :         _euler_angle_target_rad.x = euler_roll_angle_rad;
     443      245323 :         _euler_angle_target_rad.y = euler_pitch_angle_rad;
     444      245323 :         _euler_angle_target_rad.z += euler_yaw_rate_rads * _dt_s;
     445             : 
     446             :         // Compute quaternion target attitude
     447      245323 :         _attitude_target.from_euler(_euler_angle_target_rad.x, _euler_angle_target_rad.y, _euler_angle_target_rad.z);
     448             : 
     449             :         // Zero rate and acceleration feedforward targets
     450      245323 :         _euler_rate_target_rads.zero();
     451      245323 :         _ang_vel_target_rads.zero();
     452      245323 :         _ang_accel_target_rads.zero();
     453             :     }
     454             : 
     455             :     // Call quaternion attitude controller
     456    15121138 :     attitude_controller_run_quat();
     457    15121138 : }
     458             : 
     459             : // Sets the desired roll, pitch, and yaw angles (in centidegrees).
     460             : // See input_euler_angle_roll_pitch_yaw_rad() for full details.
     461      965469 : void AC_AttitudeControl::input_euler_angle_roll_pitch_yaw_cd(float euler_roll_angle_cd, float euler_pitch_angle_cd, float euler_yaw_angle_cd, bool slew_yaw)
     462             : {
     463             :     // Convert from centidegrees on public interface to radians
     464      965469 :     const float euler_roll_angle_rad = cd_to_rad(euler_roll_angle_cd);
     465      965469 :     const float euler_pitch_angle_rad = cd_to_rad(euler_pitch_angle_cd);
     466      965469 :     const float euler_yaw_angle_rad = cd_to_rad(euler_yaw_angle_cd);
     467             : 
     468      965469 :     input_euler_angle_roll_pitch_yaw_rad(euler_roll_angle_rad, euler_pitch_angle_rad, euler_yaw_angle_rad, slew_yaw);
     469      965469 : }
     470             : 
     471             : // Sets the desired roll, pitch, and yaw angle inputs (radians) to follow an absolute attitude setpoint.
     472             : // Optional yaw slew limiting constrains the rate of change of the yaw target.
     473             : // If body-frame rate feedforward shaping is enabled, shapes Euler rate/acceleration targets
     474             : // with configured limits and time constants and converts them back to body-frame targets.
     475             : // Otherwise, updates the attitude target directly (with optional yaw slew) and zeros rate/accel feedforward targets.
     476     2183113 : void AC_AttitudeControl::input_euler_angle_roll_pitch_yaw_rad(float euler_roll_angle_rad, float euler_pitch_angle_rad, float euler_yaw_angle_rad, bool slew_yaw)
     477             : {
     478             :     // update attitude target
     479     2183113 :     update_attitude_target();
     480             : 
     481             :     // calculate the attitude target euler angles
     482     2183113 :     _attitude_target.to_euler(_euler_angle_target_rad);
     483             : 
     484             :     // Add roll trim to compensate tail rotor thrust in heli (will return zero on multirotors)
     485     2183113 :     euler_roll_angle_rad += get_roll_trim_rad();
     486             :     
     487     2183113 :     float yaw_rate_max_rads = radians(_ang_vel_yaw_max_degs);
     488     2183113 :     if (slew_yaw) {
     489             :         // Replace global yaw rate limit with the slew rate limit (0 disables limiting).
     490     1895898 :         yaw_rate_max_rads = get_slew_yaw_max_rads();
     491             :     }
     492             : 
     493     2183113 :     if (_rate_bf_ff_enabled) {
     494             :         // Convert body-frame angular rate and acceleration limits (roll, pitch, yaw) into equivalent
     495             :         // Euler-angle rate and acceleration limits for the current attitude target.
     496     1905445 :         const Vector3f euler_accel_radss = body_to_euler_limit(_attitude_target, Vector3f{get_accel_roll_max_radss(), get_accel_pitch_max_radss(), get_accel_yaw_max_radss()});
     497     1905445 :         const Vector3f euler_rate_max_rads = body_to_euler_limit(_attitude_target, Vector3f{radians(_ang_vel_roll_max_degs), radians(_ang_vel_pitch_max_degs), yaw_rate_max_rads});
     498             : 
     499     1905445 :         Vector3f euler_accel_target_rads;
     500     1905445 :         body_to_euler_derivative(_attitude_target, _ang_accel_target_rads, euler_accel_target_rads);
     501     1905445 :         attitude_command_model(wrap_PI(euler_roll_angle_rad - _euler_angle_target_rad.x), 0.0, _euler_rate_target_rads.x, euler_accel_target_rads.x, fabsf(euler_rate_max_rads.x), euler_accel_radss.x, _input_tc, _dt_s);
     502     1905445 :         attitude_command_model(wrap_PI(euler_pitch_angle_rad - _euler_angle_target_rad.y), 0.0, _euler_rate_target_rads.y, euler_accel_target_rads.y, fabsf(euler_rate_max_rads.y), euler_accel_radss.y, _input_tc, _dt_s);
     503     1905445 :         attitude_command_model(wrap_PI(euler_yaw_angle_rad - _euler_angle_target_rad.z), 0.0, _euler_rate_target_rads.z, euler_accel_target_rads.z, fabsf(euler_rate_max_rads.z), euler_accel_radss.z, _input_tc, _dt_s);
     504             : 
     505             :         // Convert euler angle derivative of desired attitude into a body-frame angular velocity vector for feedforward
     506     1905445 :         euler_derivative_to_body(_attitude_target, _euler_rate_target_rads, _ang_vel_target_rads);
     507             : 
     508             :         // Convert euler angle derivative of desired attitude into a body-frame angular acceleration vector for feedforward
     509     1905445 :         euler_derivative_to_body(_attitude_target, euler_accel_target_rads, _ang_accel_target_rads);
     510             :     } else {
     511             :         // No shaping/feedforward: directly update roll/pitch attitude targets and update yaw target
     512             :         // with optional slew limiting, then zero rate/accel feedforward targets.
     513      277668 :         _euler_angle_target_rad.x = euler_roll_angle_rad;
     514      277668 :         _euler_angle_target_rad.y = euler_pitch_angle_rad;
     515             : 
     516      277668 :         if (is_positive(yaw_rate_max_rads)) {
     517             :             // Constrain yaw target change to the configured slew rate.
     518         127 :             const float yaw_error = wrap_PI(euler_yaw_angle_rad - _euler_angle_target_rad.z);
     519         127 :             const float yaw_step = constrain_float(yaw_error, -yaw_rate_max_rads * _dt_s, yaw_rate_max_rads * _dt_s);
     520         127 :             _euler_angle_target_rad.z = wrap_PI(_euler_angle_target_rad.z + yaw_step);
     521             :         } else {
     522      277541 :             _euler_angle_target_rad.z = euler_yaw_angle_rad;
     523             :         }
     524             : 
     525             :         // Compute quaternion target attitude
     526      277668 :         _attitude_target.from_euler(_euler_angle_target_rad.x, _euler_angle_target_rad.y, _euler_angle_target_rad.z);
     527             : 
     528             :         // Zero rate and acceleration feedforward targets
     529      277668 :         _euler_rate_target_rads.zero();
     530      277668 :         _ang_vel_target_rads.zero();
     531      277668 :         _ang_accel_target_rads.zero();
     532             :     }
     533             : 
     534             :     // Call quaternion attitude controller
     535     2183113 :     attitude_controller_run_quat();
     536     2183113 : }
     537             : 
     538             : // Sets the desired roll, pitch, and yaw Euler angle rate inputs (radians/s).
     539             : // If body-frame rate feedforward shaping is enabled, the inputs are shaped using acceleration limits
     540             : // and time constants to generate Euler rate/acceleration targets, which are then converted into
     541             : // body-frame angular velocity/acceleration targets for the rate controller.
     542             : // Otherwise, Euler angle targets are integrated directly from the rate inputs and feedforward targets are zeroed.
     543           0 : void AC_AttitudeControl::input_euler_rate_roll_pitch_yaw_rads(float euler_roll_rate_rads, float euler_pitch_rate_rads, float euler_yaw_rate_rads)
     544             : {
     545             :     // update attitude target
     546           0 :     update_attitude_target();
     547             : 
     548             :     // calculate the attitude target euler angles
     549           0 :     _attitude_target.to_euler(_euler_angle_target_rad);
     550             : 
     551           0 :     if (_rate_bf_ff_enabled) {
     552             :         // Convert body-frame angular acceleration limits (roll, pitch, yaw) into
     553             :         // equivalent Euler-angle acceleration limits for the current attitude target.
     554           0 :         const Vector3f euler_accel_radss = body_to_euler_limit(_attitude_target, Vector3f{get_accel_roll_max_radss(), get_accel_pitch_max_radss(), get_accel_yaw_max_radss()});
     555             : 
     556             :         // Convert the body-frame angular acceleration target into an equivalent Euler-angle
     557             :         // acceleration target for the current attitude target.
     558           0 :         Vector3f euler_accel_target_rads;
     559           0 :         body_to_euler_derivative(_attitude_target, _ang_accel_target_rads, euler_accel_target_rads);
     560             : 
     561             :         // Shape Euler rate inputs into Euler rate/acceleration targets, applying acceleration limits and time constants.
     562           0 :         attitude_command_model(0.0, euler_roll_rate_rads, _euler_rate_target_rads.x, euler_accel_target_rads.x, 0.0, euler_accel_radss.x, _rate_rp_tc, _dt_s);
     563           0 :         attitude_command_model(0.0, euler_pitch_rate_rads, _euler_rate_target_rads.y, euler_accel_target_rads.y, 0.0, euler_accel_radss.y, _rate_rp_tc, _dt_s);
     564           0 :         attitude_command_model(0.0, euler_yaw_rate_rads, _euler_rate_target_rads.z, euler_accel_target_rads.z, 0.0, euler_accel_radss.z, _rate_y_tc, _dt_s);
     565             : 
     566             :         // Convert euler angle derivative of desired attitude into a body-frame angular velocity vector for feedforward
     567           0 :         euler_derivative_to_body(_attitude_target, _euler_rate_target_rads, _ang_vel_target_rads);
     568             : 
     569             :         // Convert euler angle derivative of desired attitude into a body-frame angular acceleration vector for feedforward
     570           0 :         euler_derivative_to_body(_attitude_target, euler_accel_target_rads, _ang_accel_target_rads);
     571             :     } else {
     572             :         // No shaping/feedforward: integrate Euler angle targets from Euler rate inputs.
     573             :         // Pitch is constrained to ±85 degrees to avoid gimbal lock discontinuities.
     574           0 :         _euler_angle_target_rad.x = wrap_PI(_euler_angle_target_rad.x + euler_roll_rate_rads * _dt_s);
     575           0 :         _euler_angle_target_rad.y = constrain_float(_euler_angle_target_rad.y + euler_pitch_rate_rads * _dt_s, radians(-85.0f), radians(85.0f));
     576           0 :         _euler_angle_target_rad.z = wrap_2PI(_euler_angle_target_rad.z + euler_yaw_rate_rads * _dt_s);
     577             : 
     578             :         // Zero rate and acceleration feedforward targets
     579           0 :         _euler_rate_target_rads.zero();
     580           0 :         _ang_vel_target_rads.zero();
     581           0 :         _ang_accel_target_rads.zero();
     582             : 
     583             :         // Compute quaternion target attitude
     584           0 :         _attitude_target.from_euler(_euler_angle_target_rad.x, _euler_angle_target_rad.y, _euler_angle_target_rad.z);
     585             :     }
     586             : 
     587             :     // Call quaternion attitude controller
     588           0 :     attitude_controller_run_quat();
     589           0 : }
     590             : 
     591             : // Fully stabilized acro
     592             : // Sets the desired roll, pitch, and yaw angular rates (in centidegrees/s).
     593             : // See input_rate_bf_roll_pitch_yaw_rads() for full details.
     594         360 : void AC_AttitudeControl::input_rate_bf_roll_pitch_yaw_cds(float roll_rate_bf_cds, float pitch_rate_bf_cds, float yaw_rate_bf_cds)
     595             : {
     596             :     // Convert from centidegrees on public interface to radians
     597         360 :     const float roll_rate_bf_rads = cd_to_rad(roll_rate_bf_cds);
     598         360 :     const float pitch_rate_bf_rads = cd_to_rad(pitch_rate_bf_cds);
     599         360 :     const float yaw_rate_bf_rads = cd_to_rad(yaw_rate_bf_cds);
     600             : 
     601         360 :     input_rate_bf_roll_pitch_yaw_rads(roll_rate_bf_rads, pitch_rate_bf_rads, yaw_rate_bf_rads);
     602         360 : }
     603             : 
     604             : // Sets the desired roll, pitch, and yaw body-frame angular rate inputs (radians/s).
     605             : // Used by fully stabilized acro modes.
     606             : // If body-frame rate feedforward shaping is enabled, the inputs are shaped using acceleration limits
     607             : // and time constants to generate body-frame angular velocity/acceleration targets for the rate controller.
     608             : // Otherwise, the attitude target is incrementally rotated using the rate inputs and feedforward targets are zeroed.
     609      113716 : void AC_AttitudeControl::input_rate_bf_roll_pitch_yaw_rads(float roll_rate_bf_rads, float pitch_rate_bf_rads, float yaw_rate_bf_rads)
     610             : {
     611             :     // update attitude target
     612      113716 :     update_attitude_target();
     613             : 
     614             :     // calculate the attitude target euler angles
     615      113716 :     _attitude_target.to_euler(_euler_angle_target_rad);
     616             : 
     617      113716 :     if (_rate_bf_ff_enabled) {
     618             :         // Shape body-frame rate inputs into body-frame angular velocity/acceleration targets,
     619             :         // applying acceleration limits and configured time constants.
     620      113716 :         attitude_command_model(0.0, roll_rate_bf_rads, _ang_vel_target_rads.x, _ang_accel_target_rads.x, 0.0, get_accel_roll_max_radss(), _rate_rp_tc, _dt_s);
     621      113716 :         attitude_command_model(0.0, pitch_rate_bf_rads, _ang_vel_target_rads.y, _ang_accel_target_rads.y, 0.0, get_accel_pitch_max_radss(), _rate_rp_tc, _dt_s);
     622      113716 :         attitude_command_model(0.0, yaw_rate_bf_rads, _ang_vel_target_rads.z, _ang_accel_target_rads.z, 0.0, get_accel_yaw_max_radss(), _rate_y_tc, _dt_s);
     623             : 
     624             :         // Convert body-frame angular velocity into euler angle derivative of desired attitude
     625      113716 :         body_to_euler_derivative(_attitude_target, _ang_vel_target_rads, _euler_rate_target_rads);
     626             :     } else {
     627             :         // No shaping/feedforward: incrementally rotate the attitude target using the body-frame rate inputs.
     628           0 :         Quaternion attitude_target_update;
     629           0 :         attitude_target_update.from_axis_angle(Vector3f{roll_rate_bf_rads, pitch_rate_bf_rads, yaw_rate_bf_rads} * _dt_s);
     630           0 :         _attitude_target = _attitude_target * attitude_target_update;
     631           0 :         _attitude_target.normalize();
     632             : 
     633             :         // Zero rate and acceleration feedforward targets
     634           0 :         _euler_rate_target_rads.zero();
     635           0 :         _ang_vel_target_rads.zero();
     636           0 :         _ang_accel_target_rads.zero();
     637             :     }
     638             : 
     639             :     // Call quaternion attitude controller
     640      113716 :     attitude_controller_run_quat();
     641      113716 : }
     642             : 
     643             : // Sets the desired roll, pitch, and yaw angular rates in body-frame (in centidegrees/s).
     644             : // See input_rate_bf_roll_pitch_yaw_2_rads() for full details.
     645        9994 : void AC_AttitudeControl::input_rate_bf_roll_pitch_yaw_2_cds(float roll_rate_bf_cds, float pitch_rate_bf_cds, float yaw_rate_bf_cds)
     646             : {
     647             :     // Convert from centidegrees on public interface to radians
     648        9994 :     const float roll_rate_bf_rads = cd_to_rad(roll_rate_bf_cds);
     649        9994 :     const float pitch_rate_bf_rads = cd_to_rad(pitch_rate_bf_cds);
     650        9994 :     const float yaw_rate_bf_rads = cd_to_rad(yaw_rate_bf_cds);
     651             : 
     652        9994 :     input_rate_bf_roll_pitch_yaw_2_rads(roll_rate_bf_rads, pitch_rate_bf_rads, yaw_rate_bf_rads);
     653        9994 : }
     654             : 
     655             : // Sets the desired roll, pitch, and yaw body-frame angular rate inputs (radians/s).
     656             : // Used by Copter's rate-only acro mode.
     657             : // Shapes the body-frame rate inputs using acceleration limits and time constants to produce
     658             : // body-frame angular velocity/acceleration targets for the rate controller.
     659             : // Attitude targets are updated from the current AHRS attitude to keep target state coherent for mode transitions.
     660        9994 : void AC_AttitudeControl::input_rate_bf_roll_pitch_yaw_2_rads(float roll_rate_bf_rads, float pitch_rate_bf_rads, float yaw_rate_bf_rads)
     661             : {
     662             :     // Shape body-frame rate inputs into body-frame angular velocity/acceleration targets,
     663             :     // applying acceleration limits and configured time constants.
     664        9994 :     attitude_command_model(0.0, roll_rate_bf_rads, _ang_vel_target_rads.x, _ang_accel_target_rads.x, 0.0, get_accel_roll_max_radss(), _rate_rp_tc, _dt_s);
     665        9994 :     attitude_command_model(0.0, pitch_rate_bf_rads, _ang_vel_target_rads.y, _ang_accel_target_rads.y, 0.0, get_accel_pitch_max_radss(), _rate_rp_tc, _dt_s);
     666        9994 :     attitude_command_model(0.0, yaw_rate_bf_rads, _ang_vel_target_rads.z, _ang_accel_target_rads.z, 0.0, get_accel_yaw_max_radss(), _rate_y_tc, _dt_s);
     667             : 
     668             :     // Update attitude and Euler targets from the current vehicle attitude (used for conditioning mode transitions).
     669        9994 :     _ahrs.get_quat_body_to_ned(_attitude_target);
     670        9994 :     _attitude_target.to_euler(_euler_angle_target_rad);
     671             :     // Convert body-frame angular velocity into euler angle derivative of desired attitude
     672        9994 :     body_to_euler_derivative(_attitude_target, _ang_vel_target_rads, _euler_rate_target_rads);
     673             : 
     674             :     // Update body-frame angular velocity target used by the rate controller.
     675        9994 :     _ang_vel_body_rads = _ang_vel_target_rads;
     676        9994 : }
     677             : 
     678             : // Sets the desired roll, pitch, and yaw angular rates in body-frame (in centidegrees/s).
     679             : // See input_rate_bf_roll_pitch_yaw_3_rads() for full details.
     680           0 : void AC_AttitudeControl::input_rate_bf_roll_pitch_yaw_3_cds(float roll_rate_bf_cds, float pitch_rate_bf_cds, float yaw_rate_bf_cds)
     681             : {
     682             :     // Convert from centidegrees on public interface to radians
     683           0 :     const float roll_rate_bf_rads = cd_to_rad(roll_rate_bf_cds);
     684           0 :     const float pitch_rate_bf_rads = cd_to_rad(pitch_rate_bf_cds);
     685           0 :     const float yaw_rate_bf_rads = cd_to_rad(yaw_rate_bf_cds);
     686             : 
     687           0 :     input_rate_bf_roll_pitch_yaw_3_rads(roll_rate_bf_rads, pitch_rate_bf_rads, yaw_rate_bf_rads);
     688           0 : }
     689             : 
     690             : // Sets the desired roll, pitch, and yaw body-frame angular rate inputs (radians/s).
     691             : // Used by Plane's acro mode with rate error integration.
     692             : // Maintains an integrated attitude error quaternion using (target_rate - gyro) and combines it
     693             : // with shaped rate inputs to form the final body-frame angular velocity target for the rate controller.
     694           0 : void AC_AttitudeControl::input_rate_bf_roll_pitch_yaw_3_rads(float roll_rate_bf_rads, float pitch_rate_bf_rads, float yaw_rate_bf_rads)
     695             : {
     696             :     // Extract current integrated attitude error as axis-angle
     697           0 :     Vector3f attitude_error;
     698           0 :     _attitude_ang_error.to_axis_angle(attitude_error);
     699             : 
     700           0 :     Quaternion attitude_ang_error_update_quat;
     701             : 
     702             :     // Limit the magnitude of the integrated attitude error (prevents windup)
     703           0 :     float err_mag = attitude_error.length();
     704           0 :     if (err_mag > AC_ATTITUDE_THRUST_ERROR_ANGLE_RAD) {
     705           0 :         attitude_error *= AC_ATTITUDE_THRUST_ERROR_ANGLE_RAD / err_mag;
     706           0 :         _attitude_ang_error.from_axis_angle(attitude_error);
     707             :     }
     708             : 
     709             :     // Integrate attitude error using rate error: (target body rates - measured gyro)
     710           0 :     Vector3f gyro_latest = get_latest_gyro();
     711           0 :     attitude_ang_error_update_quat.from_axis_angle((_ang_vel_target_rads - gyro_latest) * _dt_s);
     712           0 :     _attitude_ang_error = attitude_ang_error_update_quat * _attitude_ang_error;
     713           0 :     _attitude_ang_error.normalize();
     714             : 
     715             :     // Shape body-frame rate inputs into body-frame angular velocity/acceleration targets,
     716             :     // applying acceleration limits and configured time constants.
     717           0 :     attitude_command_model(0.0, roll_rate_bf_rads, _ang_vel_target_rads.x, _ang_accel_target_rads.x, 0.0, get_accel_roll_max_radss(), _rate_rp_tc, _dt_s);
     718           0 :     attitude_command_model(0.0, pitch_rate_bf_rads, _ang_vel_target_rads.y, _ang_accel_target_rads.y, 0.0, get_accel_pitch_max_radss(), _rate_rp_tc, _dt_s);
     719           0 :     attitude_command_model(0.0, yaw_rate_bf_rads, _ang_vel_target_rads.z, _ang_accel_target_rads.z, 0.0, get_accel_yaw_max_radss(), _rate_y_tc, _dt_s);
     720             : 
     721             :     // Retrieve current vehicle attitude (body to NED)
     722           0 :     Quaternion attitude_body;
     723           0 :     _ahrs.get_quat_body_to_ned(attitude_body);
     724             : 
     725             :     // Update attitude target by applying the integrated attitude error to the current attitude
     726           0 :     _attitude_target = attitude_body * _attitude_ang_error;
     727           0 :     _attitude_target.normalize();
     728             : 
     729             :     // Update stored Euler-angle representation of the attitude target
     730           0 :     _attitude_target.to_euler(_euler_angle_target_rad);
     731             : 
     732             :     // Convert body-frame angular velocity into euler angle derivative of desired attitude
     733           0 :     body_to_euler_derivative(_attitude_target, _ang_vel_target_rads, _euler_rate_target_rads);
     734             : 
     735             :     // Compute body-frame angular velocity correction from integrated attitude error and add shaped rate input
     736           0 :     _attitude_ang_error.to_axis_angle(attitude_error);
     737           0 :     Vector3f ang_vel_body_rads = update_ang_vel_target_from_att_error(attitude_error);
     738           0 :     ang_vel_body_rads += _ang_vel_target_rads;
     739             : 
     740             :     // Update body-frame angular velocity target used by the rate controller
     741           0 :     _ang_vel_body_rads = ang_vel_body_rads;
     742           0 : }
     743             : 
     744             : /*
     745             :   set the body frame target rates to the specified rates, used by the
     746             :   quadplane code when we want to slave the VTOL controller rates to
     747             :   the fixed wing rates
     748             :  */
     749             : 
     750             : // Directly sets the body-frame angular rates without smoothing (in centidegrees/s).
     751             : // See input_rate_bf_roll_pitch_yaw_no_shaping_rads() for full details.
     752      414050 : void AC_AttitudeControl::input_rate_bf_roll_pitch_yaw_no_shaping_cds(float roll_rate_bf_cds, float pitch_rate_bf_cds, float yaw_rate_bf_cds)
     753             : {
     754             :     // Convert from centidegrees on public interface to radians
     755      414050 :     const float roll_rate_bf_rads = cd_to_rad(roll_rate_bf_cds);
     756      414050 :     const float pitch_rate_bf_rads = cd_to_rad(pitch_rate_bf_cds);
     757      414050 :     const float yaw_rate_bf_rads = cd_to_rad(yaw_rate_bf_cds);
     758             : 
     759      414050 :     input_rate_bf_roll_pitch_yaw_no_shaping_rads(roll_rate_bf_rads, pitch_rate_bf_rads, yaw_rate_bf_rads);
     760      414050 : }
     761             : 
     762             : // Directly sets body-frame angular rate targets (radians/s) without shaping.
     763             : // Used when an external controller (e.g. fixed-wing controller) provides VTOL body rates.
     764             : // No smoothing, acceleration limiting, or input shaping is applied.
     765      414050 : void AC_AttitudeControl::input_rate_bf_roll_pitch_yaw_no_shaping_rads(float roll_rate_bf_rads, float pitch_rate_bf_rads, float yaw_rate_bf_rads)
     766             : {
     767             :     // Set body-frame angular velocity targets directly from inputs
     768      414050 :     _ang_vel_target_rads.x = roll_rate_bf_rads;
     769      414050 :     _ang_vel_target_rads.y = pitch_rate_bf_rads;
     770      414050 :     _ang_vel_target_rads.z = yaw_rate_bf_rads;
     771             : 
     772             :     // Update attitude and Euler targets from the current vehicle attitude
     773             :     // (used for mode transitions / logging / target state consistency).
     774      414050 :     _ahrs.get_quat_body_to_ned(_attitude_target);
     775      414050 :     _attitude_target.to_euler(_euler_angle_target_rad);
     776             : 
     777             :     // Convert body-frame angular velocity into euler angle derivative of desired attitude
     778      414050 :     body_to_euler_derivative(_attitude_target, _ang_vel_target_rads, _euler_rate_target_rads);
     779             : 
     780             :     // Update body-frame angular velocity target used by the rate controller.
     781      414050 :     _ang_vel_body_rads = _ang_vel_target_rads;
     782      414050 : }
     783             : 
     784             : // Applies a one-time angular offset to the attitude target using body-frame roll, pitch,
     785             : // and yaw angles (radians).
     786             : // Used for step-response excitation during autotuning or manual test inputs.
     787             : // The offset is applied instantaneously; no rate or acceleration shaping is performed.
     788         471 : void AC_AttitudeControl::input_angle_step_bf_roll_pitch_yaw_rad(float roll_angle_step_bf_rad, float pitch_angle_step_bf_rad, float yaw_angle_step_bf_rad)
     789             : {
     790             :     // Apply the requested body-frame angular step to the attitude target
     791         471 :     Quaternion attitude_target_update;
     792         471 :     attitude_target_update.from_axis_angle(Vector3f{roll_angle_step_bf_rad, pitch_angle_step_bf_rad, yaw_angle_step_bf_rad});
     793         471 :     _attitude_target = _attitude_target * attitude_target_update;
     794         471 :     _attitude_target.normalize();
     795             : 
     796             :     // Update stored Euler-angle representation of the attitude target
     797         471 :     _attitude_target.to_euler(_euler_angle_target_rad);
     798             : 
     799             :     // Zero rate and acceleration feedforward targets (pure attitude step)
     800         471 :     _euler_rate_target_rads.zero();
     801         471 :     _ang_vel_target_rads.zero();
     802         471 :     _ang_accel_target_rads.zero();
     803             : 
     804             :     // Run quaternion attitude controller
     805         471 :     attitude_controller_run_quat();
     806         471 : }
     807             : 
     808             : // Applies a one-time body-frame angular rate step (radians/s) in roll, pitch, and yaw.
     809             : // Used to inject discrete disturbances or step inputs for system identification.
     810             : // This sets the body-frame rate command directly for this update; no shaping is applied.
     811      210919 : void AC_AttitudeControl::input_rate_step_bf_roll_pitch_yaw_rads(float roll_rate_step_bf_rads, float pitch_rate_step_bf_rads, float yaw_rate_step_bf_rads)
     812             : {
     813             :     // Update attitude and Euler targets from the current vehicle attitude
     814             :     // (used for mode transitions / logging / target state consistency).
     815      210919 :     _ahrs.get_quat_body_to_ned(_attitude_target);
     816      210919 :     _attitude_target.to_euler(_euler_angle_target_rad);
     817             : 
     818             :     // Zero rate and acceleration feedforward targets so the controller cleanly returns to zero rate
     819             :     // after the step input is removed.
     820      210919 :     _ang_vel_target_rads.zero();
     821      210919 :     _ang_accel_target_rads.zero();
     822      210919 :     _euler_rate_target_rads.zero();
     823             : 
     824             :     // Apply the requested body-frame angular rate step directly to the rate controller input.
     825      210919 :     _ang_vel_body_rads = Vector3f{roll_rate_step_bf_rads, pitch_rate_step_bf_rads, yaw_rate_step_bf_rads};
     826      210919 : }
     827             : 
     828             : // Sets the desired thrust vector and a yaw/heading rate input (radians/s).
     829             : // Used for tilt-based navigation with independent yaw control.
     830             : // The thrust vector determines the desired tilt (attitude) while the heading rate commands yaw.
     831             : // Optional yaw slew limiting constrains the heading rate input.
     832    12695991 : void AC_AttitudeControl::input_thrust_vector_rate_heading_rads(const Vector3f& thrust_vector, float heading_rate_rads, bool slew_yaw)
     833             : {
     834    12695991 :     float yaw_rate_max_rads = radians(_ang_vel_yaw_max_degs);
     835    12695991 :     if (slew_yaw) {
     836             :         // Replace global yaw rate limit with the slew rate limit (0 disables limiting).
     837     9236490 :         yaw_rate_max_rads = get_slew_yaw_max_rads();
     838             :     }
     839             : 
     840             :     // update attitude target
     841    12695991 :     update_attitude_target();
     842             : 
     843             :     // calculate the attitude target euler angles
     844    12695991 :     _attitude_target.to_euler(_euler_angle_target_rad);
     845             : 
     846             :     // Convert thrust vector to an attitude quaternion (zero yaw; yaw is commanded separately).
     847    12695991 :     Quaternion thrust_vec_quat = attitude_from_thrust_vector(thrust_vector, 0.0f);
     848             : 
     849             :     // Compute the attitude correction required to align the current target attitude with the thrust vector.
     850             :     float thrust_vector_diff_angle;
     851    12695991 :     Quaternion thrust_vec_correction_quat;
     852    12695991 :     Vector3f attitude_error;
     853             :     float returned_thrust_vector_angle;
     854    12695991 :     thrust_vector_rotation_angles(thrust_vec_quat, _attitude_target, thrust_vec_correction_quat, attitude_error, returned_thrust_vector_angle, thrust_vector_diff_angle);
     855             : 
     856    12695991 :     if (_rate_bf_ff_enabled) {
     857             :         // Shape roll/pitch attitude error into body-frame angular velocity/acceleration targets,
     858             :         // applying configured rate/acceleration limits and input time constant.
     859    12695991 :         attitude_command_model(attitude_error.x, 0.0, _ang_vel_target_rads.x, _ang_accel_target_rads.x, radians(_ang_vel_roll_max_degs), get_accel_roll_max_radss(), _input_tc, _dt_s);
     860    12695991 :         attitude_command_model(attitude_error.y, 0.0, _ang_vel_target_rads.y, _ang_accel_target_rads.y, radians(_ang_vel_pitch_max_degs), get_accel_pitch_max_radss(), _input_tc, _dt_s);
     861             : 
     862             :         // Shape yaw rate input into yaw angular velocity/acceleration targets, applying yaw limits and time constant.
     863    12695991 :         attitude_command_model(0.0, heading_rate_rads, _ang_vel_target_rads.z, _ang_accel_target_rads.z, yaw_rate_max_rads, get_accel_yaw_max_radss(), _rate_y_tc, _dt_s);
     864             :     } else {
     865             :         // No shaping/feedforward: directly update the attitude target using the thrust-vector correction and yaw increment.
     866           0 :         if (is_positive(yaw_rate_max_rads)) {
     867           0 :             heading_rate_rads = constrain_float(heading_rate_rads, -yaw_rate_max_rads, yaw_rate_max_rads);
     868             :         }
     869           0 :         Quaternion yaw_quat;
     870           0 :         yaw_quat.from_axis_angle(Vector3f{0.0f, 0.0f, heading_rate_rads * _dt_s});
     871           0 :         _attitude_target = _attitude_target * thrust_vec_correction_quat * yaw_quat;
     872           0 :         _attitude_target.normalize();
     873             : 
     874             :         // Zero rate and acceleration feedforward targets
     875           0 :         _euler_rate_target_rads.zero();
     876           0 :         _ang_vel_target_rads.zero();
     877           0 :         _ang_accel_target_rads.zero();
     878             :     }
     879             : 
     880             :     // Convert body-frame angular velocity into euler angle derivative of desired attitude
     881    12695991 :     body_to_euler_derivative(_attitude_target, _ang_vel_target_rads, _euler_rate_target_rads);
     882             : 
     883             :     // Call quaternion attitude controller
     884    12695991 :     attitude_controller_run_quat();
     885    12695991 : }
     886             : 
     887             : // Sets the desired thrust vector, heading angle (radians), and heading rate input (radians/s).
     888             : // Used when thrust direction (tilt) is commanded independently from yaw/heading.
     889             : // Heading rate is constrained using the configured yaw slew rate limit (0 disables limiting).
     890     2743232 : void AC_AttitudeControl::input_thrust_vector_heading_rad(const Vector3f& thrust_vector, float heading_angle_rad, float heading_rate_rads)
     891             : {
     892             :     // update attitude target
     893     2743232 :     update_attitude_target();
     894             : 
     895             :     // calculate the attitude target euler angles
     896     2743232 :     _attitude_target.to_euler(_euler_angle_target_rad);
     897             : 
     898             :     // Convert thrust vector and heading into a desired attitude quaternion.
     899     2743232 :     const Quaternion desired_attitude_quat = attitude_from_thrust_vector(thrust_vector, heading_angle_rad);
     900             : 
     901     2743232 :     if (_rate_bf_ff_enabled) {
     902             :         // Compute attitude error required to move from the current target attitude to the desired attitude.
     903     2743232 :         Vector3f attitude_error;
     904             :         float thrust_vector_diff_angle;
     905     2743232 :         Quaternion thrust_vec_correction_quat;
     906             :         float returned_thrust_vector_angle;
     907     2743232 :         thrust_vector_rotation_angles(desired_attitude_quat, _attitude_target, thrust_vec_correction_quat, attitude_error, returned_thrust_vector_angle, thrust_vector_diff_angle);
     908             : 
     909             :         // Shape attitude error and heading rate into body-frame angular velocity/acceleration targets,
     910             :         // applying configured rate/acceleration limits and time constants (roll/pitch use _input_tc, yaw uses _rate_y_tc).
     911     2743232 :         attitude_command_model(attitude_error.x, 0.0, _ang_vel_target_rads.x, _ang_accel_target_rads.x, radians(_ang_vel_roll_max_degs), get_accel_roll_max_radss(), _input_tc, _dt_s);
     912     2743232 :         attitude_command_model(attitude_error.y, 0.0, _ang_vel_target_rads.y, _ang_accel_target_rads.y, radians(_ang_vel_pitch_max_degs), get_accel_pitch_max_radss(), _input_tc, _dt_s);
     913     2743232 :         attitude_command_model(attitude_error.z, heading_rate_rads, _ang_vel_target_rads.z, _ang_accel_target_rads.z, get_slew_yaw_max_rads(), get_accel_yaw_max_radss(), _rate_y_tc, _dt_s);
     914             :     } else {
     915             :         // No shaping/feedforward: directly set the attitude target to the desired attitude.
     916           0 :         _attitude_target = desired_attitude_quat;
     917             : 
     918             :         // Zero rate and acceleration feedforward targets
     919           0 :         _euler_rate_target_rads.zero();
     920           0 :         _ang_vel_target_rads.zero();
     921           0 :         _ang_accel_target_rads.zero();
     922             :     }
     923             : 
     924             :     // Convert body-frame angular velocity into euler angle derivative of desired attitude
     925     2743232 :     body_to_euler_derivative(_attitude_target, _ang_vel_target_rads, _euler_rate_target_rads);
     926             : 
     927             :     // Call quaternion attitude controller
     928     2743232 :     attitude_controller_run_quat();
     929     2743232 : }
     930             : 
     931             : // Command a thrust vector and heading rate
     932    11730957 : void AC_AttitudeControl::input_thrust_vector_heading(const Vector3f& thrust_vector, HeadingCommand heading)
     933             : {
     934    11730957 :     switch (heading.heading_mode) {
     935     8996498 :     case HeadingMode::Rate_Only:
     936     8996498 :         input_thrust_vector_rate_heading_rads(thrust_vector, heading.yaw_rate_rads);
     937     8996498 :         break;
     938           0 :     case HeadingMode::Angle_Only:
     939           0 :         input_thrust_vector_heading_rad(thrust_vector, heading.yaw_angle_rad, 0.0);
     940           0 :         break;
     941     2734459 :     case HeadingMode::Angle_And_Rate:
     942     2734459 :         input_thrust_vector_heading_rad(thrust_vector, heading.yaw_angle_rad, heading.yaw_rate_rads);
     943     2734459 :         break;
     944             :     }
     945    11730957 : }
     946             : 
     947    15470758 : Quaternion AC_AttitudeControl::attitude_from_thrust_vector(Vector3f thrust_vector, float heading_angle_rad) const
     948             : {
     949    15470758 :     const Vector3f thrust_vector_up{0.0f, 0.0f, -1.0f};
     950             : 
     951    15470758 :     if (is_zero(thrust_vector.length_squared())) {
     952           0 :         thrust_vector = thrust_vector_up;
     953             :     } else {
     954    15470758 :         thrust_vector.normalize();
     955             :     }
     956             : 
     957             :     // the cross product of the desired and target thrust vector defines the rotation vector
     958    15470758 :     Vector3f thrust_vec_cross = thrust_vector_up % thrust_vector;
     959             : 
     960             :     // the dot product is used to calculate the angle between the target and desired thrust vectors
     961    15470758 :     const float thrust_vector_angle = acosf(constrain_float(thrust_vector_up * thrust_vector, -1.0f, 1.0f));
     962             : 
     963             :     // Normalize the thrust rotation vector
     964    15470758 :     const float thrust_vector_length = thrust_vec_cross.length();
     965    15470758 :     if (is_zero(thrust_vector_length) || is_zero(thrust_vector_angle)) {
     966     1672188 :         thrust_vec_cross = thrust_vector_up;
     967             :     } else {
     968    13798570 :         thrust_vec_cross /= thrust_vector_length;
     969             :     }
     970             : 
     971    15470758 :     Quaternion thrust_vec_quat;
     972    15470758 :     thrust_vec_quat.from_axis_angle(thrust_vec_cross, thrust_vector_angle);
     973    15470758 :     Quaternion yaw_quat;
     974    15470758 :     yaw_quat.from_axis_angle(Vector3f{0.0f, 0.0f, 1.0f}, heading_angle_rad);
     975    15470758 :     return thrust_vec_quat*yaw_quat;
     976             : }
     977             : 
     978             : // Calculates the body frame angular velocities to follow the target attitude
     979    32955703 : void AC_AttitudeControl::update_attitude_target()
     980             : {
     981             :     // rotate target and normalize
     982    32955703 :     Quaternion attitude_target_update;
     983    32955703 :     attitude_target_update.from_axis_angle(_ang_vel_target_rads * _dt_s);
     984    32955703 :     _attitude_target *= attitude_target_update;
     985    32955703 :     _attitude_target.normalize();
     986    32955703 : }
     987             : 
     988             : // Calculates the body frame angular velocities to follow the target attitude
     989    32956174 : void AC_AttitudeControl::attitude_controller_run_quat()
     990             : {
     991             :     // This represents a quaternion rotation in NED frame to the body
     992    32956174 :     Quaternion attitude_body;
     993    32956174 :     _ahrs.get_quat_body_to_ned(attitude_body);
     994             : 
     995             :     // This vector represents the angular error to rotate the thrust vector using x and y and heading using z
     996    32956174 :     Vector3f attitude_error;
     997    32956174 :     thrust_heading_rotation_angles(_attitude_target, attitude_body, attitude_error, _thrust_angle_rad, _thrust_error_angle_rad);
     998             : 
     999             :     // Compute the angular velocity corrections in the body frame from the attitude error
    1000    32956174 :     Vector3f ang_vel_body_rads = update_ang_vel_target_from_att_error(attitude_error);
    1001             : 
    1002             :     // ensure angular velocity does not go over configured limits
    1003    32956174 :     ang_vel_limit(ang_vel_body_rads, radians(_ang_vel_roll_max_degs), radians(_ang_vel_pitch_max_degs), radians(_ang_vel_yaw_max_degs));
    1004             : 
    1005             :     // rotation from the target frame to the body frame
    1006    32956174 :     Quaternion rotation_target_to_body = attitude_body.inverse() * _attitude_target;
    1007             : 
    1008             :     // target angle velocity vector in the body frame
    1009    32956174 :     Vector3f ang_vel_body_feedforward = rotation_target_to_body * _ang_vel_target_rads;
    1010    32956174 :     Vector3f gyro = get_latest_gyro();
    1011             :     // Correct the thrust vector and smoothly add feedforward and yaw input
    1012    32956174 :     _feedforward_scalar = 1.0f;
    1013    32956174 :     if (_thrust_error_angle_rad > AC_ATTITUDE_THRUST_ERROR_ANGLE_RAD * 2.0f) {
    1014      505361 :         ang_vel_body_rads.z = gyro.z;
    1015    32450813 :     } else if (_thrust_error_angle_rad > AC_ATTITUDE_THRUST_ERROR_ANGLE_RAD) {
    1016      135950 :         _feedforward_scalar = (1.0f - (_thrust_error_angle_rad - AC_ATTITUDE_THRUST_ERROR_ANGLE_RAD) / AC_ATTITUDE_THRUST_ERROR_ANGLE_RAD);
    1017      135950 :         ang_vel_body_rads.x += ang_vel_body_feedforward.x * _feedforward_scalar;
    1018      135950 :         ang_vel_body_rads.y += ang_vel_body_feedforward.y * _feedforward_scalar;
    1019      135950 :         ang_vel_body_rads.z += ang_vel_body_feedforward.z;
    1020      135950 :         ang_vel_body_rads.z = gyro.z * (1.0 - _feedforward_scalar) + ang_vel_body_rads.z * _feedforward_scalar;
    1021             :     } else {
    1022    32314863 :         ang_vel_body_rads += ang_vel_body_feedforward;
    1023             :     }
    1024             : 
    1025             :     // Record error to handle EKF resets
    1026    32956174 :     _attitude_ang_error = attitude_body.inverse() * _attitude_target;
    1027             :     // finally update the attitude target
    1028    32956174 :     _ang_vel_body_rads = ang_vel_body_rads;
    1029    32956174 : }
    1030             : 
    1031             : // thrust_heading_rotation_angles - calculates two ordered rotations to move the attitude_body quaternion to the attitude_target quaternion.
    1032             : // The maximum error in the yaw axis is limited based on static output saturation.
    1033    32964574 : void AC_AttitudeControl::thrust_heading_rotation_angles(Quaternion& attitude_target, const Quaternion& attitude_body, Vector3f& attitude_error_rad, float& thrust_angle_rad, float& thrust_error_angle_rad) const
    1034             : {
    1035    32964574 :     Quaternion thrust_vector_correction;
    1036    32964574 :     thrust_vector_rotation_angles(attitude_target, attitude_body, thrust_vector_correction, attitude_error_rad, thrust_angle_rad, thrust_error_angle_rad);
    1037             : 
    1038             :     // Todo: Limit roll an pitch error based on output saturation and maximum error.
    1039             : 
    1040             :     // Limit Yaw Error based to the maximum that would saturate the output when yaw rate is zero.
    1041    32964574 :     Quaternion heading_vec_correction_quat;
    1042             : 
    1043    32964574 :     float heading_accel_max = constrain_float(get_accel_yaw_max_radss() / 2.0f, AC_ATTITUDE_ACCEL_Y_CONTROLLER_MIN_RADSS, AC_ATTITUDE_ACCEL_Y_CONTROLLER_MAX_RADSS);
    1044    32964574 :     if (!is_zero(get_rate_yaw_pid().kP())) {
    1045    32964574 :         float heading_error_max = MIN(inv_sqrt_controller(1.0 / get_rate_yaw_pid().kP(), _p_angle_yaw.kP(), heading_accel_max), AC_ATTITUDE_YAW_MAX_ERROR_ANGLE_RAD);
    1046    32964574 :         if (!is_zero(_p_angle_yaw.kP()) && fabsf(attitude_error_rad.z) > heading_error_max) {
    1047      401605 :             attitude_error_rad.z = constrain_float(wrap_PI(attitude_error_rad.z), -heading_error_max, heading_error_max);
    1048      401605 :             heading_vec_correction_quat.from_axis_angle(Vector3f{0.0f, 0.0f, attitude_error_rad.z});
    1049      401605 :             attitude_target = attitude_body * thrust_vector_correction * heading_vec_correction_quat;
    1050             :         }
    1051             :     }
    1052    32964574 : }
    1053             : 
    1054             : // thrust_vector_rotation_angles - calculates two ordered rotations to move the attitude_body quaternion to the attitude_target quaternion.
    1055             : // The first rotation corrects the thrust vector and the second rotation corrects the heading vector.
    1056    48403797 : void AC_AttitudeControl::thrust_vector_rotation_angles(const Quaternion& attitude_target, const Quaternion& attitude_body, Quaternion& thrust_vector_correction, Vector3f& attitude_error_rad, float& thrust_angle_rad, float& thrust_error_angle_rad) const
    1057             : {
    1058             :     // The direction of thrust is [0,0,-1] is any body-fixed frame, inc. body frame and target frame.
    1059    48403797 :     const Vector3f thrust_vector_up{0.0f, 0.0f, -1.0f};
    1060             : 
    1061             :     // attitude_target and attitude_body are passive rotations from target / body frames to the NED frame
    1062             :     
    1063             :     // Rotating [0,0,-1] by attitude_target expresses (gets a view of) the target thrust vector in the inertial frame
    1064    48403797 :     const Vector3f att_target_thrust_vec = attitude_target * thrust_vector_up; // target thrust vector
    1065             : 
    1066             :     // Rotating [0,0,-1] by attitude_target expresses (gets a view of) the current thrust vector in the inertial frame
    1067    48403797 :     const Vector3f att_body_thrust_vec = attitude_body * thrust_vector_up; // current thrust vector
    1068             : 
    1069             :     // the dot product is used to calculate the current lean angle for use of external functions
    1070    48403797 :     thrust_angle_rad = acosf(constrain_float(thrust_vector_up * att_body_thrust_vec,-1.0f,1.0f));
    1071             : 
    1072             :     // the cross product of the desired and target thrust vector defines the rotation vector
    1073    48403797 :     Vector3f thrust_vec_cross = att_body_thrust_vec % att_target_thrust_vec;
    1074             : 
    1075             :     // the dot product is used to calculate the angle between the target and desired thrust vectors
    1076    48403797 :     thrust_error_angle_rad = acosf(constrain_float(att_body_thrust_vec * att_target_thrust_vec, -1.0f, 1.0f));
    1077             : 
    1078             :     // Normalize the thrust rotation vector
    1079    48403797 :     float thrust_vector_length = thrust_vec_cross.length();
    1080    48403797 :     if (is_zero(thrust_vector_length) || is_zero(thrust_error_angle_rad)) {
    1081    17904744 :         thrust_vec_cross = thrust_vector_up;
    1082             :     } else {
    1083    30499053 :         thrust_vec_cross /= thrust_vector_length;
    1084             :     }
    1085             : 
    1086             :     // thrust_vector_correction is defined relative to the body frame but its axis `thrust_vec_cross` was computed in
    1087             :     // the inertial frame. First rotate it by the inverse of attitude_body to express it back in the body frame
    1088    48403797 :     thrust_vec_cross = attitude_body.inverse() * thrust_vec_cross;
    1089    48403797 :     thrust_vector_correction.from_axis_angle(thrust_vec_cross, thrust_error_angle_rad);
    1090             : 
    1091             :     // calculate the angle error in x and y.
    1092    48403797 :     Vector3f rotation_rad;
    1093    48403797 :     thrust_vector_correction.to_axis_angle(rotation_rad);
    1094    48403797 :     attitude_error_rad.x = rotation_rad.x;
    1095    48403797 :     attitude_error_rad.y = rotation_rad.y;
    1096             : 
    1097             :     // calculate the remaining rotation required after thrust vector is rotated transformed to the body frame
    1098             :     // heading_vector_correction
    1099    48403797 :     Quaternion heading_vec_correction_quat = thrust_vector_correction.inverse() * attitude_body.inverse() * attitude_target;
    1100             : 
    1101             :     // calculate the angle error in z (x and y should be zero here).
    1102    48403797 :     heading_vec_correction_quat.to_axis_angle(rotation_rad);
    1103    48403797 :     attitude_error_rad.z = rotation_rad.z;
    1104    48403797 : }
    1105             : 
    1106             : // calculates the velocity correction from an angle error. The angular velocity has acceleration and
    1107             : // deceleration limits including basic jerk limiting using _input_tc
    1108   106487924 : void AC_AttitudeControl::attitude_command_model(float error_angle, float desired_ang_vel, float& target_ang_vel, float& target_ang_accel, float max_ang_vel, float accel_max, float input_tc, float dt) const
    1109             : {
    1110   106487924 :     if (!is_positive(dt)) {
    1111      229345 :         return;
    1112             :     }
    1113             :     
    1114             :     // protect against divide by zero
    1115   106258579 :     if (!is_positive(accel_max)) {
    1116             :         // no acceleration set so default to 1800 degrees/s²
    1117          11 :         accel_max = radians(1800);
    1118             :     }
    1119             : 
    1120   106258579 :     if (!is_positive(input_tc)) {
    1121             :         // no acceleration set so default to achieve maximum acceleration in 10 clock cycles
    1122    28682806 :         input_tc = dt * 10.0;
    1123             :     }
    1124             : 
    1125   106258579 :     shape_angle_vel_accel( error_angle, desired_ang_vel, 0.0,
    1126             :                         0.0, target_ang_vel, target_ang_accel,
    1127             :                         -max_ang_vel, max_ang_vel, accel_max,
    1128             :                         accel_max / input_tc, dt, true);
    1129   106258579 :     target_ang_vel += target_ang_accel * dt;
    1130             : }
    1131             : 
    1132             : // calculates the expected angular velocity correction from an angle error based on the AC_AttitudeControl settings.
    1133             : // This function can be used to predict the delay associated with angle requests.
    1134     4579903 : void AC_AttitudeControl::command_model_rate_predictor(const Vector2f &error_angle_rad, Vector2f& target_ang_vel_rads, Vector2f& target_ang_accel_rads, float dt) const
    1135             : {
    1136     4579903 :     if (_rate_bf_ff_enabled) {
    1137             :         // translate the roll pitch and yaw acceleration limits to the euler axis
    1138     4579903 :         attitude_command_model(wrap_PI(error_angle_rad.x), 0.0, target_ang_vel_rads.x, target_ang_accel_rads.x, radians(_ang_vel_roll_max_degs), get_accel_roll_max_radss(), _input_tc, _dt_s);
    1139     4579903 :         attitude_command_model(wrap_PI(error_angle_rad.y), 0.0, target_ang_vel_rads.y, target_ang_accel_rads.y, radians(_ang_vel_pitch_max_degs), get_accel_pitch_max_radss(), _input_tc, _dt_s);
    1140             :     } else {
    1141           0 :         const float angleP_roll = _p_angle_roll.kP() * _angle_P_scale.x;
    1142           0 :         const float angleP_pitch = _p_angle_pitch.kP() * _angle_P_scale.y;
    1143           0 :         target_ang_vel_rads.x = angleP_roll * wrap_PI(error_angle_rad.x);
    1144           0 :         target_ang_vel_rads.y = angleP_pitch * wrap_PI(error_angle_rad.y);
    1145             :     }
    1146             :     // Limit the angular velocity correction
    1147     4579903 :     Vector3f ang_vel_rads(target_ang_vel_rads.x, target_ang_vel_rads.y, 0.0f);
    1148     4579903 :     ang_vel_limit(ang_vel_rads, radians(_ang_vel_roll_max_degs), radians(_ang_vel_pitch_max_degs), 0.0f);
    1149             : 
    1150     4579903 :     target_ang_vel_rads.x = ang_vel_rads.x;
    1151     4579903 :     target_ang_vel_rads.y = ang_vel_rads.y;
    1152     4579903 : }
    1153             : 
    1154             : // scale I to represent the current angle P
    1155           0 : void AC_AttitudeControl::scale_I_to_angle_P()
    1156             : {
    1157             :     Vector3f i_scale{
    1158           0 :         _p_angle_roll.kP() * _angle_P_scale.x,
    1159           0 :         _p_angle_pitch.kP() * _angle_P_scale.y,
    1160           0 :         _p_angle_yaw.kP() * _angle_P_scale.z
    1161           0 :     };
    1162           0 :     set_I_scale_mult(i_scale);
    1163           0 : }
    1164             : 
    1165             : // perform any required parameter conversions
    1166        1285 : void AC_AttitudeControl::convert_parameters()
    1167             : {
    1168             :     // PARAMETER_CONVERSION - Added: Jan-2026 for ArduPilot-4.7
    1169             : 
    1170             :     // return immediately if no conversion is needed
    1171        1285 :     if (_angle_max_deg.configured()) {
    1172          29 :         return;
    1173             :     }
    1174             : 
    1175             : #if APM_BUILD_TYPE(APM_BUILD_ArduPlane)
    1176             :     static const AP_Param::ConversionInfo conversion_info_001[] = {
    1177             :         { 205, 10, AP_PARAM_INT16, "Q_A_ANGLE_MAX" },       // ANGLE_MAX moved to Q_A_ANGLE_MAX
    1178             :         { 108, 128, AP_PARAM_FLOAT, "Q_A_RATE_WPY_MAX" },   // SLEW_YAW moved to RATE_WPY_MAX in deg/s
    1179             :         { 108, 256, AP_PARAM_FLOAT, "Q_A_ACC_Y_MAX" },      // ACCEL_Y_MAX moved to ACC_Y_MAX in deg/s/s
    1180             :         { 108, 384, AP_PARAM_FLOAT, "Q_A_ACC_R_MAX" },      // ACCEL_R_MAX moved to ACC_R_MAX in deg/s/s
    1181             :         { 108, 448, AP_PARAM_FLOAT, "Q_A_ACC_P_MAX" },      // ACCEL_P_MAX moved to ACC_P_MAX in deg/s/s
    1182             :     };
    1183             : #elif APM_BUILD_TYPE(APM_BUILD_ArduSub)
    1184             :     static const AP_Param::ConversionInfo conversion_info_001[] = {
    1185             :         { 167, 0, AP_PARAM_INT16, "ATC_ANGLE_MAX" },      // ANGLE_MAX moved to ATC_ANGLE_MAX
    1186             :         { 53, 128, AP_PARAM_FLOAT, "ATC_RATE_WPY_MAX" },  // SLEW_YAW moved to RATE_WPY_MAX in deg/s
    1187             :         { 53, 256, AP_PARAM_FLOAT, "ATC_ACC_Y_MAX" },     // ACCEL_Y_MAX moved to ACC_Y_MAX in deg/s/s
    1188             :         { 53, 384, AP_PARAM_FLOAT, "ATC_ACC_R_MAX" },     // ACCEL_R_MAX moved to ACC_R_MAX in deg/s/s
    1189             :         { 53, 448, AP_PARAM_FLOAT, "ATC_ACC_P_MAX" },     // ACCEL_P_MAX moved to ACC_P_MAX in deg/s/s
    1190             :     };
    1191             : #else
    1192             :     static const AP_Param::ConversionInfo conversion_info_001[] = {
    1193             :         { 34, 0, AP_PARAM_INT16, "ATC_ANGLE_MAX" },       // ANGLE_MAX moved to ATC_ANGLE_MAX
    1194             :         { 102, 128, AP_PARAM_FLOAT, "ATC_RATE_WPY_MAX" }, // SLEW_YAW moved to RATE_WPY_MAX in deg/s
    1195             :         { 102, 256, AP_PARAM_FLOAT, "ATC_ACC_Y_MAX" },    // ACCEL_Y_MAX moved to ACC_Y_MAX in deg/s/s
    1196             :         { 102, 384, AP_PARAM_FLOAT, "ATC_ACC_R_MAX" },    // ACCEL_R_MAX moved to ACC_R_MAX in deg/s/s
    1197             :         { 102, 448, AP_PARAM_FLOAT, "ATC_ACC_P_MAX" },    // ACCEL_P_MAX moved to ACC_P_MAX in deg/s/s
    1198             :     };
    1199             : #endif
    1200        1256 :     AP_Param::convert_old_parameters_scaled(conversion_info_001, ARRAY_SIZE(conversion_info_001), 0.01, 0);
    1201             : }
    1202             : 
    1203             : // Limits angular velocity in roll, pitch, and yaw.
    1204             : // Roll and pitch are coupled: when both limits are set, they are scaled
    1205             : // proportionally as a 2D vector, preserving their ratio. Yaw is clamped independently
    1206    37634590 : void AC_AttitudeControl::ang_vel_limit(Vector3f& euler_rad, float ang_vel_roll_max_rads, float ang_vel_pitch_max_rads, float ang_vel_yaw_max_rads) const
    1207             : {
    1208    37634590 :     if (is_zero(ang_vel_roll_max_rads) || is_zero(ang_vel_pitch_max_rads)) {
    1209    34625565 :         if (!is_zero(ang_vel_roll_max_rads)) {
    1210           0 :             euler_rad.x = constrain_float(euler_rad.x, -ang_vel_roll_max_rads, ang_vel_roll_max_rads);
    1211             :         }
    1212    34625565 :         if (!is_zero(ang_vel_pitch_max_rads)) {
    1213           0 :             euler_rad.y = constrain_float(euler_rad.y, -ang_vel_pitch_max_rads, ang_vel_pitch_max_rads);
    1214             :         }
    1215             :     } else {
    1216     3009025 :         const Vector2f thrust_vector_ang_vel(euler_rad.x / ang_vel_roll_max_rads, euler_rad.y / ang_vel_pitch_max_rads);
    1217     3009025 :         float thrust_vector_length = thrust_vector_ang_vel.length();
    1218     3009025 :         if (thrust_vector_length > 1.0f) {
    1219        1171 :             euler_rad.x = thrust_vector_ang_vel.x * ang_vel_roll_max_rads / thrust_vector_length;
    1220        1171 :             euler_rad.y = thrust_vector_ang_vel.y * ang_vel_pitch_max_rads / thrust_vector_length;
    1221             :         }
    1222             :     }
    1223    37634590 :     if (!is_zero(ang_vel_yaw_max_rads)) {
    1224     3217027 :         euler_rad.z = constrain_float(euler_rad.z, -ang_vel_yaw_max_rads, ang_vel_yaw_max_rads);
    1225             :     }
    1226    37634590 : }
    1227             : 
    1228             : // translates body frame rotation rates and acceleration limits to the euler axis
    1229    33562520 : Vector3f AC_AttitudeControl::body_to_euler_limit(const Quaternion &att, const Vector3f &body_limit)
    1230             : {
    1231    33562520 :     if (!is_positive(body_limit.x) || !is_positive(body_limit.y) || !is_positive(body_limit.z)) {
    1232    14588273 :         return Vector3f { body_limit };
    1233             :     }
    1234             : 
    1235    18974247 :     const float phi = att.get_euler_roll();
    1236    18974247 :     const float theta = att.get_euler_pitch();
    1237             : 
    1238    18974247 :     const float sin_phi = constrain_float(fabsf(sinf(phi)), 0.1f, 1.0f);
    1239    18974247 :     const float cos_phi = constrain_float(fabsf(cosf(phi)), 0.1f, 1.0f);
    1240    18974247 :     const float sin_theta = constrain_float(fabsf(sinf(theta)), 0.1f, 1.0f);
    1241    18974247 :     const float cos_theta = constrain_float(fabsf(cosf(theta)), 0.1f, 1.0f);
    1242             : 
    1243    18974247 :     return Vector3f {
    1244    18974247 :         body_limit.x,
    1245    18974247 :         MIN(body_limit.y / cos_phi, body_limit.z / sin_phi),
    1246    18974247 :         MIN(MIN(body_limit.x / sin_theta, body_limit.y / (sin_phi * cos_theta)), body_limit.z / (cos_phi * cos_theta))
    1247    18974247 :     };
    1248             : }
    1249             : 
    1250             : // Sets attitude target to vehicle attitude and sets all rates to zero
    1251             : // If reset_rate is false rates are not reset to allow the rate controllers to run
    1252       16271 : void AC_AttitudeControl::reset_target_and_rate(bool reset_rate)
    1253             : {
    1254             :     // move attitude target to current attitude
    1255       16271 :     _ahrs.get_quat_body_to_ned(_attitude_target);
    1256       16271 :     _attitude_target.to_euler(_euler_angle_target_rad);
    1257             : 
    1258       16271 :     if (reset_rate) {
    1259       14963 :         _ang_vel_target_rads.zero();
    1260       14963 :         _ang_accel_target_rads.zero();
    1261       14963 :         _euler_rate_target_rads.zero();
    1262             :     }
    1263       16271 : }
    1264             : 
    1265             : // Sets yaw target to vehicle heading and sets yaw rate to zero
    1266             : // If reset_rate is false rates are not reset to allow the rate controllers to run
    1267    11086303 : void AC_AttitudeControl::reset_yaw_target_and_rate(bool reset_rate)
    1268             : {
    1269             :     // move attitude target to current heading
    1270    11086303 :     float yaw_shift = _ahrs.yaw - _euler_angle_target_rad.z;
    1271    11086303 :     Quaternion _attitude_target_update;
    1272    11086303 :     _attitude_target_update.from_axis_angle(Vector3f{0.0f, 0.0f, yaw_shift});
    1273    11086303 :     _attitude_target = _attitude_target_update * _attitude_target;
    1274             : 
    1275    11086303 :     if (reset_rate) {
    1276             :         // set yaw rate to zero
    1277     9913702 :         _euler_rate_target_rads.z = 0.0f;
    1278     9913702 :         _ang_accel_target_rads.z = 0.0;
    1279             : 
    1280             :         // Convert euler angle derivative of desired attitude into a body-frame angular velocity vector for feedforward
    1281     9913702 :         euler_derivative_to_body(_attitude_target, _euler_rate_target_rads, _ang_vel_target_rads);
    1282             :     }
    1283    11086303 : }
    1284             : 
    1285             : // Shifts the target attitude to maintain the current error in the event of an EKF reset
    1286        3590 : void AC_AttitudeControl::inertial_frame_reset()
    1287             : {
    1288             :     // Retrieve quaternion body attitude
    1289        3590 :     Quaternion attitude_body;
    1290        3590 :     _ahrs.get_quat_body_to_ned(attitude_body);
    1291             : 
    1292             :     // Recalculate the target quaternion
    1293        3590 :     _attitude_target = attitude_body * _attitude_ang_error;
    1294             : 
    1295             :     // calculate the attitude target euler angles
    1296        3590 :     _attitude_target.to_euler(_euler_angle_target_rad);
    1297        3590 : }
    1298             : 
    1299             : // euler_derivative_to_body - transform euler angle derivative to body-frame
    1300             : // Converts euler derivatives (rate, acceleration, etc.) to body-frame equivalents.
    1301             : // The same transformation applies regardless of derivative order.
    1302             : // Uses the kinematic relationship for 321 (yaw-pitch-roll) euler sequence.
    1303    43492745 : void AC_AttitudeControl::euler_derivative_to_body(const Quaternion& att, const Vector3f& euler_derivative_rads, Vector3f& body_derivative_rads)
    1304             : {
    1305    43492745 :     const float theta = att.get_euler_pitch();
    1306    43492745 :     const float phi = att.get_euler_roll();
    1307             : 
    1308    43492745 :     const float sin_theta = sinf(theta);
    1309    43492745 :     const float cos_theta = cosf(theta);
    1310    43492745 :     const float sin_phi = sinf(phi);
    1311    43492745 :     const float cos_phi = cosf(phi);
    1312             : 
    1313    43492745 :     body_derivative_rads.x = euler_derivative_rads.x - sin_theta * euler_derivative_rads.z;
    1314    43492745 :     body_derivative_rads.y = cos_phi * euler_derivative_rads.y + sin_phi * cos_theta * euler_derivative_rads.z;
    1315    43492745 :     body_derivative_rads.z = -sin_phi * euler_derivative_rads.y + cos_theta * cos_phi * euler_derivative_rads.z;
    1316    43492745 : }
    1317             : 
    1318             : // body_to_euler_derivative - transform body-frame derivative to euler angle derivative
    1319             : // Converts body-frame derivatives (rate, acceleration, etc.) to euler equivalents.
    1320             : // The same transformation applies regardless of derivative order.
    1321             : // Uses the kinematic relationship for 321 (yaw-pitch-roll) euler sequence.
    1322             : // Returns false if the vehicle is pitched 90 degrees up or down (gimbal lock)
    1323    35566276 : bool AC_AttitudeControl::body_to_euler_derivative(const Quaternion& att, const Vector3f& body_derivative_rads, Vector3f& euler_derivative_rads)
    1324             : {
    1325    35566276 :     const float theta = att.get_euler_pitch();
    1326    35566276 :     const float phi = att.get_euler_roll();
    1327             : 
    1328    35566276 :     const float sin_theta = sinf(theta);
    1329    35566276 :     const float cos_theta = cosf(theta);
    1330    35566276 :     const float sin_phi = sinf(phi);
    1331    35566276 :     const float cos_phi = cosf(phi);
    1332             : 
    1333             :     // When the vehicle pitches all the way up or all the way down, the euler angles become discontinuous. In this case, we just return false.
    1334    35566276 :     if (is_zero(cos_theta)) {
    1335         382 :         return false;
    1336             :     }
    1337             : 
    1338    35565894 :     euler_derivative_rads.x = body_derivative_rads.x + sin_phi * (sin_theta / cos_theta) * body_derivative_rads.y + cos_phi * (sin_theta / cos_theta) * body_derivative_rads.z;
    1339    35565894 :     euler_derivative_rads.y = cos_phi * body_derivative_rads.y - sin_phi * body_derivative_rads.z;
    1340    35565894 :     euler_derivative_rads.z = (sin_phi / cos_theta) * body_derivative_rads.y + (cos_phi / cos_theta) * body_derivative_rads.z;
    1341    35565894 :     return true;
    1342             : }
    1343             : 
    1344             : // Update rate_target_ang_vel using attitude_error_rot_vec_rad
    1345    32956174 : Vector3f AC_AttitudeControl::update_ang_vel_target_from_att_error(const Vector3f &attitude_error_rot_vec_rad)
    1346             : {
    1347    32956174 :     Vector3f rate_target_ang_vel;
    1348             : 
    1349             :     // Compute the roll angular velocity demand from the roll angle error
    1350    32956174 :     const float angleP_roll = _p_angle_roll.kP() * _angle_P_scale.x;
    1351    32956174 :     if (_use_sqrt_controller && !is_zero(get_accel_roll_max_radss())) {
    1352    32765838 :         rate_target_ang_vel.x = sqrt_controller(attitude_error_rot_vec_rad.x, angleP_roll, constrain_float(get_accel_roll_max_radss() / 2.0f, AC_ATTITUDE_ACCEL_RP_CONTROLLER_MIN_RADSS, AC_ATTITUDE_ACCEL_RP_CONTROLLER_MAX_RADSS), _dt_s);
    1353             :     } else {
    1354      190336 :         rate_target_ang_vel.x = angleP_roll * attitude_error_rot_vec_rad.x;
    1355             :     }
    1356             : 
    1357             :     // Compute the pitch angular velocity demand from the pitch angle error
    1358    32956174 :     const float angleP_pitch = _p_angle_pitch.kP() * _angle_P_scale.y;
    1359    32956174 :     if (_use_sqrt_controller && !is_zero(get_accel_pitch_max_radss())) {
    1360    32709253 :         rate_target_ang_vel.y = sqrt_controller(attitude_error_rot_vec_rad.y, angleP_pitch, constrain_float(get_accel_pitch_max_radss() / 2.0f, AC_ATTITUDE_ACCEL_RP_CONTROLLER_MIN_RADSS, AC_ATTITUDE_ACCEL_RP_CONTROLLER_MAX_RADSS), _dt_s);
    1361             :     } else {
    1362      246921 :         rate_target_ang_vel.y = angleP_pitch * attitude_error_rot_vec_rad.y;
    1363             :     }
    1364             : 
    1365             :     // Compute the yaw angular velocity demand from the yaw angle error
    1366    32956174 :     const float angleP_yaw = _p_angle_yaw.kP() * _angle_P_scale.z;
    1367    32956174 :     if (_use_sqrt_controller && !is_zero(get_accel_yaw_max_radss())) {
    1368    32582657 :         rate_target_ang_vel.z = sqrt_controller(attitude_error_rot_vec_rad.z, angleP_yaw, constrain_float(get_accel_yaw_max_radss() / 2.0f, AC_ATTITUDE_ACCEL_Y_CONTROLLER_MIN_RADSS, AC_ATTITUDE_ACCEL_Y_CONTROLLER_MAX_RADSS), _dt_s);
    1369             :     } else {
    1370      373517 :         rate_target_ang_vel.z = angleP_yaw * attitude_error_rot_vec_rad.z;
    1371             :     }
    1372             : 
    1373    32956174 :     return rate_target_ang_vel;
    1374             : }
    1375             : 
    1376             : // Enable or disable body-frame feed forward
    1377           0 : void AC_AttitudeControl::accel_limiting(bool enable_limits)
    1378             : {
    1379           0 :     if (enable_limits) {
    1380             :         // If enabling limits, reload from eeprom or set to defaults
    1381           0 :         if (is_zero(_accel_roll_max_degss)) {
    1382           0 :             _accel_roll_max_degss.load();
    1383             :         }
    1384           0 :         if (is_zero(_accel_pitch_max_degss)) {
    1385           0 :             _accel_pitch_max_degss.load();
    1386             :         }
    1387           0 :         if (is_zero(_accel_yaw_max_degss)) {
    1388           0 :             _accel_yaw_max_degss.load();
    1389             :         }
    1390             :     } else {
    1391           0 :         _accel_roll_max_degss.set(0.0f);
    1392           0 :         _accel_pitch_max_degss.set(0.0f);
    1393           0 :         _accel_yaw_max_degss.set(0.0f);
    1394             :     }
    1395           0 : }
    1396             : 
    1397             : // Returns maximum allowable tilt angle (in centidegrees) for pilot input when in altitude hold mode.
    1398             : // See get_althold_lean_angle_max_rad() for full details.
    1399      843411 : float AC_AttitudeControl::get_althold_lean_angle_max_cd() const
    1400             : {
    1401             :     // convert to centi-degrees for public interface
    1402      843411 :     return rad_to_cd(get_althold_lean_angle_max_rad());
    1403             : }
    1404             : 
    1405             : // Returns maximum allowable tilt angle (in radians) for pilot input when in altitude hold mode.
    1406             : // Used to limit lean angle based on available thrust margin, prioritising altitude stability.
    1407    31746108 : float AC_AttitudeControl::get_althold_lean_angle_max_rad() const
    1408             : {
    1409    31746108 :     return MAX(_althold_lean_angle_max_rad, radians(AC_ATTITUDE_CONTROL_ANGLE_LIMIT_MIN));
    1410             : }
    1411             : 
    1412             : // Return configured tilt angle limit in centidegrees
    1413    10678463 : float AC_AttitudeControl::lean_angle_max_cd() const
    1414             : {
    1415    10678463 :     return constrain_float(_angle_max_deg.get(), AC_ATTITUDE_CONTROL_ANGLE_LIMIT_MIN, AC_ATTITUDE_CONTROL_ANGLE_LIMIT_MAX) * 100;
    1416             : }
    1417             : 
    1418             : // Return configured tilt angle limit in radians
    1419    55927217 : float AC_AttitudeControl::lean_angle_max_rad() const
    1420             : {
    1421    55927217 :     return radians(constrain_float(_angle_max_deg.get(), AC_ATTITUDE_CONTROL_ANGLE_LIMIT_MIN, AC_ATTITUDE_CONTROL_ANGLE_LIMIT_MAX));
    1422             : }
    1423             : 
    1424             : // Return roll rate step size in centidegrees/s that results in maximum output after 4 time steps
    1425         786 : float AC_AttitudeControl::max_rate_step_bf_roll()
    1426             : {
    1427         786 :     float dt_average = AP::scheduler().get_filtered_loop_time();
    1428         786 :     float alpha = MIN(get_rate_roll_pid().get_filt_E_alpha(dt_average), get_rate_roll_pid().get_filt_D_alpha(dt_average));
    1429         786 :     float alpha_remaining = 1 - alpha;
    1430             :     // todo: When a thrust_max is available we should replace 0.5f with 0.5f * _motors.thrust_max
    1431         786 :     float throttle_hover = constrain_float(_motors.get_throttle_hover(), 0.1f, 0.5f);
    1432         786 :     float rate_max = 2.0f * throttle_hover * AC_ATTITUDE_RATE_RP_CONTROLLER_OUT_MAX / ((alpha_remaining * alpha_remaining * alpha_remaining * alpha * get_rate_roll_pid().kD()) / _dt_s + get_rate_roll_pid().kP());
    1433         786 :     if (is_positive(_ang_vel_roll_max_degs)) {
    1434         160 :         rate_max = MIN(rate_max, get_ang_vel_roll_max_rads());
    1435             :     }
    1436         786 :     return rate_max;
    1437             : }
    1438             : 
    1439             : // Return pitch rate step size in centidegrees/s that results in maximum output after 4 time steps
    1440         772 : float AC_AttitudeControl::max_rate_step_bf_pitch()
    1441             : {
    1442         772 :     const float dt_average = AP::scheduler().get_filtered_loop_time();
    1443         772 :     const float alpha = MIN(get_rate_pitch_pid().get_filt_E_alpha(dt_average), get_rate_pitch_pid().get_filt_D_alpha(dt_average));
    1444         772 :     const float alpha_remaining = 1 - alpha;
    1445             :     // todo: When a thrust_max is available we should replace 0.5f with 0.5f * _motors.thrust_max
    1446         772 :     const float throttle_hover = constrain_float(_motors.get_throttle_hover(), 0.1f, 0.5f);
    1447         772 :     float rate_max = 2.0f * throttle_hover * AC_ATTITUDE_RATE_RP_CONTROLLER_OUT_MAX / ((alpha_remaining * alpha_remaining * alpha_remaining * alpha * get_rate_pitch_pid().kD()) / _dt_s + get_rate_pitch_pid().kP());
    1448         772 :     if (is_positive(_ang_vel_pitch_max_degs)) {
    1449         156 :         rate_max = MIN(rate_max, get_ang_vel_pitch_max_rads());
    1450             :     }
    1451         772 :     return rate_max;
    1452             : }
    1453             : 
    1454             : // Return yaw rate step size in centidegrees/s that results in maximum output after 4 time steps
    1455        2806 : float AC_AttitudeControl::max_rate_step_bf_yaw()
    1456             : {
    1457        2806 :     const float dt_average = AP::scheduler().get_filtered_loop_time();
    1458        2806 :     const float alpha = MIN(get_rate_yaw_pid().get_filt_E_alpha(dt_average), get_rate_yaw_pid().get_filt_D_alpha(dt_average));
    1459        2806 :     const float alpha_remaining = 1 - alpha;
    1460             :     // todo: When a thrust_max is available we should replace 0.5f with 0.5f * _motors.thrust_max
    1461        2806 :     const float throttle_hover = constrain_float(_motors.get_throttle_hover(), 0.1f, 0.5f);
    1462        2806 :     float rate_max = 2.0f * throttle_hover * AC_ATTITUDE_RATE_YAW_CONTROLLER_OUT_MAX / ((alpha_remaining * alpha_remaining * alpha_remaining * alpha * get_rate_yaw_pid().kD()) / _dt_s + get_rate_yaw_pid().kP());
    1463        2806 :     if (is_positive(_ang_vel_yaw_max_degs)) {
    1464         212 :         rate_max = MIN(rate_max, get_ang_vel_yaw_max_rads());
    1465             :     }
    1466        2806 :     return rate_max;
    1467             : }
    1468             : 
    1469       99876 : bool AC_AttitudeControl::pre_arm_checks(const char *param_prefix,
    1470             :                                         char *failure_msg,
    1471             :                                         const uint8_t failure_msg_len)
    1472             : {
    1473             :     // validate AC_P members:
    1474             :     const struct {
    1475             :         const char *pid_name;
    1476             :         AC_P &p;
    1477       99876 :     } ps[] = {
    1478       99876 :         { "ANG_PIT", get_angle_pitch_p() },
    1479       99876 :         { "ANG_RLL", get_angle_roll_p() },
    1480       99876 :         { "ANG_YAW", get_angle_yaw_p() }
    1481       99876 :     };
    1482      399504 :     for (uint8_t i=0; i<ARRAY_SIZE(ps); i++) {
    1483             :         // all AC_P's must have a positive P value:
    1484      299628 :         if (!is_positive(ps[i].p.kP())) {
    1485           0 :             hal.util->snprintf(failure_msg, failure_msg_len, "%s_%s_P must be > 0", param_prefix, ps[i].pid_name);
    1486           0 :             return false;
    1487             :         }
    1488             :     }
    1489             : 
    1490             :     // validate AC_PID members:
    1491             :     const struct {
    1492             :         const char *pid_name;
    1493             :         AC_PID &pid;
    1494       99876 :     } pids[] = {
    1495       99876 :         { "RAT_RLL", get_rate_roll_pid() },
    1496       99876 :         { "RAT_PIT", get_rate_pitch_pid() },
    1497       99876 :         { "RAT_YAW", get_rate_yaw_pid() },
    1498       99876 :     };
    1499      399504 :     for (uint8_t i=0; i<ARRAY_SIZE(pids); i++) {
    1500             :         // if the PID has a positive FF then we just ensure kP and
    1501             :         // kI aren't negative
    1502      299628 :         AC_PID &pid = pids[i].pid;
    1503      299628 :         const char *pid_name = pids[i].pid_name;
    1504      299628 :         if (is_positive(pid.ff())) {
    1505             :             // kP and kI must be non-negative:
    1506        4702 :             if (is_negative(pid.kP())) {
    1507           0 :                 hal.util->snprintf(failure_msg, failure_msg_len, "%s_%s_P must be >= 0", param_prefix, pid_name);
    1508           0 :                 return false;
    1509             :             }
    1510        4702 :             if (is_negative(pid.kI())) {
    1511           0 :                 hal.util->snprintf(failure_msg, failure_msg_len, "%s_%s_I must be >= 0", param_prefix, pid_name);
    1512           0 :                 return false;
    1513             :             }
    1514             :         } else {
    1515             :             // kP and kI must be positive:
    1516      294926 :             if (!is_positive(pid.kP())) {
    1517           0 :                 hal.util->snprintf(failure_msg, failure_msg_len, "%s_%s_P must be > 0", param_prefix, pid_name);
    1518           0 :                 return false;
    1519             :             }
    1520      294926 :             if (!is_positive(pid.kI())) {
    1521           0 :                 hal.util->snprintf(failure_msg, failure_msg_len, "%s_%s_I must be > 0", param_prefix, pid_name);
    1522           0 :                 return false;
    1523             :             }
    1524             :         }
    1525             :         // never allow a negative D term (but zero is allowed)
    1526      299628 :         if (is_negative(pid.kD())) {
    1527           0 :             hal.util->snprintf(failure_msg, failure_msg_len, "%s_%s_D must be >= 0", param_prefix, pid_name);
    1528           0 :             return false;
    1529             :         }
    1530             :     }
    1531             : 
    1532             :     // validate ANGLE_MAX
    1533       99876 :     if (_angle_max_deg.get() < 10 || _angle_max_deg.get() > 80) {
    1534           0 :         hal.util->snprintf(failure_msg, failure_msg_len, "%s_ANGLE_MAX must be >= 10 and <= 80", param_prefix);
    1535           0 :         return false;
    1536             :     }
    1537             : 
    1538       99876 :     return true;
    1539             : }
    1540             : 
    1541             : /*
    1542             :   get the slew rate for roll, pitch and yaw, for oscillation
    1543             :   detection in lua scripts
    1544             : */
    1545        2928 : void AC_AttitudeControl::get_rpy_srate(float &roll_srate, float &pitch_srate, float &yaw_srate)
    1546             : {
    1547        2928 :     roll_srate = get_rate_roll_pid().get_pid_info().slew_rate;
    1548        2928 :     pitch_srate = get_rate_pitch_pid().get_pid_info().slew_rate;
    1549        2928 :     yaw_srate = get_rate_yaw_pid().get_pid_info().slew_rate;
    1550        2928 : }

Generated by: LCOV version 1.14