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

          Line data    Source code
       1             : #include "tiltrotor.h"
       2             : #include "Plane.h"
       3             : 
       4             : #if HAL_QUADPLANE_ENABLED
       5             : const AP_Param::GroupInfo Tiltrotor::var_info[] = {
       6             : 
       7             :     // @Param: ENABLE
       8             :     // @DisplayName: Enable Tiltrotor functionality
       9             :     // @Values: 0:Disable, 1:Enable
      10             :     // @Description: This enables Tiltrotor functionality
      11             :     // @User: Standard
      12             :     // @RebootRequired: True
      13             :     AP_GROUPINFO_FLAGS("ENABLE", 1, Tiltrotor, enable, 0, AP_PARAM_FLAG_ENABLE),
      14             : 
      15             :     // @Param: MASK
      16             :     // @DisplayName: Tiltrotor mask
      17             :     // @Description: This is a bitmask of motors that are tiltable in a tiltrotor (or tiltwing). The mask is in terms of the standard motor order for the frame type.
      18             :     // @User: Standard
      19             :     // @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
      20             :     AP_GROUPINFO("MASK", 2, Tiltrotor, tilt_mask, 0),
      21             : 
      22             :     // @Param: RATE_UP
      23             :     // @DisplayName: Tiltrotor upwards tilt rate
      24             :     // @Description: This is the maximum speed at which the motor angle will change for a tiltrotor when moving from forward flight to hover
      25             :     // @Units: deg/s
      26             :     // @Increment: 1
      27             :     // @Range: 10 300
      28             :     // @User: Standard
      29             :     AP_GROUPINFO("RATE_UP", 3, Tiltrotor, max_rate_up_dps, 40),
      30             : 
      31             :     // @Param: MAX
      32             :     // @DisplayName: Tiltrotor maximum VTOL angle
      33             :     // @Description: This is the maximum angle of the tiltable motors at which multicopter control will be enabled. Beyond this angle the plane will fly solely as a fixed wing aircraft and the motors will tilt to their maximum angle at the TILT_RATE
      34             :     // @Units: deg
      35             :     // @Increment: 1
      36             :     // @Range: 20 80
      37             :     // @User: Standard
      38             :     AP_GROUPINFO("MAX", 4, Tiltrotor, max_angle_deg, 45),
      39             : 
      40             :     // @Param: TYPE
      41             :     // @DisplayName: Tiltrotor type
      42             :     // @Description: This is the type of tiltrotor when TILT_MASK is non-zero. A continuous tiltrotor can tilt the rotors to any angle on demand. A binary tiltrotor assumes a retract style servo where the servo is either fully forward or fully up. In both cases the servo can't move faster than Q_TILT_RATE. A vectored yaw tiltrotor will use the tilt of the motors to control yaw in hover, Bicopter tiltrotor must use the tailsitter frame class (10)
      43             :     // @Values: 0:Continuous,1:Binary,2:VectoredYaw,3:Bicopter
      44             :     AP_GROUPINFO("TYPE", 5, Tiltrotor, type, TILT_TYPE_CONTINUOUS),
      45             : 
      46             :     // @Param: RATE_DN
      47             :     // @DisplayName: Tiltrotor downwards tilt rate
      48             :     // @Description: This is the maximum speed at which the motor angle will change for a tiltrotor when moving from hover to forward flight. When this is zero the Q_TILT_RATE_UP value is used.
      49             :     // @Units: deg/s
      50             :     // @Increment: 1
      51             :     // @Range: 10 300
      52             :     // @User: Standard
      53             :     AP_GROUPINFO("RATE_DN", 6, Tiltrotor, max_rate_down_dps, 0),
      54             : 
      55             :     // @Param: YAW_ANGLE
      56             :     // @DisplayName: Tilt minimum angle for vectored yaw
      57             :     // @Description: This is the angle of the tilt servos when in VTOL mode and at minimum output (fully back). This needs to be set in addition to Q_TILT_TYPE=2, to enable vectored control for yaw in tilt quadplanes. This is also used to limit the forward travel of bicopter tilts(Q_TILT_TYPE=3) when in VTOL modes.
      58             :     // @Range: 0 30
      59             :     AP_GROUPINFO("YAW_ANGLE", 7, Tiltrotor, tilt_yaw_angle, 0),
      60             : 
      61             :     // @Param: FIX_ANGLE
      62             :     // @DisplayName: Fixed wing tiltrotor angle
      63             :     // @Description: This is the angle the motors tilt down when at maximum output for forward flight. Set this to a non-zero value to enable vectoring for roll/pitch in forward flight on tilt-vectored aircraft
      64             :     // @Units: deg
      65             :     // @Range: 0 30
      66             :     // @User: Standard
      67             :     AP_GROUPINFO("FIX_ANGLE", 8, Tiltrotor, fixed_angle, 0),
      68             : 
      69             :     // @Param: FIX_GAIN
      70             :     // @DisplayName: Fixed wing tiltrotor gain
      71             :     // @Description: This is the gain for use of tilting motors in fixed wing flight for tilt vectored quadplanes
      72             :     // @Range: 0 1
      73             :     // @User: Standard
      74             :     AP_GROUPINFO("FIX_GAIN", 9, Tiltrotor, fixed_gain, 0),
      75             : 
      76             :     // @Param: WING_FLAP
      77             :     // @DisplayName: Tiltrotor tilt angle that will be used as flap
      78             :     // @Description: For use on tilt wings, the wing will tilt up to this angle for flap, transition will be complete when the wing reaches this angle from the forward fight position, 0 disables
      79             :     // @Units: deg
      80             :     // @Increment: 1
      81             :     // @Range: 0 15
      82             :     // @User: Standard
      83             :     AP_GROUPINFO("WING_FLAP", 10, Tiltrotor, flap_angle_deg, 0),
      84             : 
      85             :     AP_GROUPEND
      86             : };
      87             : 
      88             : /*
      89             :   control code for tiltrotors and tiltwings. Enabled by setting
      90             :   Q_TILT_MASK to a non-zero value
      91             :  */
      92             : 
      93         692 : Tiltrotor::Tiltrotor(QuadPlane& _quadplane, AP_MotorsMulticopter*& _motors):quadplane(_quadplane),motors(_motors)
      94             : {
      95         692 :     AP_Param::setup_object_defaults(this, var_info);
      96         692 : }
      97             : 
      98         201 : void Tiltrotor::setup()
      99             : {
     100             : 
     101         201 :     if (!enable.configured() && ((tilt_mask != 0) || (type == TILT_TYPE_BICOPTER))) {
     102           3 :         enable.set_and_save(1);
     103             :     }
     104             : 
     105         201 :     if (enable <= 0) {
     106         188 :         return;
     107             :     }
     108             : 
     109          13 :     quadplane.thrust_type = QuadPlane::ThrustType::TILTROTOR;
     110             : 
     111          13 :     _is_vectored = tilt_mask != 0 && type == TILT_TYPE_VECTORED_YAW;
     112             : 
     113             :     // true if a fixed forward motor is configured, either throttle, throttle left  or throttle right.
     114             :     // bicopter tiltrotors use throttle left and right as tilting motors, so they don't count in that case.
     115          29 :     _have_fw_motor = SRV_Channels::function_assigned(SRV_Channel::k_throttle) ||
     116          16 :                     ((SRV_Channels::function_assigned(SRV_Channel::k_throttleLeft) || SRV_Channels::function_assigned(SRV_Channel::k_throttleRight))
     117           0 :                         && (type != TILT_TYPE_BICOPTER));
     118             : 
     119             : 
     120             :     // check if there are any permanent VTOL motors
     121         129 :     for (uint8_t i = 0; i < AP_MOTORS_MAX_NUM_MOTORS; ++i) {
     122         126 :         if (motors->is_motor_enabled(i) && !is_motor_tilting(i)) {
     123             :             // enabled motor not set in tilt mask
     124          10 :             _have_vtol_motor = true;
     125          10 :             break;
     126             :         }
     127             :     }
     128             : 
     129          13 :     if (_is_vectored) {
     130             :         // we will be using vectoring for yaw
     131           7 :         motors->disable_yaw_torque();
     132             :     }
     133             : 
     134          13 :     if (tilt_mask != 0) {
     135             :         // setup tilt compensation
     136          13 :         motors->set_thrust_compensation_callback(FUNCTOR_BIND_MEMBER(&Tiltrotor::tilt_compensate, void, float *, uint8_t));
     137          13 :         if (type == TILT_TYPE_VECTORED_YAW) {
     138             :             // setup tilt servos for vectored yaw
     139           7 :             SRV_Channels::set_range(SRV_Channel::k_tiltMotorLeft,  1000);
     140           7 :             SRV_Channels::set_range(SRV_Channel::k_tiltMotorRight, 1000);
     141           7 :             SRV_Channels::set_range(SRV_Channel::k_tiltMotorRear,  1000);
     142           7 :             SRV_Channels::set_range(SRV_Channel::k_tiltMotorRearLeft, 1000);
     143           7 :             SRV_Channels::set_range(SRV_Channel::k_tiltMotorRearRight, 1000);
     144             :         }
     145             :     }
     146             : 
     147          13 :     transition = NEW_NOTHROW Tiltrotor_Transition(quadplane, motors, *this);
     148          13 :     if (!transition) {
     149           0 :         AP_BoardConfig::allocation_error("tiltrotor transition");
     150             :     }
     151          13 :     quadplane.transition = transition;
     152             : 
     153          13 :     setup_complete = true;
     154             : }
     155             : 
     156             : /*
     157             :   calculate maximum tilt change as a proportion from 0 to 1 of tilt
     158             :  */
     159     1096220 : float Tiltrotor::tilt_max_change(bool up, bool in_flap_range) const
     160             : {
     161             :     float rate;
     162     1096220 :     if (up || max_rate_down_dps <= 0) {
     163      892202 :         rate = max_rate_up_dps;
     164             :     } else {
     165      204018 :         rate = max_rate_down_dps;
     166             :     }
     167     1096220 :     if (type != TILT_TYPE_BINARY && !up && !in_flap_range) {
     168      895116 :         bool fast_tilt = false;
     169      895116 :         if (plane.control_mode == &plane.mode_manual) {
     170       14486 :             fast_tilt = true;
     171             :         }
     172      895116 :         if (plane.arming.is_armed_and_safety_off() && !quadplane.in_vtol_mode() && !quadplane.assisted_flight) {
     173      341532 :             fast_tilt = true;
     174             :         }
     175      895116 :         if (fast_tilt) {
     176             :             // allow a minimum of 90 DPS in manual or if we are not
     177             :             // stabilising, to give fast control
     178      355300 :             rate = MAX(rate, 90);
     179             :         }
     180             :     }
     181     1096220 :     return rate * plane.G_Dt * (1/90.0);
     182             : }
     183             : 
     184             : /*
     185             :   output a slew limited tiltrotor angle. tilt is from 0 to 1
     186             :  */
     187      502958 : void Tiltrotor::slew(float newtilt)
     188             : {
     189      502958 :     float max_change = tilt_max_change(newtilt<current_tilt, newtilt > get_fully_forward_tilt());
     190      502958 :     current_tilt = constrain_float(newtilt, current_tilt-max_change, current_tilt+max_change);
     191             : 
     192      502958 :     angle_achieved = is_equal(newtilt, current_tilt);
     193             : 
     194             :     // translate to 0..1000 range and output
     195      502958 :     SRV_Channels::set_output_scaled(SRV_Channel::k_motor_tilt, 1000 * current_tilt);
     196      502958 : }
     197             : 
     198             : // return the current tilt value that represents forward flight
     199             : // tilt wings can sustain forward flight with some amount of wing tilt
     200      969903 : float Tiltrotor::get_fully_forward_tilt() const
     201             : {
     202      969903 :     return 1.0 - (flap_angle_deg * (1/90.0));
     203             : }
     204             : 
     205             : // return the target tilt value for forward flight
     206      713069 : float Tiltrotor::get_forward_flight_tilt() const
     207             : {
     208      713069 :     return 1.0 - ((flap_angle_deg * (1/90.0)) * SRV_Channels::get_slew_limited_output_scaled(SRV_Channel::k_flap_auto) * 0.01);
     209             : }
     210             : 
     211             : /*
     212             :   update motor tilt for continuous tilt servos
     213             :  */
     214      502958 : void Tiltrotor::continuous_update(void)
     215             : {
     216             :     // default to inactive
     217      502958 :     _motors_active = false;
     218             : 
     219             :     // the maximum rate of throttle change
     220             :     float max_change;
     221             : 
     222      502958 :     if (!quadplane.in_vtol_mode() && (!plane.arming.is_armed_and_safety_off() || !quadplane.assisted_flight)) {
     223             :         // we are in pure fixed wing mode. Move the tiltable motors all the way forward and run them as
     224             :         // a forward motor
     225             : 
     226             :         // option set then if disarmed move to VTOL position to prevent ground strikes, allow tilt forward in manual mode for testing
     227      201600 :         const bool disarmed_tilt_up = !plane.arming.is_armed_and_safety_off() && (plane.control_mode != &plane.mode_manual) && quadplane.option_is_set(QuadPlane::Option::DISARMED_TILT_UP);
     228      201600 :         slew(disarmed_tilt_up ? 0.0 : get_forward_flight_tilt());
     229             : 
     230      201600 :         max_change = tilt_max_change(false);
     231             : 
     232      201600 :         float new_throttle = constrain_float(SRV_Channels::get_output_scaled(SRV_Channel::k_throttle)*0.01, 0, 1);
     233      201600 :         if (current_tilt < get_fully_forward_tilt()) {
     234        4671 :             current_throttle = constrain_float(new_throttle,
     235             :                                                     current_throttle-max_change,
     236             :                                                     current_throttle+max_change);
     237             :         } else {
     238      196929 :             current_throttle = new_throttle;
     239             :         }
     240      201600 :         if (!plane.arming.is_armed_and_safety_off()) {
     241       30834 :             current_throttle = 0;
     242             :         } else {
     243             :             // prevent motor shutdown
     244      170766 :             _motors_active = true;
     245             :         }
     246      201600 :         if (!quadplane.motor_test.running) {
     247             :             // the motors are all the way forward, start using them for fwd thrust
     248      201600 :             const uint32_t mask = is_zero(current_throttle) ? 0U : tilt_mask.get();
     249      201600 :             motors->output_motor_mask(current_throttle, mask, plane.rudder_dt);
     250             :         }
     251      201600 :         return;
     252             :     }
     253             : 
     254             :     // remember the throttle level we're using for VTOL flight
     255      301358 :     float motors_throttle = motors->get_throttle();
     256      301358 :     max_change = tilt_max_change(motors_throttle<current_throttle);
     257      301358 :     current_throttle = constrain_float(motors_throttle,
     258             :                                             current_throttle-max_change,
     259             :                                             current_throttle+max_change);
     260             : 
     261             :     /*
     262             :       we are in a VTOL mode. We need to work out how much tilt is
     263             :       needed. There are 5 strategies we will use:
     264             : 
     265             :       1) With use of a forward throttle controlled by Q_FWD_THR_GAIN in
     266             :          VTOL modes except Q_AUTOTUNE determined by Q_FWD_THR_USE. We set the angle based on a calculated
     267             :          forward throttle.
     268             : 
     269             :       2) With manual forward throttle control we set the angle based on the
     270             :          RC input demanded forward throttle for QACRO, QSTABILIZE and QHOVER.
     271             : 
     272             :       3) Without a RC input or calculated forward throttle value, the angle
     273             :          will be set to zero in QAUTOTUNE, QACRO, QSTABILIZE and QHOVER.
     274             :          This enables these modes to be used as a safe recovery mode.
     275             : 
     276             :       4) In fixed wing assisted flight or velocity controlled modes we will
     277             :          set the angle based on the demanded forward throttle, with a maximum
     278             :          tilt given by Q_TILT_MAX. This relies on Q_FWD_THR_GAIN or Q_VFWD_GAIN
     279             :          being set.
     280             : 
     281             :       5) if we are in TRANSITION_TIMER mode then we are transitioning
     282             :          to forward flight and should put the rotors all the way forward
     283             :     */
     284             : 
     285             : #if QAUTOTUNE_ENABLED
     286      301358 :     if (plane.control_mode == &plane.mode_qautotune) {
     287           0 :         slew(0);
     288           0 :         return;
     289             :     }
     290             : #endif
     291             : 
     292      268561 :     if (!quadplane.assisted_flight &&
     293      569919 :         quadplane.get_vfwd_method() == QuadPlane::ActiveFwdThr::NEW &&
     294           0 :         quadplane.is_flying_vtol())
     295             :     {
     296             :         // We are using the rotor tilt functionality controlled by Q_FWD_THR_GAIN which can
     297             :         // operate in all VTOL modes except Q_AUTOTUNE. Forward rotor tilt is used to produce
     298             :         // forward thrust equivalent to what would have been produced by a forward thrust motor
     299             :         // set to quadplane.forward_throttle_pct()
     300           0 :         const float fwd_g_demand = 0.01 * quadplane.forward_throttle_pct();
     301           0 :         const float fwd_tilt_deg = MIN(degrees(atanf(fwd_g_demand)), (float)max_angle_deg);
     302           0 :         slew(MIN(fwd_tilt_deg * (1/90.0), get_forward_flight_tilt()));
     303           0 :         return;
     304      301358 :     } else if (!quadplane.assisted_flight &&
     305      268561 :                (plane.control_mode == &plane.mode_qacro ||
     306      268561 :                plane.control_mode == &plane.mode_qstabilize ||
     307      268561 :                plane.control_mode == &plane.mode_qhover))
     308             :     {
     309      112322 :         if (quadplane.rc_fwd_thr_ch == nullptr) {
     310             :             // no manual throttle control, set angle to zero
     311      112322 :             slew(0);
     312             :         } else {
     313             :             // manual control of forward throttle up to max VTOL angle
     314           0 :             float settilt = 0.01f * quadplane.forward_throttle_pct();
     315           0 :             slew(MIN(settilt * max_angle_deg * (1/90.0), get_forward_flight_tilt())); 
     316             :         }
     317      112322 :         return;
     318             :     }
     319             : 
     320      189036 :     if (quadplane.assisted_flight &&
     321       32797 :         transition->transition_state >= Tiltrotor_Transition::State::TIMER) {
     322             :         // we are transitioning to fixed wing - tilt the motors all
     323             :         // the way forward
     324        4430 :         slew(get_forward_flight_tilt());
     325             :     } else {
     326             :         // until we have completed the transition we limit the tilt to
     327             :         // Q_TILT_MAX. Anything above 50% throttle gets
     328             :         // Q_TILT_MAX. Below 50% throttle we decrease linearly. This
     329             :         // relies heavily on Q_VFWD_GAIN being set appropriately.
     330      184606 :        float settilt = constrain_float((SRV_Channels::get_output_scaled(SRV_Channel::k_throttle)-MAX(plane.aparm.throttle_min.get(),0)) * 0.02, 0, 1);
     331      184606 :        slew(MIN(settilt * max_angle_deg * (1/90.0), get_forward_flight_tilt())); 
     332             :     }
     333             : }
     334             : 
     335             : 
     336             : /*
     337             :   output a slew limited tiltrotor angle. tilt is 0 or 1
     338             :  */
     339       90304 : void Tiltrotor::binary_slew(bool forward)
     340             : {
     341             :     // The servo output is binary, not slew rate limited
     342       90304 :     SRV_Channels::set_output_scaled(SRV_Channel::k_motor_tilt, forward?1000:0);
     343             : 
     344             :     // rate limiting current_tilt has the effect of delaying throttle in tiltrotor_binary_update
     345       90304 :     float max_change = tilt_max_change(!forward);
     346       90304 :     if (forward) {
     347       41733 :         current_tilt = constrain_float(current_tilt+max_change, 0, 1);
     348             :     } else {
     349       48571 :         current_tilt = constrain_float(current_tilt-max_change, 0, 1);
     350             :     }
     351       90304 : }
     352             : 
     353             : /*
     354             :   update motor tilt for binary tilt servos
     355             :  */
     356       90304 : void Tiltrotor::binary_update(void)
     357             : {
     358             :     // motors always active
     359       90304 :     _motors_active = true;
     360             : 
     361       90304 :     if (!quadplane.in_vtol_mode()) {
     362             :         // we are in pure fixed wing mode. Move the tiltable motors
     363             :         // all the way forward and run them as a forward motor
     364       41733 :         binary_slew(true);
     365             : 
     366       41733 :         float new_throttle = SRV_Channels::get_output_scaled(SRV_Channel::k_throttle)*0.01f;
     367       41733 :         if (current_tilt >= 1) {
     368       34767 :             const uint32_t mask = is_zero(new_throttle) ? 0 : tilt_mask.get();
     369             :             // the motors are all the way forward, start using them for fwd thrust
     370       34767 :             motors->output_motor_mask(new_throttle, mask, plane.rudder_dt);
     371             :         }
     372             :     } else {
     373       48571 :         binary_slew(false);
     374             :     }
     375       90304 : }
     376             : 
     377             : 
     378             : /*
     379             :   update motor tilt
     380             :  */
     381     6498054 : void Tiltrotor::update(void)
     382             : {
     383     6498054 :     if (!enabled() || tilt_mask == 0) {
     384             :         // no motors to tilt
     385     5904792 :         return;
     386             :     }
     387             : 
     388      593262 :     if (type == TILT_TYPE_BINARY) {
     389       90304 :         binary_update();
     390             :     } else {
     391      502958 :         continuous_update();
     392             :     }
     393             : 
     394      593262 :     if (type == TILT_TYPE_VECTORED_YAW) {
     395      319053 :         vectoring();
     396             :     }
     397             : }
     398             : 
     399             : #if HAL_LOGGING_ENABLED
     400             : // Write tiltrotor specific log
     401      205625 : void Tiltrotor::write_log()
     402             : {
     403             :     // Only valid on a tiltrotor
     404      205625 :     if (!enabled()) {
     405      184686 :         return;
     406             :     }
     407             : 
     408       20939 :     struct log_tiltrotor pkt {
     409             :         LOG_PACKET_HEADER_INIT(LOG_TILT_MSG),
     410       20939 :         time_us      : AP_HAL::micros64(),
     411       20939 :         current_tilt : current_tilt * 90.0,
     412       20939 :     };
     413             : 
     414       20939 :     if (type != TILT_TYPE_VECTORED_YAW) {
     415             :         // Left and right tilt are invalid
     416        9632 :         pkt.front_left_tilt = AP_Logger::quiet_nanf();
     417        9632 :         pkt.front_right_tilt = AP_Logger::quiet_nanf();
     418             : 
     419             :     } else {
     420             :         // Calculate tilt angle from servo outputs
     421       11307 :         const float total_angle = 90.0 + tilt_yaw_angle + fixed_angle;
     422       11307 :         const float scale = total_angle * 0.001;
     423       11307 :         pkt.front_left_tilt = (SRV_Channels::get_output_scaled(SRV_Channel::k_tiltMotorLeft) * scale) - tilt_yaw_angle;
     424       11307 :         pkt.front_right_tilt = (SRV_Channels::get_output_scaled(SRV_Channel::k_tiltMotorRight) * scale) - tilt_yaw_angle;
     425             :     }
     426             : 
     427       20939 :     plane.logger.WriteBlock(&pkt, sizeof(pkt));
     428             : }
     429             : #endif
     430             : 
     431             : /*
     432             :   tilt compensation for angle of tilt. When the rotors are tilted the
     433             :   roll effect of differential thrust on the tilted rotors is decreased
     434             :   and the yaw effect increased
     435             :   We have two factors we apply.
     436             : 
     437             :   1) when we are transitioning to fwd flight we scale the tilted rotors by 1/cos(angle). This pushes us towards more flight speed
     438             : 
     439             :   2) when we are transitioning to hover we scale the non-tilted rotors by cos(angle). This pushes us towards lower fwd thrust
     440             : 
     441             :   We also apply an equalisation to the tilted motors in proportion to
     442             :   how much tilt we have. This smoothly reduces the impact of the roll
     443             :   gains as we tilt further forward.
     444             : 
     445             :   For yaw, we apply differential thrust in proportion to the demanded
     446             :   yaw control and sin of the tilt angle
     447             : 
     448             :   Finally we ensure no requested thrust is over 1 by scaling back all
     449             :   motors so the largest thrust is at most 1.0
     450             :  */
     451      303465 : void Tiltrotor::tilt_compensate_angle(float *thrust, uint8_t num_motors, float non_tilted_mul, float tilted_mul)
     452             : {
     453      303465 :     float tilt_total = 0;
     454      303465 :     uint8_t tilt_count = 0;
     455             :     
     456             :     // apply tilt_factors first
     457     6131921 :     for (uint8_t i=0; i<num_motors; i++) {
     458     5828456 :         if (!is_motor_tilting(i)) {
     459     4989254 :             thrust[i] *= non_tilted_mul;
     460             :         } else {
     461      839202 :             thrust[i] *= tilted_mul;
     462      839202 :             tilt_total += thrust[i];
     463      839202 :             tilt_count++;
     464             :         }
     465             :     }
     466             : 
     467      303465 :     float largest_tilted = 0;
     468      303465 :     const float sin_tilt = sinf(radians(current_tilt*90));
     469             :     // yaw_gain relates the amount of differential thrust we get from
     470             :     // tilt, so that the scaling of the yaw control is the same at any
     471             :     // tilt angle
     472      303465 :     const float yaw_gain = sinf(radians(tilt_yaw_angle));
     473      303465 :     const float avg_tilt_thrust = tilt_total / tilt_count;
     474             : 
     475     6131921 :     for (uint8_t i=0; i<num_motors; i++) {
     476     5828456 :         if (is_motor_tilting(i)) {
     477             :             // as we tilt we need to reduce the impact of the roll
     478             :             // controller. This simple method keeps the same average,
     479             :             // but moves us to no roll control as the angle increases
     480      839202 :             thrust[i] = current_tilt * avg_tilt_thrust + thrust[i] * (1-current_tilt);
     481             :             // add in differential thrust for yaw control, scaled by tilt angle
     482      839202 :             const float diff_thrust = motors->get_roll_factor(i) * (motors->get_yaw()+motors->get_yaw_ff()) * sin_tilt * yaw_gain;
     483      839202 :             thrust[i] += diff_thrust;
     484      839202 :             largest_tilted = MAX(largest_tilted, thrust[i]);
     485             :         }
     486             :     }
     487             : 
     488             :     // if we are saturating one of the motors then reduce all motors
     489             :     // to keep them in proportion to the original thrust. This helps
     490             :     // maintain stability when tilted at a large angle
     491      303465 :     if (largest_tilted > 1.0f) {
     492        5235 :         float scale = 1.0f / largest_tilted;
     493       77695 :         for (uint8_t i=0; i<num_motors; i++) {
     494       72460 :             thrust[i] *= scale;
     495             :         }
     496             :     }
     497      303465 : }
     498             : 
     499             : /*
     500             :   choose up or down tilt compensation based on flight mode When going
     501             :   to a fixed wing mode we use tilt_compensate_down, when going to a
     502             :   VTOL mode we use tilt_compensate_up
     503             :  */
     504      593262 : void Tiltrotor::tilt_compensate(float *thrust, uint8_t num_motors)
     505             : {
     506      593262 :     if (current_tilt <= 0) {
     507             :         // the motors are not tilted, no compensation needed
     508      289797 :         return;
     509             :     }
     510      303465 :     if (quadplane.in_vtol_mode()) {
     511             :         // we are transitioning to VTOL flight
     512       27361 :         const float tilt_factor = cosf(radians(current_tilt*90));
     513       27361 :         tilt_compensate_angle(thrust, num_motors, tilt_factor, 1);
     514             :     } else {
     515             :         float inv_tilt_factor;
     516      276104 :         if (current_tilt > 0.98f) {
     517      232181 :             inv_tilt_factor = 1.0 / cosf(radians(0.98f*90));
     518             :         } else {
     519       43923 :             inv_tilt_factor = 1.0 / cosf(radians(current_tilt*90));
     520             :         }
     521      276104 :         tilt_compensate_angle(thrust, num_motors, 1, inv_tilt_factor);
     522             :     }
     523             : }
     524             : 
     525             : /*
     526             :   return true if the rotors are fully tilted forward
     527             :  */
     528     3408218 : bool Tiltrotor::fully_fwd(void) const
     529             : {
     530     3408218 :     if (!enabled() || (tilt_mask == 0)) {
     531     3147010 :         return false;
     532             :     }
     533      261208 :     return (current_tilt >= get_fully_forward_tilt());
     534             : }
     535             : 
     536             : /*
     537             :   return true if the rotors are fully tilted up
     538             :  */
     539           0 : bool Tiltrotor::fully_up(void) const
     540             : {
     541           0 :     if (!enabled() || (tilt_mask == 0)) {
     542           0 :         return false;
     543             :     }
     544           0 :     return (current_tilt <= 0);
     545             : }
     546             : 
     547             : /*
     548             :   control vectoring for tilt multicopters
     549             :  */
     550      319053 : void Tiltrotor::vectoring(void)
     551             : {
     552             :     // total angle the tilt can go through
     553      319053 :     const float total_angle = 90 + tilt_yaw_angle + fixed_angle;
     554             :     // output value (0 to 1) to get motors pointed straight up
     555      319053 :     const float zero_out = tilt_yaw_angle / total_angle;
     556      319053 :     const float fixed_tilt_limit = fixed_angle / total_angle;
     557      319053 :     const float level_out = 1.0 - fixed_tilt_limit;
     558             : 
     559             :     // calculate the basic tilt amount from current_tilt
     560      319053 :     float base_output = zero_out + (current_tilt * (level_out - zero_out));
     561             :     // for testing when disarmed, apply vectored yaw in proportion to rudder stick
     562             :     // Wait TILT_DELAY_MS after disarming to allow props to spin down first.
     563      319053 :     constexpr uint32_t TILT_DELAY_MS = 3000;
     564      319053 :     uint32_t now = AP_HAL::millis();
     565      319053 :     if (!plane.arming.is_armed_and_safety_off() && plane.quadplane.option_is_set(QuadPlane::Option::DISARMED_TILT)) {
     566             :         // this test is subject to wrapping at ~49 days, but the consequences are insignificant
     567           0 :         if ((now - hal.util->get_last_armed_change()) > TILT_DELAY_MS) {
     568           0 :             if (quadplane.in_vtol_mode()) {
     569           0 :                 float yaw_out = plane.channel_rudder->get_control_in();
     570           0 :                 yaw_out /= plane.channel_rudder->get_range();
     571           0 :                 float yaw_range = zero_out;
     572             : 
     573           0 :                 SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorLeft,  1000 * constrain_float(base_output + yaw_out * yaw_range,0,1));
     574           0 :                 SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorRight, 1000 * constrain_float(base_output - yaw_out * yaw_range,0,1));
     575           0 :                 SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorRear,  1000 * constrain_float(base_output,0,1));
     576           0 :                 SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorRearLeft,  1000 * constrain_float(base_output + yaw_out * yaw_range,0,1));
     577           0 :                 SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorRearRight, 1000 * constrain_float(base_output - yaw_out * yaw_range,0,1));
     578             :             } else {
     579             :                 // fixed wing tilt
     580           0 :                 const float gain = fixed_gain * fixed_tilt_limit;
     581             :                 // base the tilt on elevon mixing, which means it
     582             :                 // takes account of the MIXING_GAIN. The rear tilt is
     583             :                 // based on elevator
     584           0 :                 const float right = gain * SRV_Channels::get_output_scaled(SRV_Channel::k_elevon_right) * (1/4500.0);
     585           0 :                 const float left  = gain * SRV_Channels::get_output_scaled(SRV_Channel::k_elevon_left) * (1/4500.0);
     586           0 :                 const float mid  = gain * SRV_Channels::get_output_scaled(SRV_Channel::k_elevator) * (1/4500.0);
     587             :                 // front tilt is effective canards, so need to swap and use negative. Rear motors are treated live elevons.
     588           0 :                 SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorLeft,1000 * constrain_float(base_output - right,0,1));
     589           0 :                 SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorRight,1000 * constrain_float(base_output - left,0,1));
     590           0 :                 SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorRearLeft,1000 * constrain_float(base_output + left,0,1));
     591           0 :                 SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorRearRight,1000 * constrain_float(base_output + right,0,1));
     592           0 :                 SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorRear,  1000 * constrain_float(base_output + mid,0,1));
     593             :             }
     594             :         }
     595           0 :         return;
     596             :     }
     597             : 
     598      319053 :     const bool no_yaw = tilt_over_max_angle();
     599      319053 :     if (no_yaw) {
     600             :         // fixed wing  We need to apply inverse scaling with throttle, and remove the surface speed scaling as
     601             :         // we don't want tilt impacted by airspeed
     602      128430 :         const float scaler = plane.control_mode == &plane.mode_manual?1:(quadplane.FW_vector_throttle_scaling() / plane.get_speed_scaler());
     603      128430 :         const float gain = fixed_gain * fixed_tilt_limit * scaler;
     604      128430 :         const float right = gain * SRV_Channels::get_output_scaled(SRV_Channel::k_elevon_right) * (1/4500.0);
     605      128430 :         const float left  = gain * SRV_Channels::get_output_scaled(SRV_Channel::k_elevon_left) * (1/4500.0);
     606      128430 :         const float mid  = gain * SRV_Channels::get_output_scaled(SRV_Channel::k_elevator) * (1/4500.0);
     607      128430 :         SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorLeft,1000 * constrain_float(base_output - right,0,1));
     608      128430 :         SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorRight,1000 * constrain_float(base_output - left,0,1));
     609      128430 :         SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorRearLeft,1000 * constrain_float(base_output + left,0,1));
     610      128430 :         SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorRearRight,1000 * constrain_float(base_output + right,0,1));
     611      128430 :         SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorRear,  1000 * constrain_float(base_output + mid,0,1));
     612             :     } else {
     613      190623 :         const float yaw_out = motors->get_yaw()+motors->get_yaw_ff();
     614             :         // the MotorsMatrix library normalises roll factor to 0.5, so
     615             :         // we need to use the same factor here to keep the same roll
     616             :         // gains when tilted as we have when not tilted
     617      190623 :         const float roll_out = (motors->get_roll()+motors->get_roll_ff()) * 0.5;
     618      190623 :         const float yaw_range = zero_out;
     619             : 
     620             :         // Scaling yaw with throttle
     621      190623 :         const float throttle = motors->get_throttle_out();
     622      190623 :         const float scale_min = 0.5;
     623      190623 :         const float scale_max = 2.0;
     624      190623 :         float throttle_scaler = scale_max;
     625      190623 :         if (is_positive(throttle)) {
     626      139241 :             throttle_scaler = constrain_float(motors->get_throttle_hover() / throttle, scale_min, scale_max);
     627             :         }
     628             : 
     629             :         // now apply vectored thrust for yaw and roll.
     630      190623 :         const float tilt_rad = radians(current_tilt*90);
     631      190623 :         const float sin_tilt = sinf(tilt_rad);
     632      190623 :         const float cos_tilt = cosf(tilt_rad);
     633      190623 :         float tilt_scale = throttle_scaler * (yaw_out * cos_tilt - roll_out * sin_tilt);
     634             : 
     635      190623 :         if (fabsf(tilt_scale) > 1.0) {
     636           0 :             tilt_scale = constrain_float(tilt_scale, -1.0, 1.0);
     637           0 :             motors->limit.yaw = true;
     638             :         }
     639             : 
     640      190623 :         const float tilt_offset = tilt_scale * yaw_range;
     641             : 
     642      190623 :         float left_tilt = base_output + tilt_offset;
     643      190623 :         float right_tilt = base_output - tilt_offset;
     644             : 
     645             :         // if output saturation of both left and right then set yaw limit flag
     646      190623 :         if (((left_tilt > 1.0) || (left_tilt < 0.0)) &&
     647           0 :             ((right_tilt > 1.0) || (right_tilt < 0.0))) {
     648           0 :             motors->limit.yaw = true;
     649             :         }
     650             : 
     651             :         // constrain and scale to output range
     652      190623 :         left_tilt = constrain_float(left_tilt,0.0,1.0) * 1000.0;
     653      190623 :         right_tilt = constrain_float(right_tilt,0.0,1.0) * 1000.0;
     654             : 
     655      190623 :         SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorLeft, left_tilt);
     656      190623 :         SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorRight, right_tilt);
     657      190623 :         SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorRear, 1000.0 * constrain_float(base_output,0.0,1.0));
     658      190623 :         SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorRearLeft, left_tilt);
     659      190623 :         SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorRearRight, right_tilt);
     660             :     }
     661             : }
     662             : 
     663             : /*
     664             :   control bicopter tiltrotors
     665             :  */
     666     8438671 : void Tiltrotor::bicopter_output(void)
     667             : {
     668     8438671 :     if (type != TILT_TYPE_BICOPTER || quadplane.motor_test.running) {
     669             :         // don't override motor test with motors_output
     670     8438671 :         return;
     671             :     }
     672             : 
     673           0 :     if (!quadplane.in_vtol_mode() && fully_fwd()) {
     674           0 :         SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorLeft,  -SERVO_MAX);
     675           0 :         SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorRight, -SERVO_MAX);
     676           0 :         return;
     677             :     }
     678             : 
     679           0 :     float throttle = SRV_Channels::get_output_scaled(SRV_Channel::k_throttle);
     680           0 :     if (quadplane.assisted_flight) {
     681           0 :         quadplane.hold_stabilize(throttle * 0.01f);
     682           0 :         quadplane.motors_output(true);
     683             :     } else {
     684           0 :         quadplane.motors_output(false);
     685             :     }
     686             : 
     687             :     // bicopter assumes that trim is up so we scale down so match
     688           0 :     float tilt_left = SRV_Channels::get_output_scaled(SRV_Channel::k_tiltMotorLeft);
     689           0 :     float tilt_right = SRV_Channels::get_output_scaled(SRV_Channel::k_tiltMotorRight);
     690             : 
     691           0 :     if (is_negative(tilt_left)) {
     692           0 :         tilt_left *= tilt_yaw_angle * (1/90.0);
     693             :     }
     694           0 :     if (is_negative(tilt_right)) {
     695           0 :         tilt_right *= tilt_yaw_angle * (1/90.0);
     696             :     }
     697             : 
     698             :     // reduce authority of bicopter as motors are tilted forwards
     699           0 :     const float scaling = cosf(current_tilt * M_PI_2);
     700           0 :     tilt_left  *= scaling;
     701           0 :     tilt_right *= scaling;
     702             : 
     703             :     // add current tilt and constrain
     704           0 :     tilt_left  = constrain_float(-(current_tilt * SERVO_MAX) + tilt_left,  -SERVO_MAX, SERVO_MAX);
     705           0 :     tilt_right = constrain_float(-(current_tilt * SERVO_MAX) + tilt_right, -SERVO_MAX, SERVO_MAX);
     706             : 
     707           0 :     SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorLeft,  tilt_left);
     708           0 :     SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorRight, tilt_right);
     709             : }
     710             : 
     711             : /*
     712             :   when doing a forward transition of a tilt-vectored quadplane we use
     713             :   euler angle control to maintain good yaw. This updates the yaw
     714             :   target based on pilot input and target roll
     715             :  */
     716       23061 : void Tiltrotor::update_yaw_target(void)
     717             : {
     718       23061 :     uint32_t now = AP_HAL::millis();
     719       46111 :     if (now - transition_yaw_set_ms > 100 ||
     720       23050 :         !is_zero(quadplane.get_pilot_input_yaw_rate_cds())) {
     721             :         // lock initial yaw when transition is started or when
     722             :         // pilot commands a yaw change. This allows us to track
     723             :         // straight in transitions for tilt-vectored planes, but
     724             :         // allows for turns when level transition is not wanted
     725          11 :         transition_yaw_cd = quadplane.ahrs.yaw_sensor;
     726             :     }
     727             : 
     728             :     /*
     729             :       now calculate the equivalent yaw rate for a coordinated turn for
     730             :       the desired bank angle given the airspeed
     731             :      */
     732             :     float aspeed;
     733       23061 :     bool have_airspeed = quadplane.ahrs.airspeed_EAS(aspeed);
     734       23061 :     if (have_airspeed && labs(plane.nav_roll_cd)>1000) {
     735        6790 :         float dt = (now - transition_yaw_set_ms) * 0.001;
     736             :         // calculate the yaw rate to achieve the desired turn rate
     737        6790 :         const float airspeed_min = MAX(plane.aparm.airspeed_min,5);
     738        6790 :         const float yaw_rate_cds = fixedwing_turn_rate(plane.nav_roll_cd*0.01, MAX(aspeed,airspeed_min))*100;
     739        6790 :         transition_yaw_cd += yaw_rate_cds * dt;
     740             :     }
     741       23061 :     transition_yaw_set_ms = now;
     742       23061 : }
     743             : 
     744             : 
     745             : /*
     746             :   control use of multirotor rate control in forward transition
     747             :  */
     748      100595 : bool Tiltrotor_Transition::use_multirotor_control_in_fwd_transition() const
     749             : {
     750      100595 :     if (!tiltrotor.is_vectored()) {
     751       41572 :         return false;
     752             :     }
     753       59023 :     switch (transition_state) {
     754       59023 :     case State::AIRSPEED_WAIT:
     755             :     case State::TIMER:
     756       59023 :         return true;
     757           0 :     case State::DONE:
     758           0 :         return false;
     759             :     }
     760           0 :     return false;
     761             : }
     762             : 
     763       38761 : bool Tiltrotor_Transition::update_yaw_target(float& yaw_target_cd)
     764             : {
     765       38761 :     if (!use_multirotor_control_in_fwd_transition()) {
     766       15700 :         return false;
     767             :     }
     768       23061 :     tiltrotor.update_yaw_target();
     769       23061 :     yaw_target_cd = tiltrotor.transition_yaw_cd;
     770       23061 :     return true;
     771             : }
     772             : 
     773             : // return true if we should show VTOL view
     774      370525 : bool Tiltrotor_Transition::show_vtol_view() const
     775             : {
     776      370525 :     bool show_vtol = quadplane.in_vtol_mode();
     777             : 
     778      370525 :     if (!show_vtol && tiltrotor.is_vectored() && transition_state <= State::TIMER) {
     779             :         // we use multirotor controls during fwd transition for
     780             :         // vectored yaw vehicles
     781       27536 :         return true;
     782             :     }
     783             : 
     784      342989 :     return show_vtol;
     785             : }
     786             : 
     787             : // Return true if forward throttle should be allowed for position control, see Q_FWD_THR_USE
     788      914714 : bool Tiltrotor_Transition::allow_vfwd() const
     789             : {
     790             :     // Don't allow forward throttle (tilt) if a tilting motor has failed which would result in a yaw imbalance
     791             : 
     792             :     // No resultant yaw imbalance if not vectored
     793      914714 :     if (!tiltrotor.is_vectored()) {
     794      401228 :         return true;
     795             :     }
     796             : 
     797             :     // No failed motor
     798      513486 :     if (!motors->get_thrust_boost()) {
     799      485163 :         return true;
     800             :     }
     801             : 
     802             :     // Get index of failed motor
     803       28323 :     const uint8_t lost_motor = motors->get_lost_motor();
     804             : 
     805             :     // Failed motor is not tilting
     806       28323 :     if (!tiltrotor.is_motor_tilting(lost_motor)) {
     807       10824 :         return true;
     808             :     }
     809             : 
     810             :     // Failed tilting motor is on the center line, so will not cause a yaw imbalance
     811       17499 :     if (is_zero(motors->get_roll_factor(lost_motor))) {
     812           0 :         return true;
     813             :     }
     814             : 
     815             :     // Disable forward throttle (tilt)
     816       17499 :     return false;
     817             : }
     818             : 
     819             : // return true if we are tilted over the max angle threshold
     820      322433 : bool Tiltrotor::tilt_over_max_angle(void) const
     821             : {
     822      322433 :     const float tilt_threshold = (max_angle_deg/90.0f);
     823      322433 :     return (current_tilt > MIN(tilt_threshold, get_forward_flight_tilt()));
     824             : }
     825             : 
     826             : // throttle of forward flight motors including any tilting motors
     827         125 : bool Tiltrotor::get_forward_throttle(float &throttle) const
     828             : {
     829         125 :     if (!enabled() || !_is_vectored) {
     830         118 :         return false;
     831             :     }
     832           7 :     const float throttle_range = motors->thr_lin.get_spin_max() - motors->thr_lin.get_spin_min();
     833           7 :     if (!is_positive(throttle_range)) {
     834           0 :         return false;
     835             :     }
     836           7 :     float throttle_sum = 0.0f;
     837           7 :     uint8_t num_vectored_motors = 0;
     838         231 :     for (uint8_t i = 0; i < AP_MOTORS_MAX_NUM_MOTORS; ++i) {
     839         224 :         if (is_motor_tilting(i)) {
     840             :             float thrust;
     841          20 :             if (motors->get_thrust(i, thrust)) {
     842          20 :                 throttle_sum += (motors->thr_lin.thrust_to_actuator(thrust) - motors->thr_lin.get_spin_min()) / throttle_range;
     843          20 :                 num_vectored_motors ++;
     844             :             }
     845             :         }
     846             :     }
     847           7 :     if (num_vectored_motors > 0) {
     848           7 :         throttle = throttle_sum / (float)num_vectored_motors;
     849           7 :         return true;
     850             :     }
     851           0 :     return false;
     852             : }
     853             : 
     854             : #endif  // HAL_QUADPLANE_ENABLED

Generated by: LCOV version 1.14