LCOV - code coverage report
Current view: top level - home/pbarker/rc/ardupilot/ArduPlane - VTOL_Assist.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 103 107 96.3 %
Date: 2026-09-09 11:02:08 Functions: 6 6 100.0 %

          Line data    Source code
       1             : #include "Plane.h"
       2             : 
       3             : #if HAL_QUADPLANE_ENABLED
       4             : 
       5             : // Assistance hysteresis helpers
       6             : 
       7             : // Reset state
       8    12967675 : void VTOL_Assist::Assist_Hysteresis::reset()
       9             : {
      10    12967675 :     start_ms = 0;
      11    12967675 :     last_ms = 0;
      12    12967675 :     active = false;
      13    12967675 : }
      14             : 
      15             : // Update state, return true when first triggered
      16     2611856 : bool VTOL_Assist::Assist_Hysteresis::update(const bool trigger, const uint32_t &now_ms, const uint32_t &trigger_delay_ms, const uint32_t &clear_delay_ms)
      17             : {
      18     2611856 :     bool ret = false;
      19             : 
      20     2611856 :     if (trigger) {
      21       16906 :         last_ms = now_ms;
      22       16906 :         if (start_ms == 0) {
      23          49 :             start_ms = now_ms;
      24             :         }
      25       16906 :         if ((now_ms - start_ms) > trigger_delay_ms) {
      26             :             // trigger delay has elapsed
      27        9512 :             if (!active) {
      28             :                 // return true on first trigger
      29          48 :                 ret = true;
      30             :             }
      31        9512 :             active = true;
      32             :         }
      33             : 
      34     2594950 :     } else if (active) {
      35       13839 :         if ((last_ms == 0) || ((now_ms - last_ms) > clear_delay_ms)) {
      36             :             // Clear delay passed
      37          46 :             reset();
      38             :         }
      39             : 
      40             :     } else {
      41     2581111 :         reset();
      42             :     }
      43             : 
      44     2611856 :     return ret;
      45             : }
      46             : 
      47             : // Assistance not needed, reset any state
      48     3887327 : void VTOL_Assist::reset()
      49             : {
      50     3887327 :     force_assist = false;
      51     3887327 :     speed_assist = false;
      52     3887327 :     angle_error.reset();
      53     3887327 :     alt_error.reset();
      54     3887327 : }
      55             : 
      56             : /*
      57             :   return true if the quadplane should provide stability assistance
      58             :  */
      59     3472722 : bool VTOL_Assist::should_assist(float aspeed, bool have_airspeed)
      60             : {
      61     3472722 :     if (!plane.arming.is_armed_and_safety_off() || (state == STATE::ASSIST_DISABLED) || quadplane.tailsitter.is_control_surface_tailsitter()) {
      62             :         // disarmed or disabled by aux switch or because a control surface tailsitter
      63      834470 :         reset();
      64      834470 :         return false;
      65             :     }
      66             : 
      67     2856578 :     if (!quadplane.tailsitter.enabled() && !( (plane.control_mode->does_auto_throttle() && !plane.throttle_suppressed)
      68      218326 :                                                                       || is_positive(plane.get_throttle_input()) 
      69       28862 :                                                                       || plane.is_flying() ) ) {
      70             :         // not in a flight mode and condition where it would be safe to turn on vertical lift motors
      71             :         // skip this check for tailsitters because the forward and vertical motors are the same and are controlled directly by throttle input unlike other quadplanes
      72       26392 :         reset();
      73       26392 :         return false;
      74             :     }
      75             : 
      76     2611860 :     if (plane.flare_mode != Plane::FlareMode::FLARE_DISABLED) {
      77             :         // Never active in fixed wing flare
      78           0 :         reset();
      79           0 :         return false;
      80             :     }
      81             : 
      82     2611860 :     force_assist = state == STATE::FORCE_ENABLED;
      83             : 
      84     2611860 :     if (speed <= 0) {
      85             :         // all checks disabled via speed threshold, still allow force enabled
      86         358 :         speed_assist = false;
      87         358 :         alt_error.reset();
      88         358 :         angle_error.reset();
      89         358 :         return force_assist;
      90             :     }
      91             : 
      92             :     // assistance due to Q_ASSIST_SPEED
      93             :     // if option bit is enabled only allow assist with real airspeed sensor
      94     2766498 :     speed_assist = (have_airspeed && aspeed < speed) && 
      95      154996 :        (!quadplane.option_is_set(QuadPlane::Option::DISABLE_SYNTHETIC_AIRSPEED_ASSIST) || plane.ahrs.using_airspeed_sensor());
      96             : 
      97     2611502 :     const uint32_t now_ms = AP_HAL::millis();
      98     2611502 :     const uint32_t tigger_delay_ms = delay * 1000;
      99     2611502 :     const uint32_t clear_delay_ms = tigger_delay_ms * 2;
     100             : 
     101             :     /*
     102             :       optional assistance when altitude is too close to the ground
     103             :      */
     104     2611502 :     if (alt <= 0) {
     105             :         // Alt assist disabled
     106     2606887 :         alt_error.reset();
     107             : 
     108             :     } else {
     109        4615 :         const float height_above_ground = plane.relative_ground_altitude(RangeFinderUse::ASSIST);
     110        4615 :         if (alt_error.update(height_above_ground < alt, now_ms, tigger_delay_ms, clear_delay_ms)) {
     111           1 :             gcs().send_text(MAV_SEVERITY_WARNING, "Alt assist %.1fm", height_above_ground);
     112             :         }
     113             :     }
     114             : 
     115     2611502 :     if (angle <= 0 || plane.fly_inverted()) {
     116             :         // Angle assist disabled
     117        4261 :         angle_error.reset();
     118             : 
     119             :     } else {
     120             : 
     121             :         /*
     122             :         now check if we should provide assistance due to attitude error
     123             :         */
     124     2607241 :         const auto ahrs_roll_deg = plane.ahrs.get_roll_deg();
     125     2607241 :         const auto ahrs_pitch_deg = plane.ahrs.get_pitch_deg();
     126     2607241 :         constexpr float allowed_envelope_error_deg = 5.0;
     127             :         const bool inside_envelope =
     128     2607241 :             (fabsf(ahrs_roll_deg) <= (plane.aparm.roll_limit + allowed_envelope_error_deg)) &&
     129     5200196 :             (ahrs_pitch_deg < (plane.aparm.pitch_limit_max + allowed_envelope_error_deg)) &&
     130     2592955 :             (ahrs_pitch_deg > (plane.aparm.pitch_limit_min - allowed_envelope_error_deg));
     131             : 
     132             :         const bool inside_angle_error =
     133     5194869 :             (fabsf(plane.rollController.get_angle_error_deg()) < angle) &&
     134     2587628 :             (fabsf(plane.pitchController.get_angle_error_deg()) < angle);
     135             : 
     136     2607241 :         if (angle_error.update(!inside_envelope && !inside_angle_error, now_ms, tigger_delay_ms, clear_delay_ms)) {
     137          47 :             gcs().send_text(MAV_SEVERITY_WARNING, "Angle assist r=%d p=%d",
     138             :                             (int)ahrs_roll_deg,
     139             :                             (int)ahrs_pitch_deg);
     140             :         }
     141             :     }
     142             : 
     143     2611502 :     return force_assist || speed_assist || alt_error.is_active() || angle_error.is_active();
     144             : }
     145             : 
     146             : /*
     147             :   check if we are in VTOL recovery
     148             : */
     149     3776513 : bool VTOL_Assist::check_VTOL_recovery(void)
     150             : {
     151             :     const bool allow_fw_recovery =
     152     3776513 :         !option_is_set(OPTION::FW_FORCE_DISABLED) &&
     153     7427209 :         !quadplane.tailsitter.enabled() &&
     154     3650696 :         plane.control_mode != &plane.mode_qacro;
     155     3776513 :     if (!allow_fw_recovery) {
     156      134038 :         quadplane.force_fw_control_recovery = false;
     157      134038 :         quadplane.in_spin_recovery = false;
     158      134038 :         return false;
     159             :     }
     160             : 
     161             :     // see if the attitude is outside twice the Q_A_ANGLE_MAX
     162     3642475 :     const auto &ahrs = plane.ahrs;
     163     3642475 :     const float angle_max_cd = quadplane.attitude_control->lean_angle_max_cd();
     164     3642475 :     const float abs_angle_cd = fabsf(Vector2f{float(ahrs.roll_sensor), float(ahrs.pitch_sensor)}.length());
     165             : 
     166     3642475 :     if (abs_angle_cd > 2*angle_max_cd) {
     167             :         // we are 2x the angle limits, trigger fw recovery
     168       47583 :         quadplane.force_fw_control_recovery = true;
     169             :     }
     170             : 
     171     3642475 :     if (quadplane.force_fw_control_recovery) {
     172             :         // stop fixed wing recovery if inside Q_A_ANGLE_MAX
     173       64734 :         if (abs_angle_cd <= angle_max_cd) {
     174         108 :             quadplane.force_fw_control_recovery = false;
     175         108 :             quadplane.attitude_control->reset_target_and_rate(false);
     176             : 
     177         108 :             if (ahrs.groundspeed() > quadplane.wp_nav->get_default_speed_NE_ms()) {
     178             :                 /* if moving at high speed also reset position
     179             :                    controller and height controller
     180             : 
     181             :                    this avoids an issue where the position
     182             :                    controller may limit pitch after a strong
     183             :                    acceleration event
     184             :                 */
     185          90 :                 quadplane.pos_control->D_init_controller();
     186          90 :                 quadplane.pos_control->NE_init_controller();
     187             :             }
     188             :         }
     189             :     }
     190             : 
     191     7284950 :     if (!option_is_set(OPTION::SPIN_DISABLED) &&
     192     3642475 :         quadplane.force_fw_control_recovery) {
     193             :         // additionally check for needing spin recovery
     194       64626 :         const auto &gyro = plane.ahrs.get_gyro();
     195       64626 :         quadplane.in_spin_recovery =
     196       97742 :             fabsf(gyro.z) > radians(10) &&
     197       64850 :             fabsf(gyro.x) > radians(30) &&
     198       31734 :             fabsf(gyro.y) > radians(30) &&
     199      120981 :             gyro.x * gyro.z < 0 &&
     200       23239 :             plane.ahrs.get_pitch_deg() < -45;
     201             :     } else {
     202     3577849 :         quadplane.in_spin_recovery = false;
     203             :     }
     204             :     
     205     3642475 :     return quadplane.force_fw_control_recovery;
     206             : }
     207             : 
     208             : 
     209             : /* if we are in a spin then counter with rudder and elevator
     210             : 
     211             :    if roll rate and yaw rate are opposite and yaw rate is
     212             :    significant then put in full rudder to counter the yaw rate
     213             :    for spin recovery
     214             : */
     215     6652785 : void VTOL_Assist::output_spin_recovery(void)
     216             : {
     217     6652785 :     if (!quadplane.in_spin_recovery) {
     218     6650069 :         return;
     219             :     }
     220        2716 :     if (quadplane.motors->get_desired_spool_state() !=
     221             :         AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED) {
     222             :         // if we and no longer running the VTOL motors we need to
     223             :         // clear the spin flag
     224           0 :         quadplane.in_spin_recovery = false;
     225           0 :         return;
     226             :     }
     227        2716 :     const Vector3f &gyro = plane.ahrs.get_gyro();
     228             : 
     229             :     // put in opposite rudder to counter yaw, and neutral
     230             :     // elevator until we're out of the spin
     231        2716 :     SRV_Channels::set_output_scaled(SRV_Channel::k_rudder, gyro.z > 0 ? -SERVO_MAX : SERVO_MAX);
     232        2716 :     SRV_Channels::set_output_scaled(SRV_Channel::k_elevator, 0);
     233             : }
     234             : 
     235             : 
     236             : #endif  // HAL_QUADPLANE_ENABLED

Generated by: LCOV version 1.14