LCOV - code coverage report
Current view: top level - home/pbarker/rc/ardupilot/ArduPlane - Attitude.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 283 357 79.3 %
Date: 2026-09-09 11:02:08 Functions: 19 20 95.0 %

          Line data    Source code
       1             : #include "Plane.h"
       2             : 
       3             : /*
       4             :   calculate speed scaling number for control surfaces. This is applied
       5             :   to PIDs to change the scaling of the PID with speed. At high speed
       6             :   we move the surfaces less, and at low speeds we move them more.
       7             :  */
       8      547097 : float Plane::calc_speed_scaler(void)
       9             : {
      10             :     float aspeed, speed_scaler;
      11      547097 :     if (ahrs.airspeed_EAS(aspeed)) {
      12      542890 :         if (aspeed > auto_state.highest_airspeed && arming.is_armed_and_safety_off()) {
      13       28536 :             auto_state.highest_airspeed = aspeed;
      14             :         }
      15             :         // ensure we have scaling over the full configured airspeed
      16      542890 :         const float airspeed_min = MAX(aparm.airspeed_min, MIN_AIRSPEED_MIN);
      17      542890 :         const float scale_min = MIN(0.5, g.scaling_speed / (2.0 * aparm.airspeed_max));
      18             : #if HAL_QUADPLANE_ENABLED
      19             :         float scale_max;
      20      542890 :         if (quadplane.is_flying_vtol()) {
      21             :             // vtol aircraft can generate excessive large aero control surface deflections
      22             :             // during VTOL operation because the low airspeed can create large speed scaler
      23             :             // values at a flight condition where aero control surfaces have little influence.
      24       79175 :             const float stall_airspeed_1g = is_positive(aparm.airspeed_stall)
      25       79175 :                                             ? aparm.airspeed_stall : 0.7f * airspeed_min;
      26       79175 :             scale_max = g.scaling_speed / stall_airspeed_1g;
      27             :         } else {
      28             :             // use the legacy scaling limit for FW flight
      29      463715 :             scale_max = MAX(2.0, g.scaling_speed / (0.7 * airspeed_min));
      30             :         }
      31             : #else
      32             :         const float scale_max = MAX(2.0, g.scaling_speed / (0.7 * airspeed_min));
      33             : #endif
      34      542890 :         if (aspeed > 0.0001f) {
      35      541676 :             speed_scaler = g.scaling_speed / aspeed;
      36             :         } else {
      37        1214 :             speed_scaler = scale_max;
      38             :         }
      39      542890 :         speed_scaler = constrain_float(speed_scaler, scale_min, scale_max);
      40             : 
      41             : #if HAL_QUADPLANE_ENABLED
      42      542890 :         if ((quadplane.in_vtol_mode() || quadplane.in_assisted_flight()) && arming.is_armed_and_safety_off()) {
      43             :             // when in VTOL modes limit surface movement at low speed to prevent instability
      44       88733 :             float threshold = airspeed_min * 0.5;
      45       88733 :             if (aspeed < threshold) {
      46       68104 :                 float new_scaler = linear_interpolate(0.001, g.scaling_speed / threshold, aspeed, 0, threshold);
      47       68104 :                 speed_scaler = MIN(speed_scaler, new_scaler);
      48             : 
      49             :                 // we also decay the integrator to prevent an integrator from before
      50             :                 // we were at low speed persistent at high speed
      51       68104 :                 rollController.decay_I();
      52       68104 :                 pitchController.decay_I();
      53       68104 :                 yawController.decay_I();
      54             :             }
      55             :         }
      56             : #endif
      57        4207 :     } else if (arming.is_armed_and_safety_off()) {
      58             :         // scale assumed surface movement using throttle output
      59           0 :         float throttle_out = MAX(SRV_Channels::get_output_scaled(SRV_Channel::k_throttle), 1);
      60             :         // we use a fixed value here as changing the trim throttle
      61             :         // value is often done at runtime - and changing that
      62             :         // shouldn't change the speed scaler here (it will change the
      63             :         // effective PID values)
      64           0 :         speed_scaler = sqrtf(AP_PLANE_TRIM_THROTTLE_DEFAULT / throttle_out);
      65             :         // This case is constrained tighter as we don't have real speed info
      66           0 :         speed_scaler = constrain_float(speed_scaler, 0.6f, 1.67f);
      67             :     } else {
      68             :         // no speed estimate and not armed, use a unit scaling
      69        4207 :         speed_scaler = 1;
      70             :     }
      71      567415 :     if (!plane.ahrs.using_airspeed_sensor()  && 
      72      567415 :         (plane.flight_option_enabled(FlightOptions::SURPRESS_TKOFF_SCALING)) &&
      73           0 :         (plane.flight_stage == AP_FixedWing::FlightStage::TAKEOFF)) { //scaling is suppressed during climb phase of automatic takeoffs with no airspeed sensor being used due to problems with inaccurate airspeed estimates
      74           0 :         return MIN(speed_scaler, 1.0f) ;
      75             :     }
      76      547097 :     return speed_scaler;
      77             : }
      78             : 
      79             : /*
      80             :   return true if the current settings and mode should allow for stick mixing
      81             :  */
      82    12689547 : bool Plane::stick_mixing_enabled(void)
      83             : {
      84    12689547 :     if (!rc().has_valid_input()) {
      85             :         // never stick mix without valid RC
      86        3112 :         return false;
      87             :     }
      88             : #if AP_FENCE_ENABLED
      89    12686435 :     const bool stickmixing = fence_stickmixing();
      90             : #else
      91             :     const bool stickmixing = true;
      92             : #endif
      93             : #if HAL_QUADPLANE_ENABLED
      94    13168843 :     if (control_mode == &mode_qrtl &&
      95      482408 :         quadplane.poscontrol.get_state() >= QuadPlane::QPOS_POSITION1) {
      96             :         // user may be repositioning
      97      263938 :         return false;
      98             :     }
      99    12422497 :     if (quadplane.in_vtol_land_poscontrol()) {
     100             :         // user may be repositioning
     101     1178010 :         return false;
     102             :     }
     103             : #endif
     104    11244487 :     if (control_mode->does_auto_throttle() && plane.control_mode->does_auto_navigation()) {
     105             :         // we're in an auto mode. Check the stick mixing flag
     106    16820316 :         if (g.stick_mixing != StickMixing::NONE &&
     107    16820316 :             g.stick_mixing != StickMixing::VTOL_YAW &&
     108             :             stickmixing) {
     109     8380876 :             return true;
     110             :         } else {
     111       29282 :             return false;
     112             :         }
     113             :     }
     114             : 
     115     2834329 :     if (failsafe.rc_failsafe && g.fs_action_short == FS_ACTION_SHORT_FBWA) {
     116             :         // don't do stick mixing in FBWA glide mode
     117           0 :         return false;
     118             :     }
     119             : 
     120             :     // non-auto mode. Always do stick mixing
     121     2834329 :     return true;
     122             : }
     123             : 
     124             : 
     125             : /*
     126             :   this is the main roll stabilization function. It takes the
     127             :   previously set nav_roll calculates roll servo_out to try to
     128             :   stabilize the plane at the given roll
     129             :  */
     130     7541881 : void Plane::stabilize_roll()
     131             : {
     132     7541881 :     if (fly_inverted()) {
     133        5135 :         nav_roll_cd += 18000;
     134             :     }
     135     7541881 :     float roll_out = stabilize_roll_get_roll_out();
     136             : 
     137             : #if AP_PLANE_SYSTEMID_ENABLED
     138     7541881 :     const auto &systemid = plane.g2.systemid;
     139     7541881 :     if (systemid.is_running_fw()) {
     140           0 :         Vector3f offset;
     141           0 :         offset = systemid.get_output_offset();
     142           0 :         roll_out += offset.x * 100.0f;
     143             :     }
     144             : #endif 
     145             : 
     146     7541881 :     SRV_Channels::set_output_scaled(SRV_Channel::k_aileron, roll_out);
     147     7541881 : }
     148             : 
     149     7541881 : float Plane::stabilize_roll_get_roll_out()
     150             : {
     151     7541881 :     const float speed_scaler = get_speed_scaler();
     152             : #if HAL_QUADPLANE_ENABLED
     153     7541881 :     if (!quadplane.use_fw_attitude_controllers()) {
     154             :         // use the VTOL rate for control, to ensure consistency
     155     2195258 :         const auto &pid_info = quadplane.attitude_control->get_rate_roll_pid().get_pid_info();
     156             : 
     157             :         // scale FF to angle P
     158     2195258 :         if (quadplane.option_is_set(QuadPlane::Option::SCALE_FF_ANGLE_P)) {
     159           0 :             const float mc_angR = quadplane.attitude_control->get_angle_roll_p().kP()
     160           0 :                 * quadplane.attitude_control->get_last_angle_P_scale().x;
     161           0 :             if (is_positive(mc_angR)) {
     162           0 :                 rollController.set_ff_scale(MIN(1.0, rollController.get_angle_p() / mc_angR));
     163             :             }
     164             :         }
     165             : 
     166     2195258 :         const float roll_out = rollController.run_rate_control(degrees(pid_info.target), speed_scaler);
     167             :         /* when slaving fixed wing control to VTOL control we need to decay the integrator to prevent
     168             :            opposing integrators balancing between the two controllers
     169             :         */
     170     2195258 :         rollController.decay_I();
     171     2195258 :         return roll_out;
     172             :     }
     173             : #endif
     174             : 
     175     5346623 :     bool disable_integrator = false;
     176     5346623 :     if (control_mode == &mode_stabilize && channel_roll->get_control_in() != 0) {
     177          49 :         disable_integrator = true;
     178             :     }
     179     5346623 :     return rollController.run_angle_control(nav_roll_cd, speed_scaler, disable_integrator,
     180    10693246 :                                         ground_mode && !(plane.flight_option_enabled(FlightOptions::DISABLE_GROUND_PID_SUPPRESSION)));
     181             : }
     182             : 
     183             : /*
     184             :   this is the main pitch stabilization function. It takes the
     185             :   previously set nav_pitch and calculates servo_out values to try to
     186             :   stabilize the plane at the given attitude.
     187             :  */
     188     7540677 : void Plane::stabilize_pitch()
     189             : {
     190     7540677 :     int8_t force_elevator = takeoff_tail_hold();
     191     7540677 :     if (force_elevator != 0) {
     192             :         // we are holding the tail down during takeoff. Just convert
     193             :         // from a percentage to a -4500..4500 centidegree angle
     194           0 :         SRV_Channels::set_output_scaled(SRV_Channel::k_elevator, 45*force_elevator);
     195           0 :         return;
     196             :     }
     197             : 
     198     7540677 :     float pitch_out = stabilize_pitch_get_pitch_out();
     199             : 
     200             : #if AP_PLANE_SYSTEMID_ENABLED
     201     7540677 :     const auto &systemid = plane.g2.systemid;
     202     7540677 :     if (systemid.is_running_fw()) {
     203           0 :         Vector3f offset;
     204           0 :         offset = systemid.get_output_offset();
     205           0 :         pitch_out += offset.y * 100.0f;
     206             :     }
     207             : #endif 
     208             : 
     209     7540677 :     SRV_Channels::set_output_scaled(SRV_Channel::k_elevator, pitch_out);
     210             : }
     211             : 
     212     7540677 : float Plane::stabilize_pitch_get_pitch_out()
     213             : {
     214     7540677 :     const float speed_scaler = get_speed_scaler();
     215             : #if HAL_QUADPLANE_ENABLED
     216     7540677 :     if (!quadplane.use_fw_attitude_controllers()) {
     217             :         // use the VTOL rate for control, to ensure consistency
     218     2195258 :         const auto &pid_info = quadplane.attitude_control->get_rate_pitch_pid().get_pid_info();
     219             : 
     220             :         // scale FF to angle P
     221     2195258 :         if (quadplane.option_is_set(QuadPlane::Option::SCALE_FF_ANGLE_P)) {
     222           0 :             const float mc_angP = quadplane.attitude_control->get_angle_pitch_p().kP()
     223           0 :                 * quadplane.attitude_control->get_last_angle_P_scale().y;
     224           0 :             if (is_positive(mc_angP)) {
     225           0 :                 pitchController.set_ff_scale(MIN(1.0, pitchController.get_angle_p() / mc_angP));
     226             :             }
     227             :         }
     228             : 
     229     2195258 :         const int32_t pitch_out = pitchController.run_rate_control(degrees(pid_info.target), speed_scaler);
     230             :         /* when slaving fixed wing control to VTOL control we need to decay the integrator to prevent
     231             :            opposing integrators balancing between the two controllers
     232             :         */
     233     2195258 :         pitchController.decay_I();
     234     2195258 :         return pitch_out;
     235             :     }
     236             : #endif
     237             :     // if LANDING_FLARE RCx_OPTION switch is set and in FW mode, manual throttle,throttle idle then set pitch to LAND_PITCH_DEG if flight option FORCE_FLARE_ATTITUDE is set
     238             : #if HAL_QUADPLANE_ENABLED
     239     5345419 :     const bool quadplane_in_frwd_transition = quadplane.in_frwd_transition();
     240             : #else
     241             :     const bool quadplane_in_frwd_transition = false;
     242             : #endif
     243             : 
     244     5345419 :     int32_t demanded_pitch = nav_pitch_cd + int32_t(g.pitch_trim * 100.0) + SRV_Channels::get_output_scaled(SRV_Channel::k_throttle) * g.kff_throttle_to_pitch;
     245     5345419 :     bool disable_integrator = false;
     246     5345419 :     if (control_mode == &mode_stabilize && channel_pitch->get_control_in() != 0) {
     247           0 :         disable_integrator = true;
     248             :     }
     249             :     /* force landing pitch if:
     250             :        - flare switch high
     251             :        - throttle stick at zero thrust
     252             :        - in fixed wing non auto-throttle mode
     253             :     */
     254    15566145 :     if (!quadplane_in_frwd_transition &&
     255     4875307 :         !control_mode->is_vtol_mode() &&
     256     4417824 :         !control_mode->does_auto_throttle() &&
     257    10220726 :         flare_mode == FlareMode::ENABLED_PITCH_TARGET &&
     258           0 :         throttle_at_zero()) {
     259           0 :         demanded_pitch = landing.get_pitch_cd();
     260             :     }
     261             : 
     262     5345419 :     return pitchController.run_angle_control(demanded_pitch, speed_scaler, disable_integrator,
     263    10690838 :                                          ground_mode && !(plane.flight_option_enabled(FlightOptions::DISABLE_GROUND_PID_SUPPRESSION)));
     264             : }
     265             : 
     266             : /*
     267             :   this gives the user control of the aircraft in stabilization modes, only used in Stabilize Mode
     268             :   to be moved to mode_stabilize.cpp in future
     269             :  */
     270        1389 : void ModeStabilize::stabilize_stick_mixing_direct()
     271             : {
     272        1389 :     if (!plane.stick_mixing_enabled()) {
     273           0 :         return;
     274             :     }
     275             : #if HAL_QUADPLANE_ENABLED
     276        1389 :     if (!plane.quadplane.allow_stick_mixing()) {
     277           0 :         return;
     278             :     }
     279             : #endif
     280        1389 :     float aileron = SRV_Channels::get_output_scaled(SRV_Channel::k_aileron);
     281        1389 :     aileron = plane.channel_roll->stick_mixing(aileron);
     282        1389 :     SRV_Channels::set_output_scaled(SRV_Channel::k_aileron, aileron);
     283             : 
     284        1389 :     float elevator = SRV_Channels::get_output_scaled(SRV_Channel::k_elevator);
     285        1389 :     elevator = plane.channel_pitch->stick_mixing(elevator);
     286        1389 :     SRV_Channels::set_output_scaled(SRV_Channel::k_elevator, elevator);
     287             : }
     288             : 
     289             : /*
     290             :   this gives the user control of the aircraft in stabilization modes
     291             :   using FBW style controls
     292             :  */
     293     6031906 : void Plane::stabilize_stick_mixing_fbw()
     294             : {
     295     6031906 :     if (!stick_mixing_enabled() ||
     296     5426704 :         control_mode == &mode_acro ||
     297     5426704 :         control_mode == &mode_fbwa ||
     298     4332166 :         control_mode == &mode_autotune ||
     299     4312421 :         control_mode == &mode_fbwb ||
     300     4295248 :         control_mode == &mode_cruise ||
     301             : #if HAL_QUADPLANE_ENABLED
     302     4131854 :         control_mode == &mode_qstabilize ||
     303     4131854 :         control_mode == &mode_qhover ||
     304     4131853 :         control_mode == &mode_qloiter ||
     305     4131853 :         control_mode == &mode_qland ||
     306     4131426 :         control_mode == &mode_qacro ||
     307             : #if QAUTOTUNE_ENABLED
     308     4131426 :         control_mode == &mode_qautotune ||
     309             : #endif
     310    15587390 :         !quadplane.allow_stick_mixing() ||
     311             : #endif  // HAL_QUADPLANE_ENABLED
     312     4128780 :         control_mode == &mode_training) {
     313     1903126 :         return;
     314             :     }
     315             :     // do FBW style roll stick mixing. We don't treat it linearly however. For
     316             :     // inputs up to half the maximum, we use linear addition to the nav_roll.
     317             :     // Above that it goes non-linear and ends up as 2x the maximum, to ensure
     318             :     // that the user can direct the plane in any direction with stick mixing.
     319     4128780 :     float roll_input = channel_roll->norm_input_dz();
     320     4128780 :     if (roll_input > 0.5f) {
     321         484 :         roll_input = (3*roll_input - 1);
     322     4128296 :     } else if (roll_input < -0.5f) {
     323           0 :         roll_input = (3*roll_input + 1);
     324             :     }
     325     4128780 :     nav_roll_cd += roll_input * roll_limit_cd;
     326     4128780 :     nav_roll_cd = constrain_int32(nav_roll_cd, -roll_limit_cd, roll_limit_cd);
     327             : 
     328     4128780 :     if (plane.g.stick_mixing == StickMixing::FBW_NO_PITCH) {
     329           0 :         return;
     330             :     }
     331     4128780 :     if ((control_mode == &mode_loiter) && (plane.flight_option_enabled(FlightOptions::ENABLE_LOITER_ALT_CONTROL))) {
     332             :         // loiter is using altitude control based on the pitch stick, don't use it again here
     333        8407 :         return;
     334             :     }
     335             : 
     336             :     // do FBW-A style pitch stick mixing. Use the same non-linear approach as
     337             :     // roll, but based on the pitch range rather than the limits to ensure full
     338             :     // stick deflection can override either limit regardless of current
     339             :     // attitude.
     340     4120373 :     float pitch_input = channel_pitch->norm_input_dz();
     341     4120373 :     if (pitch_input > 0.5f) {
     342           0 :         pitch_input = (3*pitch_input - 1);
     343     4120373 :     } else if (pitch_input < -0.5f) {
     344         228 :         pitch_input = (3*pitch_input + 1);
     345             :     }
     346     4120373 :     if (fly_inverted()) {
     347         875 :         pitch_input = -pitch_input;
     348             :     }
     349     4120373 :     const float pitch_range = aparm.pitch_limit_max.get() - pitch_limit_min;
     350     4120373 :     nav_pitch_cd += pitch_input * (pitch_range / 2.0f) * 100;
     351     4120373 :     nav_pitch_cd = constrain_int32(nav_pitch_cd, pitch_limit_min*100, aparm.pitch_limit_max.get()*100);
     352             : }
     353             : 
     354             : 
     355             : /*
     356             :   stabilize the yaw axis. There are 3 modes of operation:
     357             : 
     358             :     - hold a specific heading with ground steering
     359             :     - rate controlled with ground steering
     360             :     - yaw control for coordinated flight    
     361             :  */
     362     6297553 : void Plane::stabilize_yaw()
     363             : {
     364     6297553 :     bool ground_steering = false;
     365     6297553 :     if (landing.is_flaring()) {
     366             :         // in flaring then enable ground steering
     367      123090 :         ground_steering = true;
     368             :     } else {
     369             :         // otherwise use ground steering when no input control and we
     370             :         // are below the GROUND_STEER_ALT
     371    12325549 :         ground_steering = (channel_roll->get_control_in() == 0 && 
     372     6151086 :                                             fabsf(relative_altitude) < g.ground_steer_alt);
     373     6174463 :         if (!landing.is_ground_steering_allowed()) {
     374             :             // don't use ground steering on landing approach
     375       33795 :             ground_steering = false;
     376             :         }
     377             :     }
     378             : 
     379             : 
     380             :     /*
     381             :       first calculate steering for a nose or tail
     382             :       wheel. We use "course hold" mode for the rudder when either performing
     383             :       a flare (when the wings are held level) or when in course hold in
     384             :       FBWA mode (when we are below GROUND_STEER_ALT)
     385             :      */
     386     6297553 :     float steering_output = 0.0;
     387    12472016 :     if (landing.is_flaring() ||
     388     6174463 :         (steer_state.hold_course_cd != -1 && ground_steering)) {
     389      123090 :         steering_output = calc_nav_yaw_course();
     390     6174463 :     } else if (ground_steering) {
     391           0 :         steering_output = calc_nav_yaw_ground();
     392             :     }
     393             : 
     394             :     /*
     395             :       now calculate rudder for the rudder
     396             :      */
     397     6297553 :     const float rudder_output = calc_nav_yaw_coordinated();
     398             : 
     399     6297553 :     if (!ground_steering) {
     400             :         // Not doing ground steering, output rudder on steering channel
     401     6174463 :         SRV_Channels::set_output_scaled(SRV_Channel::k_rudder, rudder_output);
     402     6174463 :         SRV_Channels::set_output_scaled(SRV_Channel::k_steering, rudder_output);
     403             : 
     404      123090 :     } else if (!SRV_Channels::function_assigned(SRV_Channel::k_steering)) {
     405             :         // Ground steering active but no steering output configured, output steering on rudder channel
     406      123090 :         SRV_Channels::set_output_scaled(SRV_Channel::k_rudder, steering_output);
     407      123090 :         SRV_Channels::set_output_scaled(SRV_Channel::k_steering, steering_output);
     408             : 
     409             :     } else {
     410             :         // Ground steering with both steering and rudder channels
     411           0 :         SRV_Channels::set_output_scaled(SRV_Channel::k_rudder, rudder_output);
     412           0 :         SRV_Channels::set_output_scaled(SRV_Channel::k_steering, steering_output);
     413             :     }
     414             : 
     415             : #if HAL_QUADPLANE_ENABLED
     416             :     // possibly recover from a spin
     417     6297553 :     quadplane.assist.output_spin_recovery();
     418             : #endif
     419     6297553 : }
     420             : 
     421             : /*
     422             :   main stabilization function for all 3 axes
     423             :  */
     424     8377547 : void Plane::stabilize()
     425             : {
     426     8377547 :     uint32_t now = AP_HAL::millis();
     427             : #if HAL_QUADPLANE_ENABLED
     428     8377547 :     if (quadplane.available()) {
     429     6504054 :         quadplane.transition->set_FW_roll_pitch(nav_pitch_cd, nav_roll_cd);
     430             :     }
     431             : #endif
     432             : 
     433             : #if AP_PLANE_SYSTEMID_ENABLED
     434     8377547 :     if (plane.g2.systemid.is_running_fw()) {
     435           0 :         plane.g2.systemid.fw_update();
     436             :     }
     437             : #endif
     438             : 
     439     8377547 :     if (now - last_stabilize_ms > 2000) {
     440             :         // if we haven't run the rate controllers for 2 seconds then reset
     441          92 :         control_mode->reset_controllers();
     442             :     }
     443     8377547 :     last_stabilize_ms = now;
     444             : 
     445     8377547 :     if (control_mode == &mode_training ||
     446     8376167 :             control_mode == &mode_manual) {
     447      615622 :         plane.control_mode->run();
     448             : #if AP_SCRIPTING_ENABLED
     449     7761925 :     } else if (nav_scripting_active()) {
     450             :         // scripting is in control of roll and pitch rates and throttle
     451        6657 :         const float speed_scaler = get_speed_scaler();
     452        6657 :         const float aileron = rollController.run_rate_control(nav_scripting.roll_rate_dps, speed_scaler);
     453        6657 :         const float elevator = pitchController.run_rate_control(nav_scripting.pitch_rate_dps, speed_scaler);
     454        6657 :         SRV_Channels::set_output_scaled(SRV_Channel::k_aileron, aileron);
     455        6657 :         SRV_Channels::set_output_scaled(SRV_Channel::k_elevator, elevator);
     456        6657 :         float rudder = 0;
     457        6657 :         if (yawController.rate_control_enabled()) {
     458        6657 :             rudder = nav_scripting.rudder_offset_pct * 45;
     459        6657 :             if (nav_scripting.run_yaw_rate_controller) {
     460        6655 :                 rudder += yawController.get_rate_out(nav_scripting.yaw_rate_dps, speed_scaler, false);
     461             :             } else {
     462           2 :                 yawController.reset_I();
     463             :             }
     464             :         }
     465        6657 :         SRV_Channels::set_output_scaled(SRV_Channel::k_rudder, rudder);
     466        6657 :         SRV_Channels::set_output_scaled(SRV_Channel::k_steering, rudder);
     467        6657 :         SRV_Channels::set_output_scaled(SRV_Channel::k_throttle, plane.nav_scripting.throttle_pct);
     468             : #endif
     469             :     } else {
     470     7755268 :         plane.control_mode->run();
     471             :     }
     472             : 
     473             :     /*
     474             :       see if we should zero the attitude controller integrators. 
     475             :      */
     476     8377547 :     if (is_zero(get_throttle_input()) &&
     477     6139714 :         fabsf(relative_altitude) < 5.0f && 
     478    16293483 :         fabsf(barometer.get_climb_rate()) < 0.5f &&
     479     1776222 :         ahrs.groundspeed() < 3) {
     480             :         // we are low, with no climb rate, and zero throttle, and very
     481             :         // low ground speed. Zero the attitude controller
     482             :         // integrators. This prevents integrator buildup pre-takeoff.
     483     1720634 :         rollController.reset_I();
     484     1720634 :         pitchController.reset_I();
     485     1720634 :         yawController.reset_I();
     486             : 
     487             :         // if moving very slowly also zero the steering integrator
     488     1720634 :         if (ahrs.groundspeed() < 1) {
     489     1653122 :             steerController.reset_I();            
     490             :         }
     491             :     }
     492     8377547 : }
     493             : 
     494             : 
     495             : /*
     496             :  * Set the throttle output.
     497             :  * This is called by TECS-enabled flight modes, e.g. AUTO, GUIDED, etc.
     498             : */
     499     3609969 : void Plane::calc_throttle()
     500             : {
     501     3609969 :     if (aparm.throttle_cruise <= 1) {
     502             :         // user has asked for zero throttle - this may be done by a
     503             :         // mission which wants to turn off the engine for a parachute
     504             :         // landing
     505           0 :         SRV_Channels::set_output_scaled(SRV_Channel::k_throttle, 0.0);
     506           0 :         return;
     507             :     }
     508             : 
     509             :     // Read the TECS throttle output and set it to the throttle channel.
     510     3609969 :     float commanded_throttle = TECS_controller.get_throttle_demand();
     511     3609969 :     SRV_Channels::set_output_scaled(SRV_Channel::k_throttle, commanded_throttle);
     512             : }
     513             : 
     514             : /*****************************************
     515             : * Calculate desired roll/pitch/yaw angles (in medium freq loop)
     516             : *****************************************/
     517             : 
     518             : /*
     519             :   calculate yaw control for coordinated flight
     520             :  */
     521     6297553 : int16_t Plane::calc_nav_yaw_coordinated()
     522             : {
     523     6297553 :     const float speed_scaler = get_speed_scaler();
     524     6297553 :     bool disable_integrator = false;
     525     6297553 :     int16_t rudder_in = rudder_input();
     526             : 
     527             :     int16_t commanded_rudder;
     528     6297553 :     bool using_rate_controller = false;
     529             : 
     530             :     // Received an external msg that guides yaw within g2.guided_timeout?
     531     6297553 :     if (control_mode->is_guided_mode() &&
     532     6302794 :             plane.guided_state.last_forced_rpy_ms.z > 0 &&
     533        5241 :             millis() - plane.guided_state.last_forced_rpy_ms.z < g2.guided_timeout*1000.0f) {
     534        5241 :         commanded_rudder = plane.guided_state.forced_rpy_cd.z;
     535     6292312 :     } else if (autotuning && g.acro_yaw_rate > 0 && yawController.rate_control_enabled()) {
     536             :         // user is doing an AUTOTUNE with yaw rate control
     537           0 :         const float rudd_expo = rudder_in_expo(true);
     538           0 :         const float yaw_rate = (rudd_expo/SERVO_MAX) * g.acro_yaw_rate;
     539             :         // add in the coordinated turn yaw rate to make it easier to fly while tuning the yaw rate controller
     540           0 :         const float coordination_yaw_rate = degrees(GRAVITY_MSS * tanf(cd_to_rad(nav_roll_cd))/MAX(aparm.airspeed_min,smoothed_airspeed));
     541           0 :         commanded_rudder = yawController.get_rate_out(yaw_rate+coordination_yaw_rate,  speed_scaler, false);
     542           0 :         using_rate_controller = true;
     543             :     } else {
     544     6292312 :         if (control_mode == &mode_stabilize && rudder_in != 0) {
     545           0 :             disable_integrator = true;
     546             :         }
     547             : 
     548     6292312 :         commanded_rudder = yawController.get_servo_out(speed_scaler, disable_integrator);
     549             : 
     550             :         // add in rudder mixing from roll
     551     6292312 :         commanded_rudder += SRV_Channels::get_output_scaled(SRV_Channel::k_aileron) * g.kff_rudder_mix;
     552     6292312 :         commanded_rudder += rudder_in;
     553             :     }
     554             : 
     555     6297553 :     if (!using_rate_controller) {
     556             :         /*
     557             :           When not running the yaw rate controller, we need to reset the rate
     558             :         */
     559     6297553 :         yawController.reset_rate_PID();
     560             :     }
     561             : 
     562     6297553 :     return constrain_int16(commanded_rudder, -4500, 4500);
     563             : }
     564             : 
     565             : /*
     566             :   calculate yaw control for ground steering with specific course
     567             :  */
     568      123090 : int16_t Plane::calc_nav_yaw_course(void)
     569             : {
     570             :     // holding a specific navigation course on the ground. Used in
     571             :     // auto-takeoff and landing
     572      123090 :     int32_t bearing_error_cd = nav_controller->bearing_error_cd();
     573      123090 :     int16_t steering = steerController.get_steering_out_angle_error(bearing_error_cd);
     574      123090 :     if (stick_mixing_enabled()) {
     575      123090 :         steering = channel_rudder->stick_mixing(steering);
     576             :     }
     577      123090 :     return constrain_int16(steering, -4500, 4500);
     578             : }
     579             : 
     580             : /*
     581             :   calculate yaw control for ground steering
     582             :  */
     583           0 : int16_t Plane::calc_nav_yaw_ground(void)
     584             : {
     585           0 :     if (gps.ground_speed() < 1 && 
     586           0 :         is_zero(get_throttle_input()) &&
     587           0 :         flight_stage != AP_FixedWing::FlightStage::TAKEOFF &&
     588           0 :         flight_stage != AP_FixedWing::FlightStage::ABORT_LANDING) {
     589             :         // manual rudder control while still
     590           0 :         steer_state.locked_course = false;
     591           0 :         steer_state.locked_course_err = 0;
     592           0 :         return rudder_input();
     593             :     }
     594             : 
     595             :     // if we haven't been steering for 1s then clear locked course
     596           0 :     const uint32_t now_ms = AP_HAL::millis();
     597           0 :     if (now_ms - steer_state.last_steer_ms > 1000) {
     598           0 :         steer_state.locked_course = false;
     599             :     }
     600           0 :     steer_state.last_steer_ms = now_ms;
     601             : 
     602           0 :     float steer_rate = (rudder_input()/4500.0f) * g.ground_steer_dps;
     603           0 :     if (flight_stage == AP_FixedWing::FlightStage::TAKEOFF ||
     604           0 :         flight_stage == AP_FixedWing::FlightStage::ABORT_LANDING) {
     605           0 :         steer_rate = 0;
     606             :     }
     607           0 :     if (!is_zero(steer_rate)) {
     608             :         // pilot is giving rudder input
     609           0 :         steer_state.locked_course = false;        
     610           0 :     } else if (!steer_state.locked_course) {
     611             :         // pilot has released the rudder stick or we are still - lock the course
     612           0 :         steer_state.locked_course = true;
     613           0 :         if (flight_stage != AP_FixedWing::FlightStage::TAKEOFF &&
     614           0 :             flight_stage != AP_FixedWing::FlightStage::ABORT_LANDING) {
     615           0 :             steer_state.locked_course_err = 0;
     616             :         }
     617             :     }
     618             : 
     619             :     int16_t steering;
     620           0 :     if (!steer_state.locked_course) {
     621             :         // use a rate controller at the pilot specified rate
     622           0 :         steering = steerController.get_steering_out_rate(steer_rate);
     623             :     } else {
     624             :         // use a error controller on the summed error
     625           0 :         int32_t yaw_error_cd = -degrees(steer_state.locked_course_err)*100;
     626           0 :         steering = steerController.get_steering_out_angle_error(yaw_error_cd);
     627             :     }
     628           0 :     return constrain_int16(steering, -4500, 4500);
     629             : }
     630             : 
     631             : 
     632             : /*
     633             :   calculate a new nav_pitch_cd from the speed height controller
     634             :  */
     635     3731360 : void Plane::calc_nav_pitch()
     636             : {
     637     3731360 :     int32_t commanded_pitch = TECS_controller.get_pitch_demand();
     638     3731360 :     nav_pitch_cd = constrain_int32(commanded_pitch, pitch_limit_min*100, aparm.pitch_limit_max.get()*100);
     639     3731360 : }
     640             : 
     641             : 
     642             : /*
     643             :   calculate a new nav_roll_cd from the navigation controller
     644             :  */
     645     3791334 : void Plane::calc_nav_roll()
     646             : {
     647     3791334 :     int32_t commanded_roll = nav_controller->nav_roll_cd();
     648     3791334 :     nav_roll_cd = constrain_int32(commanded_roll, -roll_limit_cd, roll_limit_cd);
     649     3791334 :     update_load_factor();
     650     3791334 : }
     651             : 
     652             : /*
     653             :   adjust nav_pitch_cd for STAB_PITCH_DOWN_CD. This is used to make
     654             :   keeping up good airspeed in FBWA mode easier, as the plane will
     655             :   automatically pitch down a little when at low throttle. It makes
     656             :   FBWA landings without stalling much easier.
     657             :  */
     658     1114368 : void Plane::adjust_nav_pitch_throttle(void)
     659             : {
     660     1114368 :     int8_t throttle = throttle_percentage();
     661     1114368 :     if (throttle >= 0 && throttle < aparm.throttle_cruise && flight_stage != AP_FixedWing::FlightStage::VTOL) {
     662      830311 :         float p = (aparm.throttle_cruise - throttle) / (float)aparm.throttle_cruise;
     663      830311 :         nav_pitch_cd -= g.stab_pitch_down * 100.0f * p;
     664             :     }
     665     1114368 : }
     666             : 
     667             : 
     668             : /*
     669             :   calculate a new aerodynamic_load_factor
     670             :  */
     671     4981710 : void Plane::update_load_factor(void)
     672             : {
     673     4981710 :     float demanded_roll = fabsf(nav_roll_cd*0.01f);
     674     4981710 :     if (demanded_roll > 85) {
     675             :         // limit to 85 degrees to prevent numerical errors
     676           0 :         demanded_roll = 85;
     677             :     }
     678             : 
     679             :     // loadFactor = liftForce / gravityForce, where gravityForce = liftForce * cos(roll) on balanced horizontal turn
     680     4981710 :     aerodynamic_load_factor = 1.0f / cosf(radians(demanded_roll));
     681             : 
     682     4981710 :     apply_load_factor_roll_limits();
     683     4981710 : }
     684             : 
     685             : /*
     686             :   limit nav_roll_cd to ensure that the load factor does not take us below the
     687             :   sustainable airspeed
     688             :  */
     689     4981710 : void Plane::apply_load_factor_roll_limits(void)
     690             : {
     691             : #if HAL_QUADPLANE_ENABLED
     692     4981710 :     if (quadplane.available() && quadplane.transition->set_FW_roll_limit(roll_limit_cd)) {
     693           0 :         nav_roll_cd = constrain_int32(nav_roll_cd, -roll_limit_cd, roll_limit_cd);
     694      332168 :         return;
     695             :     }
     696             : #endif
     697             : 
     698     4981710 :     if (!aparm.stall_prevention) {
     699             :         // stall prevention is disabled
     700      324926 :         return;
     701             :     }
     702     4656784 :     if (fly_inverted()) {
     703             :         // no roll limits when inverted
     704        5135 :         return;
     705             :     }
     706             : #if HAL_QUADPLANE_ENABLED
     707     4651649 :     if (quadplane.tailsitter.active()) {
     708             :         // no limits while hovering
     709        2107 :         return;
     710             :     }
     711             : #endif
     712             : 
     713     4649542 :     float stall_airspeed_1g = is_positive(aparm.airspeed_stall)
     714     9299084 :                                   ? aparm.airspeed_stall
     715     4649542 :                                   : aparm.airspeed_min;
     716             : 
     717             :     float max_load_factor =
     718     4649542 :         sq(smoothed_airspeed / MAX(stall_airspeed_1g, 1));
     719             : 
     720             :     // allow limiting roll command down to wings-level when necessary if
     721             :     // airspeed is accurate and airspeed stall is set (implying low load factor
     722             :     // overhead)
     723             :     const bool enforce_full_roll_limit =
     724     4649542 :         flight_option_enabled(FlightOptions::ENABLE_FULL_AERO_LF_ROLL_LIMITS) &&
     725     4649542 :         ahrs.using_airspeed_sensor() && is_positive(aparm.airspeed_stall);
     726             : 
     727     4649542 :     const float level_roll_limit_deg = g.level_roll_limit;
     728     4649542 :     float lf_roll_limit_deg = aparm.roll_limit;
     729     4649542 :     if (max_load_factor <= 1) {
     730     1067119 :         if (enforce_full_roll_limit) {
     731           0 :             lf_roll_limit_deg = level_roll_limit_deg;
     732             :         } else {
     733             :             // 25° limit to ensure maneuverability if airspeed estimate is wrong
     734     1067119 :             lf_roll_limit_deg = 25;
     735             :         }
     736     3582423 :     } else if (max_load_factor < aerodynamic_load_factor) {
     737             :         // the demanded nav_roll would take us past the aerodynamic
     738             :         // load limit. Limit our roll to a bank angle that will keep
     739             :         // the load within what the airframe can handle.
     740       14941 :         lf_roll_limit_deg = degrees(acosf(1.0f / max_load_factor));
     741             : 
     742             :         // unless enforcing full limits, allow at least 25 degrees of roll to
     743             :         // ensure the aircraft can be manoeuvered with a bad airspeed estimate.
     744             :         // At 25 degrees the load factor is 1.1 (10%)
     745       14941 :         if (!enforce_full_roll_limit && lf_roll_limit_deg < 25) {
     746        3370 :             lf_roll_limit_deg = 25;
     747             :         }
     748             : 
     749             :         // always allow at least the wings level threshold to prevent flyaways
     750       14941 :         if (lf_roll_limit_deg < level_roll_limit_deg) {
     751           0 :             lf_roll_limit_deg = level_roll_limit_deg;
     752             :         }
     753             :     }
     754             : 
     755     4649542 :     nav_roll_cd = constrain_int32(nav_roll_cd, -lf_roll_limit_deg * 100, lf_roll_limit_deg * 100);
     756     4649542 :     roll_limit_cd = MIN(roll_limit_cd, lf_roll_limit_deg * 100);
     757             : }
     758             : 
     759             : // Check if there has been a change in attitude estimate which the attitude controllers should be told about
     760             : // This allows them to compensate for the change so smooth control is maintained
     761     8377547 : void Plane::check_ahrs_reset()
     762             : {
     763     8377547 :     bool should_reset = false;
     764             : 
     765             :     // Check for yaw reset
     766     8377547 :     const uint16_t new_yaw_reset_count = ahrs.get_yaw_reset_count();
     767     8377547 :     if (ahrs_check.ahrs_yaw_reset_count != new_yaw_reset_count) {
     768         421 :         should_reset = true;
     769         421 :         ahrs_check.ahrs_yaw_reset_count = new_yaw_reset_count;
     770             :     }
     771             : 
     772             :     // check for change in primary EKF, or EKF lane.
     773     8377547 :     const uint16_t new_reset_count = ahrs.get_last_attitude_reset_count();
     774     8377547 :     if (new_reset_count != ahrs_check.attitude_reset_count) {
     775         336 :         should_reset = true;
     776         336 :         ahrs_check.attitude_reset_count = new_reset_count;
     777             :     }
     778             : 
     779             :     // Nothing to do if there are no resets
     780     8377547 :     if (!should_reset) {
     781     8377123 :         return;
     782             :     }
     783             : 
     784             :     // Reset fixed wing controllers
     785         424 :     rollController.ahrs_reset();
     786         424 :     pitchController.ahrs_reset();
     787             : 
     788             : #if HAL_QUADPLANE_ENABLED
     789             :     // Reset vtol controllers
     790         424 :     if (quadplane.initialised) {
     791         198 :         quadplane.attitude_control->inertial_frame_reset();
     792             :     }
     793             : #endif
     794             : 
     795             : }

Generated by: LCOV version 1.14