LCOV - code coverage report
Current view: top level - home/pbarker/rc/ardupilot/ArduPlane - tailsitter.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 310 425 72.9 %
Date: 2026-09-09 11:02:08 Functions: 23 24 95.8 %

          Line data    Source code
       1             : /*
       2             :    This program is free software: you can redistribute it and/or modify
       3             :    it under the terms of the GNU General Public License as published by
       4             :    the Free Software Foundation, either version 3 of the License, or
       5             :    (at your option) any later version.
       6             : 
       7             :    This program is distributed in the hope that it will be useful,
       8             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
       9             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      10             :    GNU General Public License for more details.
      11             : 
      12             :    You should have received a copy of the GNU General Public License
      13             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.
      14             :  */
      15             : /*
      16             :   control code for tailsitters. Enabled by setting Q_FRAME_CLASS=10 
      17             :   or by setting Q_TAILSIT_MOTMX nonzero and Q_FRAME_CLASS and Q_FRAME_TYPE
      18             :   to a configuration supported by AP_MotorsMatrix
      19             :  */
      20             : #include "tailsitter.h"
      21             : #include "Plane.h"
      22             : 
      23             : #if HAL_QUADPLANE_ENABLED
      24             : 
      25             : const AP_Param::GroupInfo Tailsitter::var_info[] = {
      26             : 
      27             :     // @Param: ENABLE
      28             :     // @DisplayName: Enable Tailsitter
      29             :     // @Values: 0:Disable, 1:Enable, 2:Enable Always
      30             :     // @Description: This enables Tailsitter functionality. A value of 2 forces Qassist active and always stabilize in forward flight with airmode for stabilisation at 0 throttle, for use on vehicles with no control surfaces, vehicle will not arm in forward flight modes, see also Q_OPTIONS "Mtrs_Only_Qassist"
      31             :     // @User: Standard
      32             :     // @RebootRequired: True
      33             :     AP_GROUPINFO_FLAGS("ENABLE", 1, Tailsitter, enable, 0, AP_PARAM_FLAG_ENABLE),
      34             : 
      35             :     // @Param: ANGLE
      36             :     // @DisplayName: Tailsitter fixed wing transition angle
      37             :     // @Description: This is the pitch angle at which tailsitter aircraft will change from VTOL control to fixed wing control.
      38             :     // @Units: deg
      39             :     // @Range: 5 80
      40             :     AP_GROUPINFO("ANGLE", 2, Tailsitter, transition_angle_fw, 45),
      41             : 
      42             :     // @Param: ANG_VT
      43             :     // @DisplayName: Tailsitter VTOL transition angle
      44             :     // @Description: This is the pitch angle at which tailsitter aircraft will change from fixed wing control to VTOL control, if zero Q_TAILSIT_ANGLE will be used
      45             :     // @Units: deg
      46             :     // @Range: 5 80
      47             :     AP_GROUPINFO("ANG_VT", 3, Tailsitter, transition_angle_vtol, 0),
      48             : 
      49             :     // @Param: INPUT
      50             :     // @DisplayName: Tailsitter input type bitmask
      51             :     // @Description: This controls whether stick input when hovering as a tailsitter follows the conventions for fixed wing hovering or multicopter hovering. When PlaneMode is not enabled (bit0 = 0) the roll stick will roll the aircraft in earth frame and yaw stick will yaw in earth frame. When PlaneMode input is enabled, the roll and yaw sticks are swapped so that the roll stick controls earth-frame yaw and rudder controls earth-frame roll. When body-frame roll is enabled (bit1 = 1), the yaw stick controls earth-frame yaw rate and the roll stick controls roll in the tailsitter's body frame when flying level.
      52             :     // @Bitmask: 0:PlaneMode,1:BodyFrameRoll
      53             :     AP_GROUPINFO("INPUT", 4, Tailsitter, input_type, 0),
      54             : 
      55             :     // 5 was MASK
      56             :     // 6 was MASKCH
      57             : 
      58             :     // @Param: VFGAIN
      59             :     // @DisplayName: Tailsitter vector thrust gain in forward flight
      60             :     // @Description: This controls the amount of vectored thrust control used in forward flight for a vectored tailsitter
      61             :     // @Range: 0 1
      62             :     // @Increment: 0.01
      63             :     AP_GROUPINFO("VFGAIN", 7, Tailsitter, vectored_forward_gain, 0),
      64             : 
      65             :     // @Param: VHGAIN
      66             :     // @DisplayName: Tailsitter vector thrust gain in hover
      67             :     // @Description: This controls the amount of vectored thrust control used in hover for a vectored tailsitter
      68             :     // @Range: 0 1
      69             :     // @Increment: 0.01
      70             :     AP_GROUPINFO("VHGAIN", 8, Tailsitter, vectored_hover_gain, 0.5),
      71             : 
      72             :     // @Param: VHPOW
      73             :     // @DisplayName: Tailsitter vector thrust gain power
      74             :     // @Description: This controls the amount of extra pitch given to the vectored control when at high pitch errors
      75             :     // @Range: 0 4
      76             :     // @Increment: 0.1
      77             :     AP_GROUPINFO("VHPOW", 9, Tailsitter, vectored_hover_power, 2.5),
      78             : 
      79             :     // @Param: GSCMAX
      80             :     // @DisplayName: Maximum tailsitter gain scaling
      81             :     // @Description: Maximum gain scaling for tailsitter Q_TAILSIT_GSCMSK options
      82             :     // @Range: 1 5
      83             :     // @User: Standard
      84             :     AP_GROUPINFO("GSCMAX", 10, Tailsitter, throttle_scale_max, 2),
      85             : 
      86             :     // @Param: RLL_MX
      87             :     // @DisplayName: Maximum Roll angle
      88             :     // @Description: Maximum Allowed roll angle for tailsitters. If this is zero then Q_A_ANGLE_MAX is used.
      89             :     // @Units: deg
      90             :     // @Range: 0 80
      91             :     // @User: Standard
      92             :     AP_GROUPINFO("RLL_MX", 11, Tailsitter, max_roll_angle, 0),
      93             : 
      94             :     // @Param: MOTMX
      95             :     // @DisplayName: Tailsitter motor mask
      96             :     // @Description: Bitmask of motors to remain active in forward flight for a 'Copter' tailsitter. Non-zero indicates airframe is a Copter tailsitter and uses copter style motor layouts determined by Q_FRAME_CLASS and Q_FRAME_TYPE. This should be zero for non-Copter tailsitters.
      97             :     // @User: Standard
      98             :     // @Bitmask: 0:Motor 1, 1:Motor 2, 2:Motor 3, 3:Motor 4, 4:Motor 5, 5:Motor 6, 6:Motor 7, 7:Motor 8, 8:Motor 9, 9:Motor 10, 10:Motor 11, 11:Motor 12
      99             :     AP_GROUPINFO("MOTMX", 12, Tailsitter, motor_mask, 0),
     100             : 
     101             :     // @Param: GSCMSK
     102             :     // @DisplayName: Tailsitter gain scaling mask
     103             :     // @Description: Bitmask of gain scaling methods to be applied: Throttle: scale gains with throttle, ATT_THR: reduce gain at high throttle/tilt, 2:Disk theory velocity calculation, requires Q_TAILSIT_DSKLD to be set, ATT_THR must not be set, 3:Altitude correction, scale with air density
     104             :     // @User: Standard
     105             :     // @Bitmask: 0:Throttle,1:ATT_THR,2:Disk Theory,3:Altitude correction
     106             :     AP_GROUPINFO("GSCMSK", 13, Tailsitter, gain_scaling_mask, TAILSITTER_GSCL_THROTTLE),
     107             : 
     108             :     // @Param: GSCMIN
     109             :     // @DisplayName: Minimum tailsitter gain scaling
     110             :     // @Description: Minimum gain scaling for tailsitter Q_TAILSIT_GSCMSK options
     111             :     // @Range: 0.1 1
     112             :     // @User: Standard
     113             :     AP_GROUPINFO("GSCMIN", 14, Tailsitter, gain_scaling_min, 0.4),
     114             : 
     115             :     // @Param: DSKLD
     116             :     // @DisplayName: Tailsitter disk loading
     117             :     // @Description: This is the vehicle weight in kg divided by the total disk area of all propellers in m^2. Only used with Q_TAILSIT_GSCMSK = 4
     118             :     // @Units: kg/m/m
     119             :     // @Range: 0 50
     120             :     // @User: Standard
     121             :     AP_GROUPINFO("DSKLD", 15, Tailsitter, disk_loading, 0),
     122             : 
     123             :     // @Param: RAT_FW
     124             :     // @DisplayName: Tailsitter VTOL to forward flight transition rate
     125             :     // @Description: The pitch rate at which tailsitter aircraft will pitch down in the transition from VTOL to forward flight
     126             :     // @Units: deg/s
     127             :     // @Range: 10 500
     128             :     AP_GROUPINFO("RAT_FW", 16, Tailsitter, transition_rate_fw, 50),
     129             : 
     130             :     // @Param: RAT_VT
     131             :     // @DisplayName: Tailsitter forward flight to VTOL transition rate
     132             :     // @Description: The pitch rate at which tailsitter aircraft will pitch up in the transition from forward flight to VTOL
     133             :     // @Units: deg/s
     134             :     // @Range: 10 500
     135             :     AP_GROUPINFO("RAT_VT", 17, Tailsitter, transition_rate_vtol, 50),
     136             : 
     137             :     // @Param: THR_VT
     138             :     // @DisplayName: Tailsitter forward flight to VTOL transition throttle
     139             :     // @Description: Throttle used during FW->VTOL transition, -1 uses hover throttle
     140             :     // @Units: %
     141             :     // @Range: -1 100
     142             :     AP_GROUPINFO("THR_VT", 18, Tailsitter, transition_throttle_vtol, -1),
     143             : 
     144             :     // @Param: VT_R_P
     145             :     // @DisplayName: Tailsitter VTOL control surface roll gain
     146             :     // @Description: Scale from PID output to control surface, for use where a single axis is actuated by both motors and Tilt/control surface on a copter style tailsitter, increase to favor control surfaces and reduce motor output by reducing gains
     147             :     // @Range: 0 2
     148             :     AP_GROUPINFO("VT_R_P", 19, Tailsitter, VTOL_roll_scale, 1),
     149             : 
     150             :     // @Param: VT_P_P
     151             :     // @DisplayName: Tailsitter VTOL control surface pitch gain
     152             :     // @Description: Scale from PID output to control surface, for use where a single axis is actuated by both motors and Tilt/control surface on a copter style tailsitter, increase to favor control surfaces and reduce motor output by reducing gains
     153             :     // @Range: 0 2
     154             :     AP_GROUPINFO("VT_P_P", 20, Tailsitter, VTOL_pitch_scale, 1),
     155             : 
     156             :     // @Param: VT_Y_P
     157             :     // @DisplayName: Tailsitter VTOL control surface yaw gain
     158             :     // @Description: Scale from PID output to control surface, for use where a single axis is actuated by both motors and Tilt/control surface on a copter style tailsitter, increase to favor control surfaces and reduce motor output by reducing gains
     159             :     // @Range: 0 2
     160             :     AP_GROUPINFO("VT_Y_P", 21, Tailsitter, VTOL_yaw_scale, 1),
     161             : 
     162             :     // @Param: MIN_VO
     163             :     // @DisplayName: Tailsitter Disk loading minimum outflow speed
     164             :     // @Description: Use in conjunction with disk theory gain scaling and Q_TAILSIT_DSKLD to specify minimum airspeed over control surfaces, this will be used to boost throttle, when descending for example, 0 disables
     165             :     // @Range: 0 15
     166             :     AP_GROUPINFO("MIN_VO", 22, Tailsitter, disk_loading_min_outflow, 0),
     167             : 
     168             :     AP_GROUPEND
     169             : };
     170             : 
     171             : /*
     172             :   defaults for tailsitters
     173             :  */
     174             : static const struct AP_Param::defaults_table_struct defaults_table_tailsitter[] = {
     175             :     { "KFF_RDDRMIX",       0.02 },
     176             :     { "Q_A_RAT_PIT_FF",    0.2 },
     177             :     { "Q_A_RAT_YAW_FF",    0.2 },
     178             :     { "Q_A_RAT_YAW_I",     0.18 },
     179             :     { "Q_A_ANGLE_BOOST",   0 },
     180             :     { "PTCH_LIM_MAX_DEG",  30 },
     181             :     { "PTCH_LIM_MIN_DEG", -30 },
     182             :     { "MIXING_GAIN",      1.0 },
     183             :     { "RUDD_DT_GAIN",      10 },
     184             :     { "Q_TRANSITION_MS",   2000 },
     185             :     { "Q_TRANS_DECEL",    6 },
     186             :     { "Q_A_ACC_P_MAX",     300},
     187             :     { "Q_A_ACC_R_MAX",     300},
     188             :     { "Q_P_NE_POS_P",        0.5},
     189             :     { "Q_P_NE_VEL_P",        1.0},
     190             :     { "Q_P_NE_VEL_I",        0.5},
     191             :     { "Q_P_NE_VEL_D",        0.25},
     192             :     
     193             : };
     194             : 
     195         692 : Tailsitter::Tailsitter(QuadPlane& _quadplane, AP_MotorsMulticopter*& _motors):quadplane(_quadplane),motors(_motors)
     196             : {
     197         692 :     AP_Param::setup_object_defaults(this, var_info);
     198         692 : }
     199             : 
     200         201 : void Tailsitter::setup()
     201             : {
     202             :     // Set tailsitter enable flag based on old heuristics
     203         201 :     if (!enable.configured() && (((quadplane.frame_class == AP_Motors::MOTOR_FRAME_TAILSITTER) || (motor_mask != 0)) && (quadplane.tiltrotor.type != Tiltrotor::TILT_TYPE_BICOPTER))) {
     204           2 :         enable.set_and_save(1);
     205             :     }
     206             : 
     207         201 :     if (enable <= 0) {
     208         195 :         return;
     209             :     }
     210             : 
     211           6 :     quadplane.thrust_type = QuadPlane::ThrustType::TAILSITTER;
     212             : 
     213             :     // Set tailsitter transition rate to match old calculation
     214           6 :     if (!transition_rate_fw.configured()) {
     215           3 :         transition_rate_fw.set_and_save(transition_angle_fw / (quadplane.transition_time_ms/2000.0f));
     216             :     }
     217             : 
     218             :     // TODO: update this if servo function assignments change
     219             :     // used by relax_attitude_control() to control special behavior for vectored tailsitters
     220           7 :     _is_vectored = (quadplane.frame_class == AP_Motors::MOTOR_FRAME_TAILSITTER) &&
     221           2 :                    (!is_zero(vectored_hover_gain) &&
     222           2 :                     (SRV_Channels::function_assigned(SRV_Channel::k_tiltMotorLeft) ||
     223           1 :                      SRV_Channels::function_assigned(SRV_Channel::k_tiltMotorRight)));
     224             : 
     225           6 :     _have_elevator = SRV_Channels::function_assigned(SRV_Channel::k_elevator);
     226           6 :     _have_aileron = SRV_Channels::function_assigned(SRV_Channel::k_aileron);
     227           6 :     _have_rudder = SRV_Channels::function_assigned(SRV_Channel::k_rudder);
     228           6 :     _have_elevon = SRV_Channels::function_assigned(SRV_Channel::k_elevon_left) || SRV_Channels::function_assigned(SRV_Channel::k_elevon_right);
     229           6 :     _have_v_tail = SRV_Channels::function_assigned(SRV_Channel::k_vtail_left) || SRV_Channels::function_assigned(SRV_Channel::k_vtail_right);
     230             : 
     231             :     // set defaults for dual/single motor tailsitter
     232           6 :     if (quadplane.frame_class == AP_Motors::MOTOR_FRAME_TAILSITTER) {
     233           1 :         AP_Param::set_defaults_from_table(defaults_table_tailsitter, ARRAY_SIZE(defaults_table_tailsitter));
     234             :     }
     235             : 
     236             :     // Setup for control surface less operation
     237           6 :     if (enable == 2) {
     238           0 :         quadplane.assist.set_state(VTOL_Assist::STATE::FORCE_ENABLED);
     239           0 :         quadplane.air_mode = AirMode::ASSISTED_FLIGHT_ONLY;
     240             : 
     241             :         // Do not allow arming in forward flight modes
     242             :         // motors will become active due to assisted flight airmode, the vehicle will try very hard to get level
     243           0 :         quadplane.options.set(quadplane.options.get() | int32_t(QuadPlane::Option::ONLY_ARM_IN_QMODE_OR_AUTO));
     244             :     }
     245             : 
     246           6 :     transition = NEW_NOTHROW Tailsitter_Transition(quadplane, motors, *this);
     247           6 :     if (!transition) {
     248           0 :         AP_BoardConfig::allocation_error("tailsitter transition");
     249             :     }
     250           6 :     quadplane.transition = transition;
     251             : 
     252           6 :     setup_complete = true;
     253             : }
     254             : 
     255             : /*
     256             :   return true when flying a control surface only tailsitter
     257             :  */
     258     2638252 : bool Tailsitter::is_control_surface_tailsitter(void) const
     259             : {
     260     2638252 :     return quadplane.frame_class == AP_Motors::MOTOR_FRAME_TAILSITTER
     261     2638252 :            && ( is_zero(vectored_hover_gain) || !SRV_Channels::function_assigned(SRV_Channel::k_tiltMotorLeft));
     262             : }
     263             : 
     264             : /*
     265             :   check if we are flying as a tailsitter
     266             :  */
     267    17514218 : bool Tailsitter::active(void)
     268             : {
     269    17514218 :     if (!enabled()) {
     270    17063232 :         return false;
     271             :     }
     272      450986 :     if (quadplane.in_vtol_mode()) {
     273      252064 :         return true;
     274             :     }
     275             :     // check if we are in ANGLE_WAIT fixed wing transition
     276      198922 :     if (transition->transition_state == Tailsitter_Transition::State::ANGLE_WAIT_FW) {
     277        6666 :         return true;
     278             :     }
     279      192256 :     return false;
     280             : }
     281             : 
     282             : /*
     283             :   run output for tailsitters
     284             :  */
     285     8438671 : void Tailsitter::output(void)
     286             : {
     287     8438671 :     if (!enabled() || quadplane.motor_test.running || !quadplane.initialised) {
     288             :         // if motor test is running we don't want to overwrite it with output_motor_mask or motors_output
     289     8340314 :         return;
     290             :     }
     291             : 
     292      275963 :     if (!hal.util->get_soft_armed() ||
     293      118099 :         SRV_Channels::get_emergency_stop()) {
     294             :         // Ensure motors stop on disarm
     295       39765 :         motors->output_min();
     296             :     }
     297             : 
     298             :     // throttle 0 to 1
     299      157864 :     float throttle = SRV_Channels::get_output_scaled(SRV_Channel::k_throttle) * 0.01;
     300             : 
     301             :     // handle forward flight modes and transition to VTOL modes
     302      157864 :     if (!active() || in_vtol_transition()) {
     303             :         // get FW controller throttle demand and mask of motors enabled during forward flight
     304       62421 :         if (plane.arming.is_armed_and_safety_off() && in_vtol_transition() && !quadplane.throttle_wait) {
     305             :             /*
     306             :               during transitions to vtol mode set the throttle to hover thrust, center the rudder
     307             :             */
     308        1214 :             if (!is_negative(transition_throttle_vtol)) { 
     309             :                 // Q_TAILSIT_THR_VT is positive use it until transition is complete
     310           0 :                 throttle = motors->thr_lin.actuator_to_thrust(MIN(transition_throttle_vtol*0.01,1.0));
     311             :             } else {
     312        1214 :                 throttle = motors->get_throttle_hover();
     313             :                 // work out equivalent motors throttle level for cruise
     314        1214 :                 throttle = MAX(throttle,motors->thr_lin.actuator_to_thrust(plane.aparm.throttle_cruise.get() * 0.01));
     315             :             }
     316             : 
     317        1214 :             SRV_Channels::set_output_scaled(SRV_Channel::k_rudder, 0.0);
     318        1214 :             plane.rudder_dt = 0;
     319             : 
     320             :             // in assisted flight this is done in the normal motor output path
     321        1214 :             if (!quadplane.assisted_flight) {
     322             : 
     323             :                 // keep attitude control throttle level upto date, this value should never be output to motors
     324             :                 // it is used to re-set the accel Z integrator term allowing for a smooth transfer of control
     325        1214 :                 quadplane.attitude_control->set_throttle_out(throttle, false, 0);
     326             : 
     327             :                 // convert the hover throttle to the same output that would result if used via AP_Motors
     328             :                 // apply expo, battery scaling and SPIN min/max.
     329        1214 :                 throttle = motors->thr_lin.thrust_to_actuator(throttle);
     330             : 
     331             :                 // override AP_MotorsTailsitter throttles during back transition
     332             : 
     333             :                 // apply PWM min and MAX to throttle left and right, just as via AP_Motors
     334        1214 :                 uint16_t throttle_pwm = motors->get_pwm_output_min() + (motors->get_pwm_output_max() - motors->get_pwm_output_min()) * throttle;
     335        1214 :                 SRV_Channels::set_output_pwm(SRV_Channel::k_throttleLeft, throttle_pwm);
     336        1214 :                 SRV_Channels::set_output_pwm(SRV_Channel::k_throttleRight, throttle_pwm);
     337             : 
     338             :                 // throttle output is not used by AP_Motors so might have different PWM range, set scaled
     339        1214 :                 SRV_Channels::set_output_scaled(SRV_Channel::k_throttle, throttle * 100.0);
     340             :             }
     341             :         }
     342             : 
     343       62421 :         if (!quadplane.assisted_flight) {
     344             :             // set AP_MotorsMatrix throttles for forward flight
     345             :             // Output throttle to masked motors and zero all others
     346       59507 :             const uint32_t mask = uint32_t(motor_mask.get());
     347       59507 :             motors->output_motor_mask(0.0, ~mask, 0.0);
     348       59507 :             motors->output_motor_mask(throttle, mask, plane.rudder_dt);
     349             : 
     350             :             // No tilt output unless forward gain is set
     351       59507 :             float tilt_left = 0.0;
     352       59507 :             float tilt_right = 0.0;
     353             : 
     354             :             // in forward flight: set motor tilt servos and throttles using FW controller
     355       59507 :             if (vectored_forward_gain > 0) {
     356             :                 // remove scaling from surface speed scaling and apply throttle scaling
     357           0 :                 const float scaler = plane.control_mode == &plane.mode_manual?1:(quadplane.FW_vector_throttle_scaling() / plane.get_speed_scaler());
     358             :                 // thrust vectoring in fixed wing flight
     359           0 :                 float aileron = SRV_Channels::get_output_scaled(SRV_Channel::k_aileron);
     360           0 :                 float elevator = SRV_Channels::get_output_scaled(SRV_Channel::k_elevator);
     361           0 :                 tilt_left  = (elevator + aileron) * vectored_forward_gain * scaler;
     362           0 :                 tilt_right = (elevator - aileron) * vectored_forward_gain * scaler;
     363             :             }
     364       59507 :             SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorLeft, tilt_left);
     365       59507 :             SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorRight, tilt_right);
     366       59507 :             return;
     367             :         }
     368             :     }
     369             : 
     370             :     // handle Copter controller
     371             :     // the MultiCopter rate controller has already been run in an earlier call
     372             :     // to motors_output() from quadplane.update(), unless we are in assisted flight
     373             :     // tailsitter in Tailsitter_Transition::State::ANGLE_WAIT_FW is not really in assisted flight, its still in a VTOL mode
     374       98357 :     if (quadplane.assisted_flight && (transition->transition_state != Tailsitter_Transition::State::ANGLE_WAIT_FW)) {
     375        2914 :         quadplane.hold_stabilize(throttle);
     376        2914 :         quadplane.motors_output(true);
     377             : 
     378        2914 :         if (quadplane.option_is_set(QuadPlane::Option::TAILSIT_Q_ASSIST_MOTORS_ONLY)) {
     379             :             // only use motors for Q assist, control surfaces remain under plane control. Zero copter I terms and use plane.
     380             :             // Smoothly relax to zero so there is no step change in output, must also set limit flags so integrator cannot build faster than the relax.
     381             :             // Assume there is always roll control surfaces, otherwise motors only assist should not be set.
     382           0 :             const float dt = quadplane.attitude_control->get_dt_s();
     383             : 
     384             :             // VTOL yaw / FW roll
     385           0 :             quadplane.attitude_control->get_rate_yaw_pid().relax_integrator(0.0, dt, AC_ATTITUDE_RATE_RELAX_TC);
     386           0 :             motors->limit.yaw = true;
     387             : 
     388             :             // VTOL and FW pitch
     389           0 :             if (_have_elevator || _have_elevon || _have_v_tail) {
     390             :                 // have pitch control surfaces, use them
     391           0 :                 quadplane.attitude_control->get_rate_pitch_pid().relax_integrator(0.0, dt, AC_ATTITUDE_RATE_RELAX_TC);
     392           0 :                 motors->limit.pitch = true;
     393             :             } else {
     394             :                 // no pitch control surfaces, zero plane I terms and use motors
     395             :                 // We skip the outputting to surfaces for this axis from the copter controller but there are none setup
     396           0 :                 plane.pitchController.reset_I();
     397             :             }
     398             : 
     399             :             // VTOL roll / FW yaw
     400           0 :             if (_have_rudder || _have_v_tail) {
     401             :                 // there are yaw control  surfaces, zero motor I term
     402           0 :                 quadplane.attitude_control->get_rate_roll_pid().relax_integrator(0.0, dt, AC_ATTITUDE_RATE_RELAX_TC);
     403           0 :                 motors->limit.roll = true;
     404             :             } else {
     405             :                 // no yaw control surfaces, zero plane I terms and use motors
     406             :                 // We skip the outputting to surfaces for this axis from the copter controller but there are none setup
     407           0 :                 plane.yawController.reset_I();
     408             :             }
     409             : 
     410             :             // output tilt motors
     411             : 
     412             :             // No output unless hover gain is set
     413           0 :             float tilt_left = 0.0;
     414           0 :             float tilt_right = 0.0;
     415             : 
     416           0 :             if (vectored_hover_gain > 0) {
     417           0 :                 const float hover_throttle = motors->get_throttle_hover();
     418           0 :                 const float output_throttle = motors->get_throttle();
     419           0 :                 float throttle_scaler = throttle_scale_max;
     420           0 :                 if (is_positive(output_throttle)) {
     421           0 :                     throttle_scaler = constrain_float(hover_throttle / output_throttle, gain_scaling_min, throttle_scale_max);
     422             :                 }
     423           0 :                 tilt_left = SRV_Channels::get_output_scaled(SRV_Channel::k_tiltMotorLeft) * vectored_hover_gain * throttle_scaler;
     424           0 :                 tilt_right = SRV_Channels::get_output_scaled(SRV_Channel::k_tiltMotorRight) * vectored_hover_gain * throttle_scaler;
     425             :             }
     426           0 :             SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorLeft, tilt_left);
     427           0 :             SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorRight, tilt_right);
     428             : 
     429             : 
     430             :             // skip remainder of the function that overwrites plane control surface outputs with copter
     431           0 :             return;
     432             :         }
     433        2914 :     } else {
     434       95443 :         quadplane.motors_output(false);
     435             :     }
     436             : 
     437             :     // In full Q assist it is better to use copter I and zero plane
     438       98357 :     plane.pitchController.reset_I();
     439       98357 :     plane.rollController.reset_I();
     440       98357 :     plane.yawController.reset_I();
     441             : 
     442             :     // pull in copter control outputs
     443       98357 :     SRV_Channels::set_output_scaled(SRV_Channel::k_aileron, (motors->get_yaw()+motors->get_yaw_ff())*-SERVO_MAX*VTOL_yaw_scale);
     444       98357 :     SRV_Channels::set_output_scaled(SRV_Channel::k_elevator, (motors->get_pitch()+motors->get_pitch_ff())*SERVO_MAX*VTOL_pitch_scale);
     445       98357 :     SRV_Channels::set_output_scaled(SRV_Channel::k_rudder, (motors->get_roll()+motors->get_roll_ff())*SERVO_MAX*VTOL_roll_scale);
     446             : 
     447       98357 :     if (plane.arming.is_armed_and_safety_off()) {
     448             :         // scale surfaces for throttle
     449       78495 :         speed_scaling();
     450       19862 :     } else if (tailsitter_motors != nullptr) {
     451       18764 :         tailsitter_motors->set_min_throttle(0.0);
     452             :     }
     453             : 
     454             :     // No tilt output unless hover gain is set
     455       98357 :     float tilt_left = 0.0;
     456       98357 :     float tilt_right = 0.0;
     457             : 
     458       98357 :     if (vectored_hover_gain > 0) {
     459             :         // thrust vectoring VTOL modes
     460       98357 :         tilt_left = SRV_Channels::get_output_scaled(SRV_Channel::k_tiltMotorLeft);
     461       98357 :         tilt_right = SRV_Channels::get_output_scaled(SRV_Channel::k_tiltMotorRight);
     462             :         /*
     463             :           apply extra elevator when at high pitch errors, using a
     464             :           power law. This allows the motors to point straight up for
     465             :           takeoff without integrator windup
     466             :          */
     467       98357 :         float des_pitch_cd = quadplane.attitude_control->get_att_target_euler_cd().y;
     468       98357 :         int32_t pitch_error_cd = (des_pitch_cd - quadplane.ahrs_view->pitch_sensor) * 0.5;
     469       98357 :         float extra_pitch = constrain_float(pitch_error_cd, -SERVO_MAX, SERVO_MAX) / SERVO_MAX;
     470       98357 :         float extra_sign = extra_pitch > 0?1:-1;
     471       98357 :         float extra_elevator = 0;
     472       98357 :         if (!is_zero(extra_pitch) && quadplane.in_vtol_mode()) {
     473       55630 :             extra_elevator = extra_sign * powf(fabsf(extra_pitch), vectored_hover_power) * SERVO_MAX;
     474             :         }
     475       98357 :         tilt_left  = extra_elevator + tilt_left * vectored_hover_gain;
     476       98357 :         tilt_right = extra_elevator + tilt_right * vectored_hover_gain;
     477             :     }
     478       98357 :     SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorLeft, tilt_left);
     479       98357 :     SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorRight, tilt_right);
     480             : 
     481             :     // Check for saturated limits
     482       98357 :     bool tilt_lim = _is_vectored && ((fabsf(SRV_Channels::get_output_scaled(SRV_Channel::Function::k_tiltMotorLeft)) >= SERVO_MAX) || (fabsf(SRV_Channels::get_output_scaled(SRV_Channel::Function::k_tiltMotorRight)) >= SERVO_MAX));
     483       98357 :     bool roll_lim = _have_rudder && (fabsf(SRV_Channels::get_output_scaled(SRV_Channel::Function::k_rudder)) >= SERVO_MAX);
     484       98357 :     bool pitch_lim = _have_elevator && (fabsf(SRV_Channels::get_output_scaled(SRV_Channel::Function::k_elevator)) >= SERVO_MAX);
     485       98357 :     bool yaw_lim = _have_aileron && (fabsf(SRV_Channels::get_output_scaled(SRV_Channel::Function::k_aileron)) >= SERVO_MAX);
     486             : 
     487             :     // Mix elevons and V-tail, always giving full priority to pitch
     488       98357 :     float elevator_mix = SRV_Channels::get_output_scaled(SRV_Channel::k_elevator) * (100.0 - plane.g.mixing_offset) * 0.01 * plane.g.mixing_gain;
     489       98357 :     float aileron_mix = SRV_Channels::get_output_scaled(SRV_Channel::k_aileron) * (100.0 + plane.g.mixing_offset) * 0.01 * plane.g.mixing_gain;
     490       98357 :     float rudder_mix = SRV_Channels::get_output_scaled(SRV_Channel::k_rudder) * (100.0 + plane.g.mixing_offset) * 0.01 * plane.g.mixing_gain;
     491             : 
     492       98357 :     const float headroom = SERVO_MAX - fabsf(elevator_mix);
     493       98357 :     if (is_positive(headroom)) {
     494       97633 :         if (fabsf(aileron_mix) > headroom) {
     495         121 :             aileron_mix *= headroom / fabsf(aileron_mix);
     496         121 :             yaw_lim |= _have_elevon;
     497             :         }
     498       97633 :         if (fabsf(rudder_mix) > headroom) {
     499         712 :             rudder_mix *= headroom / fabsf(rudder_mix);
     500         712 :             roll_lim |= _have_v_tail;
     501             :         }
     502             :     } else {
     503         724 :         aileron_mix = 0.0;
     504         724 :         rudder_mix = 0.0;
     505         724 :         yaw_lim |= _have_elevon;
     506         724 :         pitch_lim |= _have_elevon || _have_v_tail;
     507         724 :         roll_lim |= _have_v_tail;
     508             :     }
     509       98357 :     SRV_Channels::set_output_scaled(SRV_Channel::k_elevon_left, elevator_mix - aileron_mix);
     510       98357 :     SRV_Channels::set_output_scaled(SRV_Channel::k_elevon_right, elevator_mix + aileron_mix);
     511       98357 :     SRV_Channels::set_output_scaled(SRV_Channel::k_vtail_right, elevator_mix - rudder_mix);
     512       98357 :     SRV_Channels::set_output_scaled(SRV_Channel::k_vtail_left, elevator_mix + rudder_mix);
     513             : 
     514       98357 :     if (roll_lim) {
     515        2793 :         motors->limit.roll = true;
     516             :     }
     517       98357 :     if (pitch_lim || tilt_lim) {
     518        6400 :         motors->limit.pitch = true;
     519             :     }
     520       98357 :     if (yaw_lim || tilt_lim) {
     521           0 :         motors->limit.yaw = true;
     522             :     }
     523             : 
     524             : }
     525             : 
     526             : 
     527             : /*
     528             :   return true when we have completed enough of a transition to switch to fixed wing control
     529             :  */
     530        2107 : bool Tailsitter::transition_fw_complete(void)
     531             : {
     532        2107 :     if (!plane.arming.is_armed_and_safety_off()) {
     533             :         // instant transition when disarmed, no message
     534           0 :         return true;
     535             :     }
     536        2107 :     if (labs(quadplane.ahrs_view->pitch_sensor) > transition_angle_fw*100) {
     537           3 :         gcs().send_text(MAV_SEVERITY_INFO, "Transition FW done");
     538           3 :         return true;
     539             :     }
     540        2104 :     if (labs(quadplane.ahrs_view->roll_sensor) > MAX(4500, plane.roll_limit_cd + 500)) {
     541           0 :         gcs().send_text(MAV_SEVERITY_WARNING, "Transition FW done, roll error");
     542           0 :         return true;
     543             :     }
     544        2104 :     if (AP_HAL::millis() - transition->fw_transition_start_ms > ((transition_angle_fw+(transition->fw_transition_initial_pitch*0.01f))/transition_rate_fw)*1500) {
     545           0 :         gcs().send_text(MAV_SEVERITY_WARNING, "Transition FW done, timeout");
     546           0 :         return true;
     547             :     }
     548             :     // still waiting
     549        2104 :     return false;
     550             : }
     551             : 
     552             : 
     553             : /*
     554             :   return true when we have completed enough of a transition to switch to VTOL control
     555             :  */
     556        1220 : bool Tailsitter::transition_vtol_complete(void) const
     557             : {
     558        1220 :     if (!plane.arming.is_armed_and_safety_off()) {
     559             :         // instant transition when disarmed, no message
     560           1 :         return true;
     561             :     }
     562             :     // for vectored tailsitters at zero pilot throttle
     563        1219 :     if ((quadplane.get_pilot_throttle() < 0.05f) && _is_vectored) {
     564             :         // if we are not moving (hence on the ground?) or don't know
     565             :         // transition immediately to tilt motors up and prevent prop strikes
     566           0 :         if (quadplane.ahrs.groundspeed() < 1.0f) {
     567           0 :             gcs().send_text(MAV_SEVERITY_INFO, "Transition VTOL done, zero throttle");
     568           0 :             return true;
     569             :         }
     570             :     }
     571        1219 :     const float trans_angle = get_transition_angle_vtol();
     572        1219 :     if (labs(plane.ahrs.pitch_sensor) > trans_angle*100) {
     573           2 :         gcs().send_text(MAV_SEVERITY_INFO, "Transition VTOL done");
     574           2 :         return true;
     575             :     }
     576        1217 :     int32_t roll_cd = labs(plane.ahrs.roll_sensor);
     577        1217 :     if (plane.fly_inverted()) {
     578           0 :         roll_cd = 18000 - roll_cd;
     579             :     }
     580        1217 :     if (roll_cd > MAX(4500, plane.roll_limit_cd + 500)) {
     581           0 :         gcs().send_text(MAV_SEVERITY_WARNING, "Transition VTOL done, roll error");
     582           0 :         return true;
     583             :     }
     584        1217 :     if (AP_HAL::millis() - transition->vtol_transition_start_ms >  ((trans_angle-(transition->vtol_transition_initial_pitch*0.01f))/transition_rate_vtol)*1500) {
     585           3 :         gcs().send_text(MAV_SEVERITY_WARNING, "Transition VTOL done, timeout");
     586           3 :         return true;
     587             :     }
     588        1214 :     return false;
     589             : }
     590             : 
     591             : // handle different tailsitter input types
     592     2710433 : void Tailsitter::check_input(void)
     593             : {
     594     2710433 :     if (active() && (input_type & TAILSITTER_INPUT_PLANE)) {
     595             :         // the user has asked for body frame controls when tailsitter
     596             :         // is active. We switch around the control_in value for the
     597             :         // channels to do this, as that ensures the value is
     598             :         // consistent throughout the code
     599           0 :         int16_t roll_in = plane.channel_roll->get_control_in();
     600           0 :         int16_t yaw_in = plane.channel_rudder->get_control_in();
     601           0 :         plane.channel_roll->set_control_in(yaw_in);
     602           0 :         plane.channel_rudder->set_control_in(-roll_in);
     603             :     }
     604     2710433 : }
     605             : 
     606             : /*
     607             :   return true if we are a tailsitter transitioning to VTOL flight
     608             :  */
     609     7785772 : bool Tailsitter::in_vtol_transition(uint32_t now) const
     610             : {
     611     7785772 :     if (!enabled() || !quadplane.in_vtol_mode()) {
     612     7234125 :         return false;
     613             :     }
     614      551647 :     if (transition->transition_state == Tailsitter_Transition::State::ANGLE_WAIT_VTOL) {
     615        7687 :         return true;
     616             :     }
     617      543960 :     if ((now != 0) && ((now - transition->last_vtol_mode_ms) > 1000)) {
     618             :         // only just come out of forward flight
     619          16 :         return true;
     620             :     }
     621      543944 :     return false;
     622             : }
     623             : 
     624             : /*
     625             :   return true if we are a tailsitter in FW flight
     626             :  */
     627    10219728 : bool Tailsitter::is_in_fw_flight(void) const
     628             : {
     629    10219728 :     return enabled() && !quadplane.in_vtol_mode() && transition->transition_state == Tailsitter_Transition::State::DONE;
     630             : }
     631             : 
     632             : /*
     633             :  return the tailsitter.transition_angle_vtol value if non zero, otherwise returns the tailsitter.transition_angle_fw value.
     634             :  */
     635        1219 : int8_t Tailsitter::get_transition_angle_vtol() const
     636             : {
     637        1219 :     if (transition_angle_vtol == 0) {
     638        1219 :         return transition_angle_fw;
     639             :     }
     640           0 :     return transition_angle_vtol;
     641             : }
     642             : 
     643             : 
     644             : /*
     645             :   account for speed scaling of control surfaces in VTOL modes
     646             : */
     647       78495 : void Tailsitter::speed_scaling(void)
     648             : {
     649       78495 :     const float hover_throttle = motors->get_throttle_hover();
     650       78495 :     const float throttle = motors->get_throttle_out();
     651       78495 :     float spd_scaler = 1.0f;
     652       78495 :     float disk_loading_min_throttle = 0.0;
     653             : 
     654             :     // Scaling with throttle
     655       78495 :     float throttle_scaler = throttle_scale_max;
     656       78495 :     if (is_positive(throttle)) {
     657       76945 :         throttle_scaler = constrain_float(hover_throttle / throttle, gain_scaling_min, throttle_scale_max);
     658             :     }
     659             : 
     660       78495 :     if ((gain_scaling_mask & TAILSITTER_GSCL_ATT_THR) != 0) {
     661             :         // reduce gains when flying at high speed in Q modes:
     662             : 
     663             :         // critical parameter: violent oscillations if too high
     664             :         // sudden loss of attitude control if too low
     665           0 :         const float min_scale = gain_scaling_min;
     666           0 :         float tthr = 1.25f * hover_throttle;
     667             : 
     668             :         // reduce control surface throws at large tilt
     669             :         // angles (assuming high airspeed)
     670             :         // ramp down from 1 to max_atten at tilt angles over trans_angle
     671             :         // (angles here are represented by their cosines)
     672             : 
     673             :         // Note that the cosf call will be necessary if trans_angle becomes a parameter
     674             :         // but the C language spec does not guarantee that trig functions can be used
     675             :         // in constant expressions, even though gcc currently allows it.
     676           0 :         constexpr float c_trans_angle = 0.9238795; // cosf(.125f * M_PI)
     677             : 
     678             :         // alpha = (1 - max_atten) / (c_trans_angle - cosf(radians(90)));
     679           0 :         const float alpha = (1 - min_scale) / c_trans_angle;
     680           0 :         const float beta = 1 - alpha * c_trans_angle;
     681             : 
     682           0 :         const float c_tilt = quadplane.ahrs_view->get_rotation_body_to_ned().c.z;
     683           0 :         if (c_tilt < c_trans_angle) {
     684           0 :             spd_scaler = constrain_float(beta + alpha * c_tilt, min_scale, 1.0f);
     685             :             // reduce throttle attenuation threshold too
     686           0 :             tthr = 0.5f * hover_throttle;
     687             :         }
     688             :         // if throttle is above hover thrust, apply additional attenuation
     689           0 :         if (throttle > tthr) {
     690           0 :             const float throttle_atten = 1 - (throttle - tthr) / (1 - tthr);
     691           0 :             spd_scaler *= throttle_atten;
     692           0 :             spd_scaler = constrain_float(spd_scaler, min_scale, 1.0f);
     693             :         }
     694             : 
     695             :         // limit positive and negative slew rates of applied speed scaling
     696           0 :         constexpr float posTC = 2.0f;   // seconds
     697           0 :         constexpr float negTC = 1.0f;   // seconds
     698           0 :         const float posdelta = plane.G_Dt / posTC;
     699           0 :         const float negdelta = plane.G_Dt / negTC;
     700           0 :         spd_scaler = constrain_float(spd_scaler, last_spd_scaler - negdelta, last_spd_scaler + posdelta);
     701           0 :         last_spd_scaler = spd_scaler;
     702             : 
     703             :         // also apply throttle scaling if enabled
     704           0 :         if ((spd_scaler >= 1.0f) && ((gain_scaling_mask & TAILSITTER_GSCL_THROTTLE) != 0)) {
     705           0 :             spd_scaler = MAX(throttle_scaler,1.0f);
     706             :         }
     707             : 
     708       78495 :     } else if (((gain_scaling_mask & TAILSITTER_GSCL_DISK_THEORY) != 0) && is_positive(disk_loading.get())) {
     709             :         // Use disk theory to estimate the velocity over the control surfaces
     710             :         // https://web.mit.edu/16.unified/www/FALL/thermodynamics/notes/node86.html
     711             : 
     712             :         float airspeed;
     713           0 :         if (!quadplane.ahrs.airspeed_EAS(airspeed)) {
     714             :             // No airspeed estimate, use throttle scaling
     715           0 :             spd_scaler = throttle_scaler;
     716             : 
     717             :         } else {
     718             : 
     719             : 
     720             :             // use the equation: T = 0.5 * rho * A (Ue^2 - U0^2) solved for Ue^2:
     721             :             // Ue^2 = (T / (0.5 * rho *A)) + U0^2
     722             :             // We don't know thrust or disk area, use T = (throttle/throttle_hover) * weight
     723             :             // ((t / t_h ) * weight) / (0.5 * rho * A) = ((t / t_h) * mass * 9.81) / (0.5 * rho * A)
     724             :             // (mass / A) is disk loading DL so:
     725             :             // Ue^2 = (((t / t_h) * DL * 9.81)/(0.5 * rho)) + U0^2
     726             : 
     727           0 :             const float rho = SSL_AIR_DENSITY * quadplane.ahrs.get_air_density_ratio();
     728           0 :             float hover_rho = rho;
     729           0 :             if ((gain_scaling_mask & TAILSITTER_GSCL_ALTITUDE) != 0) {
     730             :                 // if applying altitude correction use sea level density for hover case
     731           0 :                 hover_rho = SSL_AIR_DENSITY;
     732             :             }
     733             : 
     734             :             // hover case: (t / t_h) = 1 and U0 = 0
     735           0 :             const float sq_hover_outflow = (disk_loading.get() * GRAVITY_MSS) / (0.5f * hover_rho);
     736             : 
     737             : 
     738             :             // calculate the true outflow speed
     739           0 :             const float sq_outflow = (((throttle/hover_throttle) *  disk_loading.get() * GRAVITY_MSS) / (0.5f * rho)) + sq(MAX(airspeed,0));
     740             : 
     741             :             // Scale by the ratio of squared hover outflow velocity to squared actual outflow velocity
     742           0 :             spd_scaler = throttle_scale_max;
     743           0 :             if (is_positive(sq_outflow)) {
     744           0 :                 spd_scaler = constrain_float(sq_hover_outflow / sq_outflow, gain_scaling_min.get(), throttle_scale_max.get());
     745             :             }
     746             : 
     747           0 :             if (is_positive(disk_loading_min_outflow)) {
     748             :                 // calculate throttle required to give minimum outflow speed over control surfaces
     749           0 :                 if (is_positive(airspeed)) {
     750           0 :                     disk_loading_min_throttle = (((sq(disk_loading_min_outflow) - sq(airspeed)) * (0.5 * rho)) / (disk_loading.get() * GRAVITY_MSS)) * hover_throttle;
     751             :                 } else {
     752             :                     // estimate backwards airspeed
     753           0 :                     float reverse_airspeed = 0.0;
     754           0 :                     Vector3f vel;
     755           0 :                     if (quadplane.ahrs.get_velocity_NED(vel)) {
     756           0 :                         Vector3f wind;
     757             :                         // use the estimate even if it is not marked
     758             :                         // valid, to preserve existing behaviour
     759           0 :                         IGNORE_RETURN(quadplane.ahrs.get_wind(wind));
     760           0 :                         reverse_airspeed = quadplane.ahrs.earth_to_body(vel - wind).x;
     761             :                     }
     762             :                     // make sure actually negative
     763           0 :                     reverse_airspeed = MIN(reverse_airspeed, 0.0);
     764           0 :                     disk_loading_min_throttle = (((sq(disk_loading_min_outflow) + sq(reverse_airspeed)) * (0.5 * rho)) / (disk_loading.get() * GRAVITY_MSS)) * hover_throttle;
     765             :                 }
     766           0 :                 disk_loading_min_throttle = MAX(disk_loading_min_throttle, 0.0);
     767             :             }
     768             :         }
     769             : 
     770       78495 :     } else if ((gain_scaling_mask & TAILSITTER_GSCL_THROTTLE) != 0) {
     771       78495 :         spd_scaler = throttle_scaler;
     772             :     }
     773             : 
     774       78495 :     if ((gain_scaling_mask & TAILSITTER_GSCL_ALTITUDE) != 0) {
     775             :         // air density correction
     776           0 :         spd_scaler /= quadplane.ahrs.get_air_density_ratio();
     777             :     }
     778             : 
     779       78495 :     const SRV_Channel::Function functions[] = {
     780             :         SRV_Channel::Function::k_aileron,
     781             :         SRV_Channel::Function::k_elevator,
     782             :         SRV_Channel::Function::k_rudder,
     783             :         SRV_Channel::Function::k_tiltMotorLeft,
     784             :         SRV_Channel::Function::k_tiltMotorRight};
     785      470970 :     for (uint8_t i=0; i<ARRAY_SIZE(functions); i++) {
     786      392475 :         float v = SRV_Channels::get_output_scaled(functions[i]);
     787      392475 :         if ((functions[i] == SRV_Channel::Function::k_tiltMotorLeft) || (functions[i] == SRV_Channel::Function::k_tiltMotorRight)) {
     788             :             // always apply throttle scaling to tilts
     789      156990 :             v *= throttle_scaler;
     790             :         } else {
     791      235485 :             v *= spd_scaler;
     792             :         }
     793      392475 :         SRV_Channels::set_output_scaled(functions[i], v);
     794             :     }
     795             : 
     796       78495 :     if (tailsitter_motors != nullptr) {
     797           0 :         tailsitter_motors->set_min_throttle(disk_loading_min_throttle);
     798             :     }
     799             : 
     800             :     // Record for log
     801       78495 :     log_data.throttle_scaler = throttle_scaler;
     802       78495 :     log_data.speed_scaler = spd_scaler;
     803       78495 :     log_data.min_throttle = disk_loading_min_throttle;
     804             : 
     805       78495 : }
     806             : 
     807             : #if HAL_LOGGING_ENABLED
     808             : // Write tailsitter specific log
     809       91778 : void Tailsitter::write_log()
     810             : {
     811       91778 :     if (!enabled()) {
     812       89159 :         return;
     813             :     }
     814             : 
     815        2619 :     struct log_tailsitter pkt = {
     816             :         LOG_PACKET_HEADER_INIT(LOG_TSIT_MSG),
     817        2619 :         time_us             : AP_HAL::micros64(),
     818        2619 :         throttle_scaler     : log_data.throttle_scaler,
     819        2619 :         speed_scaler        : log_data.speed_scaler,
     820        2619 :         min_throttle        : log_data.min_throttle,
     821        2619 :     };
     822        2619 :     plane.logger.WriteBlock(&pkt, sizeof(pkt));
     823             : }
     824             : #endif  // HAL_LOGGING_ENABLED
     825             : 
     826             : // return true if pitch control should be relaxed
     827             : // on vectored belly sitters the pitch control is not relaxed in order to keep motors pointing and avoid risk of props hitting the ground
     828             : // always relax after a transition
     829      318754 : bool Tailsitter::relax_pitch()
     830             : {
     831      318754 :     return !enabled() || !_is_vectored || (transition->vtol_limit_start_ms != 0);
     832             : }
     833             : 
     834             : /*
     835             :   update for transition from quadplane to fixed wing mode
     836             :  */
     837       62355 : void Tailsitter_Transition::update()
     838             : {
     839       62355 :     const uint32_t now = millis();
     840             : 
     841             :     float aspeed;
     842       62355 :     bool have_airspeed = quadplane.ahrs.airspeed_EAS(aspeed);
     843       62355 :     quadplane.assisted_flight = quadplane.assist.should_assist(aspeed, have_airspeed);
     844             : 
     845       62355 :     if (transition_state != State::DONE) {
     846             :         // during transition we ask TECS to use a synthetic
     847             :         // airspeed. Otherwise the pitch limits will throw off the
     848             :         // throttle calculation which is driven by pitch
     849        2107 :         plane.TECS_controller.use_synthetic_airspeed();
     850             :     }
     851             : 
     852       62355 :     switch (transition_state) {
     853             : 
     854        2107 :     case State::ANGLE_WAIT_FW: {
     855        2107 :         if (tailsitter.transition_fw_complete()) {
     856           3 :             transition_state = State::DONE;
     857           3 :             if (plane.arming.is_armed_and_safety_off()) {
     858           3 :                 fw_limit_start_ms = now;
     859           3 :                 fw_limit_initial_pitch = constrain_float(quadplane.ahrs.pitch_sensor,-8500,8500);
     860           3 :                 plane.nav_pitch_cd = fw_limit_initial_pitch;
     861           3 :                 plane.nav_roll_cd = 0;
     862             :             }
     863           3 :             break;
     864             :         }
     865        2104 :         quadplane.set_desired_spool_state(AP_Motors::DesiredSpoolState::THROTTLE_UNLIMITED);
     866        2104 :         quadplane.assisted_flight = true;
     867        2104 :         uint32_t dt = now - fw_transition_start_ms;
     868             :         // multiply by 0.1 to convert (degrees/second * milliseconds) to centi degrees
     869        2104 :         plane.nav_pitch_cd = constrain_float(fw_transition_initial_pitch - (quadplane.tailsitter.transition_rate_fw * dt) * 0.1f * (plane.fly_inverted()?-1.0f:1.0f), -8500, 8500);
     870        2104 :         plane.nav_roll_cd = 0;
     871        2104 :         quadplane.disable_yaw_rate_time_constant();
     872        2104 :         quadplane.attitude_control->input_euler_angle_roll_pitch_euler_rate_yaw_cd(plane.nav_roll_cd,
     873        2104 :                                                                       plane.nav_pitch_cd,
     874             :                                                                       0);
     875             :         // set throttle at either hover throttle or current throttle, whichever is higher, through the transition
     876        2104 :         quadplane.attitude_control->set_throttle_out(MAX(motors->get_throttle_hover(),quadplane.attitude_control->get_throttle_in()), true, 0);
     877        2104 :         quadplane.motors_output();
     878        2104 :         break;
     879             :     }
     880             : 
     881           0 :     case State::ANGLE_WAIT_VTOL:
     882             :         // nothing to do, this is handled in the fixed wing attitude controller
     883           0 :         break;
     884             : 
     885       60248 :     case State::DONE:
     886       60248 :         break;
     887             :     }
     888       62355 : }
     889             : 
     890       94901 : void Tailsitter_Transition::VTOL_update()
     891             : {
     892       94901 :     const uint32_t now = AP_HAL::millis();
     893             : 
     894       94901 :     if ((now - last_vtol_mode_ms) > 1000) {
     895             :         /*
     896             :           we are just entering a VTOL mode as a tailsitter, set
     897             :           our transition state so the fixed wing controller brings
     898             :           the nose up before we start trying to fly as a
     899             :           multicopter
     900             :          */
     901           6 :         transition_state = State::ANGLE_WAIT_VTOL;
     902             :     }
     903       94901 :     last_vtol_mode_ms = now;
     904             : 
     905       94901 :     if (transition_state == State::ANGLE_WAIT_VTOL) {
     906             :         float aspeed;
     907        1220 :         bool have_airspeed = quadplane.ahrs.airspeed_EAS(aspeed);
     908             :         // provide assistance in forward flight portion of tailsitter transition
     909        1220 :         quadplane.assisted_flight = quadplane.assist.should_assist(aspeed, have_airspeed);
     910        1220 :         if (!quadplane.tailsitter.transition_vtol_complete()) {
     911        1214 :             return;
     912             :         }
     913             :         // transition to VTOL complete, if armed set vtol rate limit starting point
     914           6 :         if (plane.arming.is_armed_and_safety_off()) {
     915           5 :             vtol_limit_start_ms = now;
     916           5 :             vtol_limit_initial_pitch = quadplane.ahrs_view->pitch_sensor;
     917             :         }
     918             : 
     919             :         // clear inverted flight flag to make behaviour consistent
     920             :         // with other quadplane types
     921           6 :         plane.inverted_flight = false;
     922             :     } else {
     923             :         // Keep assistance reset while not checking
     924       93681 :         quadplane.assist.reset();
     925             :     }
     926       93687 :     restart();
     927             : }
     928             : 
     929             : // return true if we should show VTOL view
     930      107046 : bool Tailsitter_Transition::show_vtol_view() const
     931             : {
     932      107046 :     bool show_vtol = quadplane.in_vtol_mode();
     933             : 
     934      107046 :     if (show_vtol && (transition_state == State::ANGLE_WAIT_VTOL)) {
     935             :         // in a vtol mode but still transitioning from forward flight
     936        1435 :         return false;
     937             :     }
     938      105611 :     if (!show_vtol && (transition_state == State::ANGLE_WAIT_FW)) {
     939             :         // not in VTOL mode but still transitioning from VTOL
     940        2494 :         return true;
     941             :     }
     942      103117 :     return show_vtol;
     943             : }
     944             : 
     945      157760 : void Tailsitter_Transition::set_FW_roll_pitch(int32_t& nav_pitch_cd, int32_t& nav_roll_cd)
     946             : {
     947      157760 :     uint32_t now = AP_HAL::millis();
     948      157760 :     if (tailsitter.in_vtol_transition(now)) {
     949             :         /*
     950             :           during transition to vtol in a tailsitter try to raise the
     951             :           nose while keeping the wings level
     952             :          */
     953        1220 :         uint32_t dt = now - vtol_transition_start_ms;
     954             :         // multiply by 0.1 to convert (degrees/second * milliseconds) to centi degrees
     955        1220 :         nav_pitch_cd = constrain_float(vtol_transition_initial_pitch + (tailsitter.transition_rate_vtol * dt) * 0.1f, -8500, 8500);
     956        1220 :         nav_roll_cd = 0;
     957             : 
     958      156540 :     } else if (transition_state == State::DONE) {
     959             :         // still in FW, reset transition starting point
     960       60752 :         vtol_transition_start_ms = now;
     961       60752 :         vtol_transition_initial_pitch = constrain_float(plane.nav_pitch_cd,-8500,8500);
     962             : 
     963             :         // rate limit initial pitch down
     964       60752 :         if (fw_limit_start_ms != 0) {
     965        2240 :             const float pitch_limit_cd = fw_limit_initial_pitch - (now - fw_limit_start_ms) * tailsitter.transition_rate_fw * 0.1;
     966        2240 :             if ((pitch_limit_cd <= 0) || (nav_pitch_cd >= pitch_limit_cd)) {
     967             :                 // never limit past 0, never limit to a smaller pitch angle
     968           3 :                 fw_limit_start_ms = 0;
     969             :             } else {
     970        2237 :                 nav_pitch_cd = pitch_limit_cd;
     971        2237 :                 nav_roll_cd = 0;
     972             :             }
     973             :         }
     974             :     }
     975      157760 : }
     976             : 
     977       55978 : bool Tailsitter_Transition::allow_stick_mixing() const
     978             : {
     979             :     // Transitioning into VTOL flight, initial pitch up
     980       55978 :     if (tailsitter.in_vtol_transition()) {
     981         409 :         return false;
     982             :     }
     983             :     // Transitioning into fixed wing flight, levelling off
     984       55569 :     if ((transition_state == State::DONE) && (fw_limit_start_ms != 0)) {
     985        2237 :         return false;
     986             :     }
     987       53332 :     return true;
     988             : }
     989             : 
     990      104173 : bool Tailsitter_Transition::set_VTOL_roll_pitch_limit(int32_t& nav_roll_cd, int32_t& nav_pitch_cd)
     991             : {
     992      104173 :     if (vtol_limit_start_ms == 0) {
     993      103165 :         return false;
     994             :     }
     995             :     // prevent pitching towards 0 too quickly
     996        1008 :     const float pitch_change_cd = (AP_HAL::millis() - vtol_limit_start_ms) * tailsitter.transition_rate_vtol * 0.1;
     997        1008 :     if (pitch_change_cd > fabsf(vtol_limit_initial_pitch)) {
     998             :         // limit has passed 0, nothing to do
     999           2 :         vtol_limit_start_ms = 0;
    1000           2 :         return false;
    1001             :     }
    1002             :     // continue limiting while limit angle is larger than desired angle
    1003        1006 :     if (is_negative(vtol_limit_initial_pitch)) {
    1004        1006 :         const float pitch_limit = vtol_limit_initial_pitch + pitch_change_cd;
    1005        1006 :         if (nav_pitch_cd > pitch_limit) {
    1006        1005 :             nav_pitch_cd = pitch_limit;
    1007        1005 :             nav_roll_cd = 0;
    1008        1005 :             return true;
    1009             :         }
    1010             :     } else {
    1011           0 :         const float pitch_limit = vtol_limit_initial_pitch - pitch_change_cd;
    1012           0 :         if (nav_pitch_cd < pitch_limit) {
    1013           0 :             nav_pitch_cd = pitch_limit;
    1014           0 :             nav_roll_cd = 0;
    1015           0 :             return true;
    1016             :         }
    1017             :     }
    1018           1 :     vtol_limit_start_ms = 0;
    1019           1 :     return false;
    1020             : }
    1021             : 
    1022             : // setup for the transition back to fixed wing
    1023       93689 : void Tailsitter_Transition::restart()
    1024             : {
    1025       93689 :     transition_state = State::ANGLE_WAIT_FW;
    1026       93689 :     fw_transition_start_ms = AP_HAL::millis();
    1027       93689 :     fw_transition_initial_pitch = constrain_float(quadplane.attitude_control->get_attitude_target_quat().get_euler_pitch() * degrees(100.0),-8500,8500);
    1028       93689 : }
    1029             : 
    1030             : // force state to FW and setup for the transition back to VTOL
    1031         510 : void Tailsitter_Transition::force_transition_complete()
    1032             : {
    1033         510 :     transition_state = State::DONE;
    1034         510 :     vtol_transition_start_ms = AP_HAL::millis();
    1035         510 :     vtol_transition_initial_pitch = constrain_float(plane.nav_pitch_cd,-8500,8500);
    1036         510 :     fw_limit_start_ms = 0;
    1037             : 
    1038         510 :     quadplane.assist.reset();
    1039         510 : }
    1040             : 
    1041           0 : MAV_VTOL_STATE Tailsitter_Transition::get_mav_vtol_state() const
    1042             : {
    1043           0 :     switch (transition_state) {
    1044           0 :         case State::ANGLE_WAIT_VTOL:
    1045           0 :             return MAV_VTOL_STATE_TRANSITION_TO_MC;
    1046             : 
    1047           0 :         case State::DONE:
    1048           0 :             return MAV_VTOL_STATE_FW;
    1049             : 
    1050           0 :         case State::ANGLE_WAIT_FW: {
    1051           0 :             if (quadplane.in_vtol_mode()) {
    1052           0 :                 return MAV_VTOL_STATE_MC;
    1053             :             }
    1054           0 :             return MAV_VTOL_STATE_TRANSITION_TO_FW;
    1055             :         }
    1056             :     }
    1057             : 
    1058           0 :     return MAV_VTOL_STATE_UNDEFINED;
    1059             : }
    1060             : 
    1061             : // only allow to weathervane once transition is complete and desired pitch has been reached
    1062       73885 : bool Tailsitter_Transition::allow_weathervane()
    1063             : {
    1064       73885 :     return !tailsitter.in_vtol_transition() && (vtol_limit_start_ms == 0);
    1065             : }
    1066             : 
    1067             : #endif  // HAL_QUADPLANE_ENABLED

Generated by: LCOV version 1.14