LCOV - code coverage report
Current view: top level - AP_Motors - AP_MotorsMulticopter.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 274 297 92.3 %
Date: 2026-09-09 11:02:08 Functions: 24 25 96.0 %

          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             : #include "AP_MotorsMulticopter.h"
      17             : #include <AP_HAL/AP_HAL.h>
      18             : #include <AP_BattMonitor/AP_BattMonitor.h>
      19             : #include <SRV_Channel/SRV_Channel.h>
      20             : #include <AP_Logger/AP_Logger.h>
      21             : 
      22             : #include <AP_Vehicle/AP_Vehicle_Type.h>
      23             : #if APM_BUILD_TYPE(APM_BUILD_ArduPlane)
      24             :     #define AP_MOTORS_PARAM_PREFIX "Q_M_"
      25             : #else
      26             :     #define AP_MOTORS_PARAM_PREFIX "MOT_"
      27             : #endif
      28             : 
      29             : extern const AP_HAL::HAL& hal;
      30             : 
      31             : // parameters for the motor class
      32             : const AP_Param::GroupInfo AP_MotorsMulticopter::var_info[] = {
      33             :     // 0 was used by TB_RATIO
      34             :     // 1,2,3 were used by throttle curve
      35             :     // 5 was SPIN_ARMED
      36             : 
      37             :     // @Param: YAW_HEADROOM
      38             :     // @DisplayName: Matrix Yaw Min
      39             :     // @Description: Yaw control is given at least this pwm in microseconds range
      40             :     // @Range: 0 500
      41             :     // @Units: PWM
      42             :     // @Increment: 1
      43             :     // @User: Advanced
      44             :     AP_GROUPINFO("YAW_HEADROOM", 6, AP_MotorsMulticopter, _yaw_headroom, AP_MOTORS_YAW_HEADROOM_DEFAULT),
      45             : 
      46             :     // 7 was THR_LOW_CMP
      47             : 
      48             :     // @Param: THST_EXPO
      49             :     // @DisplayName: Thrust Curve Expo
      50             :     // @Description: Motor thrust curve exponent (0.0 for linear to 1.0 for second order curve)
      51             :     // @Range: -1.0 1.0
      52             :     // @User: Advanced
      53             :     AP_GROUPINFO("THST_EXPO", 8, AP_MotorsMulticopter, thr_lin.curve_expo, AP_MOTORS_THST_EXPO_DEFAULT),
      54             : 
      55             :     // @Param: SPIN_MAX
      56             :     // @DisplayName: Motor Spin maximum
      57             :     // @Description: Point at which the thrust saturates expressed as a number from 0 to 1 in the entire output range
      58             :     // @Values: 0.9:Low, 0.95:Default, 1.0:High
      59             :     // @Range: 0.9 1.0
      60             :     // @User: Advanced
      61             :     AP_GROUPINFO("SPIN_MAX", 9, AP_MotorsMulticopter, thr_lin.spin_max, AP_MOTORS_SPIN_MAX_DEFAULT),
      62             : 
      63             :     // @Param: BAT_VOLT_MAX
      64             :     // @DisplayName: Battery voltage compensation maximum voltage
      65             :     // @Description: Battery voltage compensation maximum voltage (voltage above this will have no additional scaling effect on thrust).  Recommend 4.2 * cell count, 0 = Disabled
      66             :     // @Range: 6 53
      67             :     // @Units: V
      68             :     // @Increment: 0.1
      69             :     // @User: Advanced
      70             :     AP_GROUPINFO("BAT_VOLT_MAX", 10, AP_MotorsMulticopter, thr_lin.batt_voltage_max, AP_MOTORS_BAT_VOLT_MAX_DEFAULT),
      71             : 
      72             :     // @Param: BAT_VOLT_MIN
      73             :     // @DisplayName: Battery voltage compensation minimum voltage
      74             :     // @Description: Battery voltage compensation minimum voltage (voltage below this will have no additional scaling effect on thrust).  Recommend 3.3 * cell count, 0 = Disabled
      75             :     // @Range: 6 42
      76             :     // @Units: V
      77             :     // @Increment: 0.1
      78             :     // @User: Advanced
      79             :     AP_GROUPINFO("BAT_VOLT_MIN", 11, AP_MotorsMulticopter, thr_lin.batt_voltage_min, AP_MOTORS_BAT_VOLT_MIN_DEFAULT),
      80             : 
      81             :     // @Param: BAT_CURR_MAX
      82             :     // @DisplayName: Motor Current Max
      83             :     // @Description: Maximum current over which maximum throttle is limited (0 = Disabled)
      84             :     // @Range: 0 200
      85             :     // @Units: A
      86             :     // @Increment: 0.1
      87             :     // @User: Advanced
      88             :     AP_GROUPINFO("BAT_CURR_MAX", 12, AP_MotorsMulticopter, _batt_current_max, AP_MOTORS_BAT_CURR_MAX_DEFAULT),
      89             : 
      90             :     // 13, 14 were used by THR_MIX_MIN, THR_MIX_MAX
      91             : 
      92             :     // @Param: PWM_TYPE
      93             :     // @DisplayName: Output PWM type
      94             :     // @Description: This selects the output PWM type, allowing for normal PWM continuous output, OneShot, brushed or DShot motor output.PWMRange and PWMAngle are PWM special/rare cases for ESCs that dont calibrate normally (some Sub motors) or where each ESC must have its PWM range set individually using the Servo params instead of PWM_MIN/MAX parameters.
      95             :     // @Values: 0:Normal,1:OneShot,2:OneShot125,3:Brushed,4:DShot150,5:DShot300,6:DShot600,7:DShot1200,8:PWMRange,9:PWMAngle
      96             :     // @User: Advanced
      97             :     // @RebootRequired: True
      98             :     AP_GROUPINFO("PWM_TYPE", 15, AP_MotorsMulticopter, _pwm_type, float(PWMType::NORMAL)),
      99             : 
     100             :     // @Param: PWM_MIN
     101             :     // @DisplayName: PWM output minimum
     102             :     // @Description: This sets the min PWM output value in microseconds that will ever be output to the motors
     103             :     // @Units: PWM
     104             :     // @Range: 0 2000
     105             :     // @Increment: 1
     106             :     // @User: Advanced
     107             :     AP_GROUPINFO("PWM_MIN", 16, AP_MotorsMulticopter, _pwm_min, 1000),
     108             : 
     109             :     // @Param: PWM_MAX
     110             :     // @DisplayName: PWM output maximum
     111             :     // @Description: This sets the max PWM value in microseconds that will ever be output to the motors
     112             :     // @Units: PWM
     113             :     // @Range: 0 2000
     114             :     // @Increment: 1
     115             :     // @User: Advanced
     116             :     AP_GROUPINFO("PWM_MAX", 17, AP_MotorsMulticopter, _pwm_max, 2000),
     117             : 
     118             :     // @Param: SPIN_MIN
     119             :     // @DisplayName: Motor Spin minimum
     120             :     // @Description: Point at which the thrust starts expressed as a number from 0 to 1 in the entire output range.  Should be higher than MOT_SPIN_ARM.
     121             :     // @Values: 0.0:Low, 0.15:Default, 0.25:High
     122             :     // @Range: 0.0 0.25
     123             :     // @Increment: 0.01
     124             :     // @User: Advanced
     125             :     AP_GROUPINFO("SPIN_MIN", 18, AP_MotorsMulticopter, thr_lin.spin_min, AP_MOTORS_SPIN_MIN_DEFAULT),
     126             : 
     127             :     // @Param: SPIN_ARM
     128             :     // @DisplayName: Motor Spin armed
     129             :     // @Description: Point at which the motors start to spin expressed as a number from 0 to 1 in the entire output range.  Should be lower than MOT_SPIN_MIN.
     130             :     // @Values: 0.0:Low, 0.1:Default, 0.2:High
     131             :     // @Range: 0.0 0.2
     132             :     // @Increment: 0.01
     133             :     // @User: Advanced
     134             :     AP_GROUPINFO("SPIN_ARM", 19, AP_MotorsMulticopter, _spin_arm, AP_MOTORS_SPIN_ARM_DEFAULT),
     135             : 
     136             :     // @Param: BAT_CURR_TC
     137             :     // @DisplayName: Motor Current Max Time Constant
     138             :     // @Description: Time constant used to limit the maximum current
     139             :     // @Range: 0 10
     140             :     // @Units: s
     141             :     // @Increment: 0.01
     142             :     // @User: Advanced
     143             :     AP_GROUPINFO("BAT_CURR_TC", 20, AP_MotorsMulticopter, _batt_current_time_constant, AP_MOTORS_BAT_CURR_TC_DEFAULT),
     144             : 
     145             :     // @Param: THST_HOVER
     146             :     // @DisplayName: Thrust Hover Value
     147             :     // @Description: Motor thrust needed to hover expressed as a number from 0 to 1
     148             :     // @Range: 0.125 0.6875
     149             :     // @User: Advanced
     150             :     AP_GROUPINFO("THST_HOVER", 21, AP_MotorsMulticopter, _throttle_hover, AP_MOTORS_THST_HOVER_DEFAULT),
     151             : 
     152             :     // @Param: HOVER_LEARN
     153             :     // @DisplayName: Hover Value Learning
     154             :     // @Description: Enable/Disable automatic learning of hover throttle
     155             :     // @Values{Copter}: 0:Disabled, 1:Learn, 2:Learn and Save
     156             :     // @Values{Sub}: 0:Disabled
     157             :     // @Values{Plane}: 0:Disabled, 1:Learn, 2:Learn and Save
     158             :     // @User: Advanced
     159             :     AP_GROUPINFO("HOVER_LEARN", 22, AP_MotorsMulticopter, _throttle_hover_learn, HOVER_LEARN_AND_SAVE),
     160             : 
     161             :     // @Param: SAFE_DISARM
     162             :     // @DisplayName: Motor PWM output disabled when disarmed
     163             :     // @Description: Disables motor PWM output when disarmed
     164             :     // @Values: 0:PWM enabled while disarmed, 1:PWM disabled while disarmed
     165             :     // @User: Advanced
     166             :     AP_GROUPINFO("SAFE_DISARM", 23, AP_MotorsMulticopter, _disarm_disable_pwm, 0),
     167             : 
     168             :     // @Param{Copter,Plane}: YAW_SV_ANGLE
     169             :     // @DisplayName: Yaw Servo Max Lean Angle
     170             :     // @Description: Yaw servo's maximum lean angle (Tricopter only)
     171             :     // @Range: 5 80
     172             :     // @Units: deg
     173             :     // @Increment: 1
     174             :     // @User: Standard
     175             :     AP_GROUPINFO_FRAME("YAW_SV_ANGLE", 35, AP_MotorsMulticopter, _yaw_servo_angle_max_deg, 30, AP_PARAM_FRAME_TRICOPTER),
     176             : 
     177             :     // @Param: SPOOL_TIME
     178             :     // @DisplayName: Spool up time
     179             :     // @Description: Time in seconds to spool up the motors from zero to min throttle. 
     180             :     // @Range: 0.05 2
     181             :     // @Units: s
     182             :     // @Increment: 0.1
     183             :     // @User: Advanced
     184             :     AP_GROUPINFO("SPOOL_TIME", 36, AP_MotorsMulticopter, _spool_up_time, AP_MOTORS_SPOOL_UP_TIME_DEFAULT),
     185             : 
     186             :     // @Param: BOOST_SCALE
     187             :     // @DisplayName: Motor boost scale
     188             :     // @Description: Booster motor output scaling factor vs main throttle.  The output to the BoostThrottle servo will be the main throttle times this scaling factor. A higher scaling factor will put more of the load on the booster motor. A value of 1 will set the BoostThrottle equal to the main throttle.
     189             :     // @Range: 0 5
     190             :     // @Increment: 0.1
     191             :     // @User: Advanced
     192             :     AP_GROUPINFO("BOOST_SCALE", 37, AP_MotorsMulticopter, _boost_scale, 0),
     193             : 
     194             :     // 38 RESERVED for BAT_POW_MAX
     195             :     
     196             :     // @Param: BAT_IDX
     197             :     // @DisplayName: Battery compensation index
     198             :     // @Description: Which battery monitor should be used for doing compensation
     199             :     // @Values: 0:First battery, 1:Second battery
     200             :     // @Range: 0 15
     201             :     // @Increment: 1
     202             :     // @User: Advanced
     203             :     AP_GROUPINFO("BAT_IDX", 39, AP_MotorsMulticopter, thr_lin.batt_idx, 0),
     204             : 
     205             :     // @Param: SLEW_UP_TIME
     206             :     // @DisplayName: Output slew time for increasing throttle
     207             :     // @Description: Time in seconds to slew output from zero to full. This is used to limit the rate at which output can change. Range is constrained between 0 and 0.5.
     208             :     // @Range: 0 0.5
     209             :     // @Units: s
     210             :     // @Increment: 0.001
     211             :     // @User: Advanced
     212             :     AP_GROUPINFO("SLEW_UP_TIME", 40, AP_MotorsMulticopter, _slew_up_time, AP_MOTORS_SLEW_TIME_DEFAULT),
     213             : 
     214             :     // @Param: SLEW_DN_TIME
     215             :     // @DisplayName: Output slew time for decreasing throttle
     216             :     // @Description: Time in seconds to slew output from full to zero. This is used to limit the rate at which output can change.  Range is constrained between 0 and 0.5.
     217             :     // @Range: 0 0.5
     218             :     // @Units: s
     219             :     // @Increment: 0.001
     220             :     // @User: Advanced
     221             :     AP_GROUPINFO("SLEW_DN_TIME", 41, AP_MotorsMulticopter, _slew_dn_time, AP_MOTORS_SLEW_TIME_DEFAULT),
     222             : 
     223             :     // @Param: SAFE_TIME
     224             :     // @DisplayName: PWM re-enable delay after arming when disarmed pwm was disabled
     225             :     // @Description: Delay after arming before spool transitions are allowed, used to let ESCs/servos complete startup when PWM was disabled while disarmed.
     226             :     // @Range: 0 5
     227             :     // @Units: s
     228             :     // @Increment: 0.001
     229             :     // @User: Advanced
     230             :     AP_GROUPINFO("SAFE_TIME", 42, AP_MotorsMulticopter, _safe_time, AP_MOTORS_SAFE_TIME_DEFAULT),
     231             : 
     232             :     // @Param: OPTIONS
     233             :     // @DisplayName: Motor options
     234             :     // @Description: Motor options
     235             :     // @Bitmask: 0:Voltage compensation uses raw voltage
     236             :     // @User: Advanced
     237             :     AP_GROUPINFO("OPTIONS", 43, AP_MotorsMulticopter, _options, 0),
     238             : 
     239             :     // @Param: SPOOL_TIM_DN
     240             :     // @DisplayName: Spool down time
     241             :     // @Description: Time to ramp the throttle ceiling down toward zero. If 0, SPOOL_TIME is used.
     242             :     // @Range: 0 2
     243             :     // @Units: s
     244             :     // @Increment: 0.001
     245             :     // @User: Advanced
     246             :     AP_GROUPINFO("SPOOL_TIM_DN", 44, AP_MotorsMulticopter, _spool_down_time, 0),
     247             : 
     248             :     // @Param: IDLE_SEC
     249             :     // @DisplayName: Idle time
     250             :     // @Description: Delay after reaching ground idle when armed to allow ESC startup to complete.
     251             :     // @Range: 0 5
     252             :     // @Units: s
     253             :     // @Increment: 0.1
     254             :     // @User: Advanced
     255             :     AP_GROUPINFO("IDLE_SEC", 45, AP_MotorsMulticopter, _idle_time_delay_s, 0),
     256             : 
     257             :     AP_GROUPEND
     258             : };
     259             : 
     260             : // Constructor
     261        1202 : AP_MotorsMulticopter::AP_MotorsMulticopter(uint16_t speed_hz) :
     262             :                 AP_Motors(speed_hz),
     263        1202 :                 _throttle_limit(1.0f)
     264             : {
     265        1202 :     AP_Param::setup_object_defaults(this, var_info);
     266        1202 : };
     267             : 
     268             : // output - sends commands to the motors
     269    36102663 : void AP_MotorsMulticopter::output()
     270             : {
     271             :     // update throttle filter
     272    36102663 :     update_throttle_filter();
     273             : 
     274             :     // calc filtered battery voltage and lift_max
     275    36102663 :     thr_lin.update_lift_max_from_batt_voltage();
     276             : 
     277             :     // run spool logic
     278    36102663 :     output_logic();
     279             : 
     280             :     // calculate thrust
     281    36102663 :     output_armed_stabilizing();
     282             : 
     283             :     // apply any thrust compensation for the frame
     284    36102663 :     thrust_compensation();
     285             : 
     286             :     // convert rpy_thrust values to pwm
     287    36102663 :     output_to_motors();
     288             : 
     289             :     // output any booster throttle
     290    36102663 :     output_boost_throttle();
     291             : 
     292             :     // output raw roll/pitch/yaw/thrust
     293    36102663 :     output_rpyt();
     294             : 
     295             :     // check for any external limit flags
     296    36102663 :     update_external_limits();
     297             : 
     298             :     // clear mask of overridden motors
     299    36102663 :     _motor_mask_override = 0;
     300    36102663 : };
     301             : 
     302             : // update external limits from scripting
     303    36102663 : void AP_MotorsMulticopter::update_external_limits()
     304             : {
     305             : #if AP_SCRIPTING_ENABLED
     306             :     // merge external (scripting) limit flags into the internal limit state
     307    36102663 :     limit.roll |= external_limits.roll;
     308    36102663 :     limit.pitch |= external_limits.pitch;
     309    36102663 :     limit.yaw |= external_limits.yaw;
     310    36102663 :     limit.throttle_lower |= external_limits.throttle_lower;
     311    36102663 :     limit.throttle_upper |= external_limits.throttle_upper;
     312             : #endif
     313    36102663 : }
     314             : 
     315             : // output booster throttle, if any
     316    36102663 : void AP_MotorsMulticopter::output_boost_throttle(void)
     317             : {
     318    36102663 :     if (_boost_scale > 0) {
     319           0 :         float throttle = constrain_float(get_throttle() * _boost_scale, 0, 1);
     320           0 :         SRV_Channels::set_output_scaled(SRV_Channel::k_boost_throttle, throttle * 1000);
     321             :     } else {
     322    36102663 :         SRV_Channels::set_output_scaled(SRV_Channel::k_boost_throttle, 0);
     323             :     }
     324    36102663 : }
     325             : 
     326             : // output roll/pitch/yaw/thrust
     327    36102663 : void AP_MotorsMulticopter::output_rpyt(void)
     328             : {
     329    36102663 :     SRV_Channels::set_output_scaled(SRV_Channel::k_roll_out, _roll_in_ff * 4500);
     330    36102663 :     SRV_Channels::set_output_scaled(SRV_Channel::k_pitch_out, _pitch_in_ff * 4500);
     331    36102663 :     SRV_Channels::set_output_scaled(SRV_Channel::k_yaw_out, _yaw_in_ff * 4500);
     332    36102663 :     SRV_Channels::set_output_scaled(SRV_Channel::k_thrust_out, get_throttle() * 1000);
     333    36102663 : }
     334             : 
     335             : // sends minimum values out to the motors
     336       43981 : void AP_MotorsMulticopter::output_min()
     337             : {
     338       43981 :     set_desired_spool_state(DesiredSpoolState::SHUT_DOWN);
     339       43981 :     _spool_state = SpoolState::SHUT_DOWN;
     340       43981 :     output();
     341       43981 : }
     342             : 
     343             : // update the throttle input filter
     344    36102663 : void AP_MotorsMulticopter::update_throttle_filter()
     345             : {
     346    36102663 :     const float last_thr = _throttle_filter.get();
     347             : 
     348    36102663 :     if (armed()) {
     349    23838589 :         _throttle_filter.apply(_throttle_in, _dt_s);
     350             :         // constrain filtered throttle
     351    23838589 :         if (_throttle_filter.get() < 0.0f) {
     352      621537 :             _throttle_filter.reset(0.0f);
     353             :         }
     354    23838589 :         if (_throttle_filter.get() > 1.0f) {
     355       94339 :             _throttle_filter.reset(1.0f);
     356             :         }
     357             :     } else {
     358             :         // when disarmed, reset filter to 0 so throttle slew resumes from zero on arm
     359    12264074 :         _throttle_filter.reset(0.0f);
     360             :     }
     361             : 
     362    36102663 :     float new_thr = _throttle_filter.get();
     363             : 
     364    36102663 :     if (!is_equal(last_thr, new_thr)) {
     365    19213987 :         _throttle_slew.update(new_thr, AP_HAL::micros());
     366             :     }
     367             : 
     368             :     // calculate slope normalized from per-micro
     369    36102663 :     const float rate = fabsf(_throttle_slew.slope() * 1e6);
     370    36102663 :     _throttle_slew_rate = _throttle_slew_filter.apply(rate, _dt_s);
     371    36102663 : }
     372             : 
     373             : // return current-limit as a normalised throttle [0..1] within the min..max range
     374             : // returns 1.0 when current limiting is disabled, disarmed, or telemetry is unavailable
     375    19631709 : float AP_MotorsMulticopter::get_current_limit_max_throttle()
     376             : {
     377             : #if AP_BATTERY_ENABLED
     378    19631709 :     AP_BattMonitor &battery = AP::battery();
     379             : 
     380    19631709 :     const uint8_t batt_idx = thr_lin.get_battery_index();
     381             :     float _batt_current;
     382             : 
     383             :     // early exits:
     384             :     // - current limiting disabled (param <= 0)
     385             :     // - disarmed (do not apply a throttle limit while disarmed)
     386             :     // - no current telemetry available
     387    19631709 :     if (_batt_current_max <= 0 || // return maximum if current limiting is disabled
     388    19726566 :         !armed() || // remove throttle limit if disarmed
     389       94857 :         !battery.current_amps(_batt_current, batt_idx)) { // no current monitoring is available
     390    19536852 :         _throttle_limit = 1.0f;
     391    19536852 :         return 1.0f;
     392             :     }
     393             : 
     394             :     // pack resistance for selected battery index
     395       94857 :     float _batt_resistance = battery.get_resistance(batt_idx);
     396             : 
     397             :     // if resistance is unknown/zero, do not attempt limiting
     398       94857 :     if (is_zero(_batt_resistance)) {
     399        1912 :         _throttle_limit = 1.0f;
     400        1912 :         return 1.0f;
     401             :     }
     402             : 
     403             :     // compute the maximum permissible current
     404             :     // the limit is the minimum of:
     405             :     //  - user/parameter maximum (_batt_current_max)
     406             :     //  - ohmic limit that avoids sagging below the configured minimum voltage:
     407             :     //    I_max_ohmic = I_measured + (V_batt - V_min) / R_internal
     408             :     // calculate the maximum current to prevent voltage sag below _batt_voltage_min
     409       92945 :     float batt_current_max = MIN(_batt_current_max, _batt_current + (battery.voltage(batt_idx) - thr_lin.get_battery_min_voltage()) / _batt_resistance);
     410             : 
     411             :     // ratio of measured current to the permissible current (1.0 means at the limit)
     412       92945 :     float batt_current_ratio = _batt_current / batt_current_max;
     413             : 
     414             :     // integrate throttle headroom based on current margin
     415             :     // batt_current_ratio = I_measured / I_max
     416             :     // - if ratio < 1 then (1 - ratio) > 0 and allowed headroom increases
     417             :     // - if ratio > 1 then (1 - ratio) < 0 and allowed headroom decreases
     418             :     // _batt_current_time_constant scales the step size; this is an integrator, not an RC toward a target
     419       92945 :     _throttle_limit += (_dt_s / (_dt_s + _batt_current_time_constant)) * (1.0f - batt_current_ratio);
     420             : 
     421             :     // constrain the headroom scaler to [0.2, 1.0]
     422             :     // ensures some minimum authority above hover remains available
     423       92945 :     _throttle_limit = constrain_float(_throttle_limit, 0.2f, 1.0f);
     424             : 
     425             :     // map the limit into the pilot/controller throttle range
     426             :     // at hover (get_throttle_hover) the result is 1.0 (no reduction)
     427             :     // toward full throttle the available range is reduced by _throttle_limit
     428             :     // returns a normalised maximum throttle in [0..1]
     429       92945 :     return get_throttle_hover() + ((1.0 - get_throttle_hover()) * _throttle_limit);
     430             : #else
     431             :     // no battery backend, no limiting
     432             :     return 1.0;
     433             : #endif
     434             : }
     435             : 
     436             : #if HAL_LOGGING_ENABLED
     437             : // 10hz logging of voltage scaling and max trust
     438      757935 : void AP_MotorsMulticopter::Log_Write()
     439             : {
     440      757935 :     const struct log_MotBatt pkt_mot {
     441             :         LOG_PACKET_HEADER_INIT(LOG_MOTBATT_MSG),
     442      757935 :         time_us         : AP_HAL::micros64(),
     443      757935 :         lift_max        : thr_lin.get_lift_max(),
     444     1515870 :         bat_volt        : thr_lin.batt_voltage_filt.get(),
     445      757935 :         th_limit        : _throttle_limit,
     446      757935 :         th_average_max  : _throttle_avg_max,
     447      757935 :         th_out          : _throttle_out,
     448      757935 :         mot_fail_flags  : (uint8_t)(_thrust_boost | (_thrust_balanced << 1U)),
     449      757935 :     };
     450      757935 :     AP::logger().WriteBlock(&pkt_mot, sizeof(pkt_mot));
     451      757935 : }
     452             : #endif
     453             : 
     454             : // convert normalised actuator output (0..1) to pwm
     455             : // in SHUT_DOWN: output 0 pwm when _disarm_disable_pwm && !armed(), else pwm_min
     456             : // in all other states: map [0..1] linearly between pwm_min and pwm_max
     457   141756127 : int16_t AP_MotorsMulticopter::output_to_pwm(float actuator)
     458             : {
     459             :     float pwm_output;
     460   141756127 :     if (_spool_state == SpoolState::SHUT_DOWN) {
     461             :         // in shutdown mode, use PWM 0 or minimum PWM
     462    58265736 :         if (_disarm_disable_pwm && !armed()) {
     463           0 :             pwm_output = 0;
     464             :         } else {
     465    58265736 :             pwm_output = get_pwm_output_min();
     466             :         }
     467             :     } else {
     468             :         // in all other spool modes, covert to desired PWM
     469    83490391 :         pwm_output = get_pwm_output_min() + (get_pwm_output_max() - get_pwm_output_min()) * actuator;
     470             :     }
     471             : 
     472   141756127 :     return pwm_output;
     473             : }
     474             : 
     475             : // slew limiting on motor outputs:
     476             : // - MOT_SLEW_UP_TIME = time to go 0 -> 1 (s); 0 disables up-slew limiting
     477             : // - MOT_SLEW_DN_TIME = time to go 1 -> 0 (s); 0 disables down-slew limiting
     478             : // - both times are constrained to 0..0.5 s
     479             : // - no slew limiting while in SHUT_DOWN to allow immediate motor de-energisation
     480    84198163 : void AP_MotorsMulticopter::set_actuator_with_slew(float& actuator_output, float input)
     481             : {
     482             :     /*
     483             :     If MOT_SLEW_UP_TIME is 0 (default), no slew limit is applied to increasing output.
     484             :     If MOT_SLEW_DN_TIME is 0 (default), no slew limit is applied to decreasing output.
     485             :     MOT_SLEW_UP_TIME and MOT_SLEW_DN_TIME are constrained to 0.0~0.5 for sanity.
     486             :     If spool mode is shutdown, no slew limit is applied to allow immediate disarming of motors.
     487             :     */
     488             : 
     489             :     // Output limits with no slew time applied
     490    84198163 :     float output_slew_limit_up = 1.0f;
     491    84198163 :     float output_slew_limit_dn = 0.0f;
     492             : 
     493             :     // If MOT_SLEW_UP_TIME is set, calculate the highest allowed new output value, constrained 0.0~1.0
     494    84198163 :     if (is_positive(_slew_up_time)) {
     495           0 :         float output_delta_up_max = _dt_s / (constrain_float(_slew_up_time, 0.0f, 0.5f));
     496           0 :         output_slew_limit_up = constrain_float(actuator_output + output_delta_up_max, 0.0f, 1.0f);
     497             :     }
     498             : 
     499             :     // If MOT_SLEW_DN_TIME is set, calculate the lowest allowed new output value, constrained 0.0~1.0
     500    84198163 :     if (is_positive(_slew_dn_time)) {
     501           0 :         float output_delta_dn_max = _dt_s / (constrain_float(_slew_dn_time, 0.0f, 0.5f));
     502           0 :         output_slew_limit_dn = constrain_float(actuator_output - output_delta_dn_max, 0.0f, 1.0f);
     503             :     }
     504             : 
     505             :     // Constrain change in output to within the above limits
     506    84198163 :     actuator_output = constrain_float(input, output_slew_limit_dn, output_slew_limit_up);
     507    84198163 : }
     508             : 
     509             : // gradually increase actuator output to spin_min
     510             : // equals _spin_up_ratio * spin_min (constrained to [0..1])
     511     1043006 : float AP_MotorsMulticopter::actuator_spin_up_to_ground_idle() const
     512             : {
     513     1043006 :     return constrain_float(_spin_up_ratio, 0.0f, 1.0f) * thr_lin.get_spin_min();
     514             : }
     515             : 
     516             : // return throttle out for motor motor_num, returns true if value is valid false otherwise
     517     1385396 : bool AP_MotorsMulticopter::get_thrust(uint8_t motor_num, float& thr_out) const
     518             : {
     519     1385396 :     if (motor_num >= AP_MOTORS_MAX_NUM_MOTORS || !motor_enabled[motor_num]) {
     520     1210304 :         return false;
     521             :     }
     522             : 
     523             :     // Constrain to linearization range.
     524      175092 :     const float actuator = constrain_float(_actuator[motor_num], thr_lin.get_spin_min(), thr_lin.get_spin_max());
     525             : 
     526             :     // Remove linearization and compensation gain
     527      175092 :     thr_out = thr_lin.actuator_to_thrust(actuator) / thr_lin.get_compensation_gain();
     528      175092 :     return true;
     529             : }
     530             : 
     531             : // parameter checks for MOT_PWM_MIN/MAX, returns true if parameters are valid
     532      100455 : bool AP_MotorsMulticopter::check_mot_pwm_params() const
     533             : {
     534             :     // _pwm_min is a value greater than or equal to 1.
     535             :     // _pwm_max is greater than _pwm_min.
     536             :     // The values of _pwm_min and _pwm_max are positive values.
     537      100455 :     if (_pwm_min < 1 || _pwm_min >= _pwm_max) {
     538           2 :         return false;
     539             :     }
     540      100453 :     return true;
     541             : }
     542             : 
     543             : // update_throttle_range - update throttle endpoints
     544       27173 : void AP_MotorsMulticopter::update_throttle_range()
     545             : {
     546             :     // if all outputs are digital adjust the range. We also do this for type PWM_RANGE, as those use the
     547             :     // scaled output, which is then mapped to PWM via the SRV_Channel library
     548       27173 :     if (SRV_Channels::have_digital_outputs(get_motor_mask()) || (_pwm_type == PWMType::PWM_RANGE) || (_pwm_type == PWMType::PWM_ANGLE)) {
     549         128 :         _pwm_min.set_and_default(1000);
     550         128 :         _pwm_max.set_and_default(2000);
     551             :     }
     552             : 
     553       27173 :     hal.rcout->set_esc_scaling(get_pwm_output_min(), get_pwm_output_max());
     554       27173 : }
     555             : 
     556             : // update the throttle input filter.  should be called at 100hz
     557     1305938 : void AP_MotorsMulticopter::update_throttle_hover(float dt)
     558             : {
     559     1305938 :     if (_throttle_hover_learn != HOVER_LEARN_DISABLED) {
     560             :         // we have chosen to constrain the hover throttle to be within the range reachable by the third order expo polynomial.
     561     1300085 :         _throttle_hover.set(constrain_float(_throttle_hover + (dt / (dt + AP_MOTORS_THST_HOVER_TC)) * (get_throttle() - _throttle_hover), AP_MOTORS_THST_HOVER_MIN, AP_MOTORS_THST_HOVER_MAX));
     562             :     }
     563     1305938 : }
     564             : 
     565             : // set_desired_spool_state - set desired spool state with safety constraints
     566    29334859 : void AP_MotorsMulticopter::set_desired_spool_state(DesiredSpoolState desired)
     567             : {
     568             :     // Safety constraint: disarmed or no interlock means motors must shut down
     569             :     // (multicopters don't use GROUND_IDLE like helis - props must not spin without interlock)
     570    29334859 :     if (!armed() || !get_interlock()) {
     571    11082542 :         _spool_desired = DesiredSpoolState::SHUT_DOWN;
     572    11082542 :         return;
     573             :     }
     574             : 
     575             :     // No safety constraints active - accept requested state
     576    18252317 :     _spool_desired = desired;
     577             : }
     578             : 
     579             : // run spool logic
     580             : // advance the motor spool state machine once per cycle
     581             : // enforce arming/interlock and disarm-pwm safe-time guards
     582             : // shape two normalised scalars with monotonic ramps during normal operation
     583             : //  - _spin_up_ratio        controls motor spin/idle shaping
     584             : //  - _throttle_thrust_max  defines the moving throttle ceiling, limited by current draw
     585             : // both values are normalised between 0 and 1
     586             : // zero thrust while !armed() or !get_interlock()
     587             : // ramps are limited by the configured spool times (see minimum_spool_time)
     588             : // disarm or interlock drop immediately forces SHUT_DOWN for safety
     589             : // pre-takeoff checks and idle-time delay may hold in GROUND_IDLE until cleared
     590             : // (get_spoolup_block(), IDLE_TIME after reaching ground-idle spin)
     591    36102663 : void AP_MotorsMulticopter::output_logic()
     592             : {
     593             :     // Minimum allowable spool time [s].
     594             :     // Prevents divide-by-zero or excessively abrupt transitions when calculating
     595             :     // spool_step = dt / time. A value of 0.05 s (~20 Hz) ensures numerically
     596             :     // stable ramps while remaining perceptually instantaneous to the user.
     597    36102663 :     const constexpr float minimum_spool_time = 0.05f;
     598             : 
     599             :     // disarm-pwm safety window
     600             :     // when _disarm_disable_pwm is enabled, pwm outputs are off while disarmed
     601             :     // after arming, _safe_time provides a delay to let escs or servos detect
     602             :     // the restored pwm signal and complete their startup sequence
     603             :     // the timer only advances while armed and resets to 0 when disarmed
     604    36102663 :     if (armed()) {
     605    23838589 :         if (_disarm_disable_pwm && (_disarm_safe_timer < _safe_time)) {
     606           0 :             _disarm_safe_timer += _dt_s;
     607             :         } else {
     608    23838589 :             _disarm_safe_timer = _safe_time;
     609             :         }
     610             :     } else {
     611    12264074 :            _disarm_safe_timer = 0.0f;
     612             :     }
     613             : 
     614             :     // safety rule (always applies)
     615             :     // if not armed or interlock is false, force both desired and actual
     616             :     // spool states to SHUT_DOWN immediately with no ramping
     617             :     // ensures zero thrust and prevents any motor output until both armed() and get_interlock() are true
     618    36102663 :     if (!armed() || !get_interlock()) {
     619    12659277 :         _spool_desired = DesiredSpoolState::SHUT_DOWN;
     620    12659277 :         _spool_state = SpoolState::SHUT_DOWN;
     621             :     }
     622             : 
     623    36102663 :     if (_spool_up_time < minimum_spool_time) {
     624             :         // Enforce a lower bound on spool-up time for numerical stability and
     625             :         // predictability.
     626           0 :         _spool_up_time.set(minimum_spool_time);
     627             :     }
     628             : 
     629             :     // limits flags convention:
     630             :     //  - SHUT_DOWN, GROUND_IDLE: limit.set_all(true)   // no attitude/throttle authority
     631             :     //  - SPOOLING_UP/DOWN, THROTTLE_UNLIMITED: set_all(false) // controller runs normally
     632    36102663 :     switch (_spool_state) {
     633    14980496 :     case SpoolState::SHUT_DOWN:
     634             :         // motors should be stationary
     635             :         // servos set to their trim values or in a test condition
     636             : 
     637             :         // zero energy/thrust, pwm disabled
     638             :         // leave only after safe-time has elapsed and a higher-energy state is requested
     639             :         // next state is GROUND_IDLE
     640             : 
     641             :         // set limits flags
     642             :         // keep all limits set while motors are stopped and thrust is disabled
     643    14980496 :         limit.set_all(true);
     644             : 
     645             :         // set ramp variables
     646             :         // zero both ramps in shut_down
     647    14980496 :         _spin_up_ratio = 0.0f;
     648    14980496 :         _throttle_thrust_max = 0.0f;
     649    14980496 :         _idle_time = 0.0;
     650             : 
     651             :         // initialise motor failure variables
     652             :         // disable thrust boost in shut_down
     653    14980496 :         _thrust_boost = false;
     654    14980496 :         _thrust_boost_ratio = 0.0f;
     655             : 
     656             :         // ensure motors only begin spooling after arming safety delay has expired
     657             :         // leave SHUT_DOWN only once the safe-time window has elapsed and
     658             :         // a higher-energy state is requested. this prevents any PWM output
     659             :         // until ESCs or servos have completed their start-up sequence.
     660    14980496 :         if (_spool_desired != DesiredSpoolState::SHUT_DOWN && _disarm_safe_timer >= _safe_time.get()) {
     661         772 :             _spool_state = SpoolState::GROUND_IDLE;
     662             :         }
     663    14980496 :         break;
     664             : 
     665      263368 :     case SpoolState::GROUND_IDLE: {
     666             :         // motors should be stationary or at ground idle
     667             :         // servos should be moving to correct the current attitude
     668             : 
     669             :         // pwm may be enabled at or near spin_min if required
     670             :         // throttle does not yet follow demand
     671             :         // this staging state ensures all spool-up checks pass before allowing thrust authority
     672             : 
     673             :         // set limits flags
     674             :         // keep all limits set while in a low-energy or gated state
     675      263368 :         limit.set_all(true);
     676             :         
     677      263368 :         float spin_up_ground_idle_ratio = 0.0f;
     678      263368 :         if ( is_positive(thr_lin.get_spin_min()) ) {
     679      263368 :             spin_up_ground_idle_ratio = _spin_arm / thr_lin.get_spin_min();
     680             :         }
     681             : 
     682      263368 :         if (_spin_up_ratio >= spin_up_ground_idle_ratio) {
     683             :             // Advance the idle-time delay only once the motors have reached ground-idle spin.
     684      175421 :             _idle_time = MIN(_idle_time_delay_s, _idle_time + _dt_s);
     685             :         }
     686             : 
     687             :         // set and increment ramp variables
     688      263368 :         switch (_spool_desired) {
     689        9171 :         case DesiredSpoolState::SHUT_DOWN: {
     690             :             // Down path: reduce spin toward 0 using SPOOL_TIM_DN if configured,
     691             :             // otherwise fall back to SPOOL_TIME for symmetry.
     692        9171 :             const float spool_time = _spool_down_time > minimum_spool_time ? _spool_down_time : _spool_up_time;
     693        9171 :             const float spool_step = _dt_s / spool_time;
     694        9171 :             _spin_up_ratio -= spool_step;
     695             : 
     696             :             // constrain ramp value and update mode
     697             :             // Drop back to SHUT_DOWN only after spin ratio hits zero.
     698        9171 :             if (_spin_up_ratio <= 0.0f) {
     699         121 :                 _spin_up_ratio = 0.0f;
     700         121 :                 _spool_state = SpoolState::SHUT_DOWN;
     701             :             }
     702        9171 :             break;
     703             :         }
     704             : 
     705      116612 :         case DesiredSpoolState::THROTTLE_UNLIMITED: {
     706             :             // Up path: ramp spin toward 1.0.
     707             :             // While the idle-time delay is running, hold spin at ground idle to allow ESC startup.
     708             :             // After the delay, continue ramping to 1.0 and proceed when spool-up checks are clear.
     709             : 
     710      116612 :             const float spool_step = _dt_s / _spool_up_time;
     711      116612 :             _spin_up_ratio += spool_step;
     712             : 
     713             :             // Hold at ground-idle spin until the configured idle-time delay has elapsed.
     714             :             // This allows ESCs to complete their startup sequence with PWM active at idle
     715             :             // before allowing further spool-up.
     716      116612 :             if (_idle_time < _idle_time_delay_s) {
     717           0 :                 _spin_up_ratio = MIN(_spin_up_ratio, spin_up_ground_idle_ratio);
     718           0 :                 break;
     719             :             }
     720             : 
     721             :             // wait for spin up to complete
     722      116612 :             if (_spin_up_ratio < 1.0f) {
     723      112677 :                 _spin_up_complete = false;
     724             :             } else {
     725        3935 :                 _spin_up_ratio = 1.0f;
     726        3935 :                 if (!_spin_up_complete) {
     727             :                     // Spin up is complete.
     728             :                     // Enable spoolup block to hold the aircraft in GROUND_IDLE.
     729             :                     // Main code should start checks when the block is enabled and remove the lock when ready.
     730         737 :                     _spin_up_complete = true;
     731         737 :                     set_spoolup_block(true);
     732             :                 }
     733             :             }
     734      116612 :             if (_spin_up_complete && !get_spoolup_block()) {
     735             :                 // only advance from ground idle when pre-takeoff checks have cleared
     736             :                 // get_spoolup_block() is true while takeoff_check() is blocking spool-up
     737             :                 // (e.g. esc telemetry inactive, rpm not in range, cpu overload, or disarmed)
     738             :                 // must be false to proceed
     739         734 :                 _spool_state = SpoolState::SPOOLING_UP;
     740             :             }
     741      116612 :             break;
     742             :         }
     743      137585 :         case DesiredSpoolState::GROUND_IDLE: {
     744             :             // slew limits for spin-up ratio while in ground idle
     745             :             // up uses _spool_up_time; down uses _spool_down_time (or up time if not set)
     746      137585 :             const float spool_up_step = _dt_s / _spool_up_time;
     747      137585 :             const float spool_down_time = _spool_down_time > minimum_spool_time ? _spool_down_time : _spool_up_time;
     748      137585 :             const float spool_down_step = _dt_s / spool_down_time;
     749             : 
     750             :             // target spin ratio while armed:
     751             :             // raise spin toward the armed idle (_spin_arm) as a fraction of spin_min
     752             :             // (keeps ratio normalized; if no spin_min, stay at 0)
     753             : 
     754             :             // asymmetrical slew toward target: limit decrease with spool_down_step and increase with spool_up_step
     755             :             // prevents step changes while allowing a different down vs up time constant
     756      137585 :             _spin_up_ratio += constrain_float(spin_up_ground_idle_ratio - _spin_up_ratio, -spool_down_step, spool_up_step);
     757      137585 :             break;
     758             :         }
     759             :         }
     760             :         // no throttle authority yet in ground idle
     761      263368 :         _throttle_thrust_max = 0.0f;
     762             : 
     763             :         // initialise motor failure variables
     764             :         // disable thrust boost in ground idle
     765      263368 :         _thrust_boost = false;
     766      263368 :         _thrust_boost_ratio = 0.0f;
     767      263368 :         break;
     768             :     }
     769       10488 :     case SpoolState::SPOOLING_UP: {
     770             :         // maximum throttle should move from minimum to maximum
     771             :         // spin is already 1.0 and ramp the throttle ceiling smoothly (avoid step changes)
     772       10488 :         const float spool_step = _dt_s / _spool_up_time;
     773             : 
     774             :         // initialize limits flags
     775             :         // clear all limits and allow normal attitude control during throttle ramp-up
     776       10488 :         limit.set_all(false);
     777             : 
     778             :         // make sure the motors are spooling in the correct direction
     779             :         // if desired target is below THROTTLE_UNLIMITED, reverse to SPOOLING_DOWN
     780       10488 :         if (_spool_desired != DesiredSpoolState::THROTTLE_UNLIMITED) {
     781           0 :             _spool_state = SpoolState::SPOOLING_DOWN;
     782           0 :             break;
     783             :         }
     784             : 
     785             :         // set and increment ramp variables
     786             :         // hold spin at 1.0; increase throttle ceiling by bounded step
     787       10488 :         _spin_up_ratio = 1.0f;
     788       10488 :         _throttle_thrust_max += spool_step;
     789             : 
     790             :         // switch to THROTTLE_UNLIMITED once the moving ceiling no longer limits the commanded throttle
     791             :         // then set the ceiling to the steady-state current-limited maximum used in normal operation
     792       10488 :         if (_throttle_thrust_max >= MIN(get_throttle(), get_current_limit_max_throttle())) {
     793         737 :             _throttle_thrust_max = get_current_limit_max_throttle();
     794         737 :             _spool_state = SpoolState::THROTTLE_UNLIMITED;
     795        9751 :         } else if (_throttle_thrust_max < 0.0f) {
     796           0 :             _throttle_thrust_max = 0.0f;
     797             :         }
     798             : 
     799             :         // fade any thrust boost during spool-up
     800       10488 :         _thrust_boost = false;
     801       10488 :         _thrust_boost_ratio = MAX(0.0, _thrust_boost_ratio - spool_step);
     802       10488 :         break;
     803             :     }
     804             : 
     805    20763527 :     case SpoolState::THROTTLE_UNLIMITED: {
     806             :         // minimum and maximum throttle at normal values
     807             :         // throttle follows controller demand, bounded by runtime current limiting
     808             :         // servos should exhibit normal flight behavior
     809             : 
     810             :         // step used to slew boost ratio (not used for throttle in this state)
     811    20763527 :         const float spool_step = _dt_s / _spool_up_time;
     812             : 
     813             :         // initialize limits flags
     814             :         // clear all limits and allow normal attitude control
     815    20763527 :         limit.set_all(false);
     816             : 
     817             :         // make sure the motors are spooling in the correct direction
     818             :         // if desired drops below THROTTLE_UNLIMITED, begin spooling down
     819    20763527 :         if (_spool_desired != DesiredSpoolState::THROTTLE_UNLIMITED) {
     820         500 :             _spool_state = SpoolState::SPOOLING_DOWN;
     821         500 :             break;
     822             :         }
     823             : 
     824             :         // set steady-state scalars
     825             :         // hold spin at 1.0; throttle ceiling follows the steady-state current-limited maximum
     826    20763027 :         _spin_up_ratio = 1.0f;
     827    20763027 :         _throttle_thrust_max = get_current_limit_max_throttle();
     828             : 
     829             :         // manage thrust boost ratio smoothly:
     830             :         // - increase toward 1 when boost requested and not yet balanced
     831             :         // - otherwise decay toward 0
     832    20763027 :         if (_thrust_boost && !_thrust_balanced) {
     833           0 :             _thrust_boost_ratio = MIN(1.0, _thrust_boost_ratio + spool_step);
     834             :         } else {
     835    20763027 :             _thrust_boost_ratio = MAX(0.0, _thrust_boost_ratio - spool_step);
     836             :         }
     837    20763027 :         break;
     838             :     }
     839             : 
     840       84784 :     case SpoolState::SPOOLING_DOWN:
     841             :         // maximum throttle should move from maximum to minimum
     842             :         // throttle ceiling ramps down; spin stays at 1.0 until we reach ground idle
     843             :         // servos should exhibit normal flight behavior
     844             : 
     845             :         // initialize limits flags
     846             :         // clear all limits; allow normal attitude control during throttle ramp-down
     847       84784 :         limit.set_all(false);
     848             : 
     849             :         // make sure the motors are spooling in the correct direction
     850             :         // if desired returns to THROTTLE_UNLIMITED, reverse to SPOOLING_UP
     851       84784 :         if (_spool_desired == DesiredSpoolState::THROTTLE_UNLIMITED) {
     852           5 :             _spool_state = SpoolState::SPOOLING_UP;
     853           5 :             break;
     854             :         }
     855             : 
     856             :         // set and increment ramp variables
     857             :         // hold spin at 1.0 during throttle down-ramp; spin reduction happens in GROUND_IDLE
     858       84779 :         _spin_up_ratio = 1.0f;
     859             : 
     860             :         // choose down-ramp time: use SPOOL_TIM_DN when configured, else mirror SPOOL_TIME
     861       84779 :         const float spool_time = _spool_down_time > minimum_spool_time ? _spool_down_time : _spool_up_time;
     862       84779 :         const float spool_step = _dt_s / spool_time;
     863             : 
     864             :         // ramp the normalized throttle ceiling toward 0
     865       84779 :         _throttle_thrust_max -= spool_step;
     866             : 
     867             :         // constrain ramp value and update mode
     868             :         // clamp lower bound at 0
     869       84779 :         if (_throttle_thrust_max <= 0.0f) {
     870         494 :             _throttle_thrust_max = 0.0f;
     871             :         }
     872             :         // respect runtime current-limit ceiling
     873       84779 :         if (_throttle_thrust_max >= get_current_limit_max_throttle()) {
     874           0 :             _throttle_thrust_max = get_current_limit_max_throttle();
     875             :         // transition to GROUND_IDLE when the ceiling is effectively zero
     876       84779 :         } else if (is_zero(_throttle_thrust_max)) {
     877         494 :             _spool_state = SpoolState::GROUND_IDLE;
     878             :         }
     879             : 
     880             :         // fade any thrust boost to zero during down-ramp
     881       84779 :         _thrust_boost_ratio = MAX(0.0, _thrust_boost_ratio - spool_step);
     882       84779 :         break;
     883             :     }
     884    36102663 : }
     885             : 
     886             : // passes throttle directly to all motors for ESC calibration.
     887             : // throttle_input is in the range of 0 ~ 1 where 0 will send get_pwm_output_min() and 1 will send get_pwm_output_max()
     888        2263 : void AP_MotorsMulticopter::set_throttle_passthrough_for_esc_calibration(float throttle_input)
     889             : {
     890        2263 :     if (armed()) {
     891        2263 :         uint16_t pwm_out = get_pwm_output_min() + constrain_float(throttle_input, 0.0f, 1.0f) * (get_pwm_output_max() - get_pwm_output_min());
     892             :         // send the pilot's input directly to each enabled motor
     893       74679 :         for (uint16_t i = 0; i < AP_MOTORS_MAX_NUM_MOTORS; i++) {
     894       72416 :             if (motor_enabled[i]) {
     895        9052 :                 rc_write(i, pwm_out);
     896             :             }
     897             :         }
     898             :         // send pwm output to channels used by bicopter
     899        2263 :         SRV_Channels::set_output_pwm(SRV_Channel::k_throttleRight, pwm_out);
     900        2263 :         SRV_Channels::set_output_pwm(SRV_Channel::k_throttleLeft, pwm_out);
     901             :     }
     902        2263 : }
     903             : 
     904             : // output a thrust to all motors that match a given motor mask. This
     905             : // is used to control tiltrotor motors in forward flight. Thrust is in
     906             : // the range 0 to 1
     907      343285 : void AP_MotorsMulticopter::output_motor_mask(float thrust, uint32_t mask, float rudder_dt)
     908             : {
     909      343285 :     const int16_t pwm_min = get_pwm_output_min();
     910      343285 :     const int16_t pwm_range = get_pwm_output_max() - pwm_min;
     911             : 
     912      343285 :     _motor_mask_override = mask;
     913             : 
     914    11328405 :     for (uint8_t i = 0; i < AP_MOTORS_MAX_NUM_MOTORS; i++) {
     915    10985120 :         if (motor_enabled[i] && (mask & (1U << i)) != 0) {
     916             :             // only apply thrust and rudder mixing when armed and interlocked; otherwise zero throttle
     917      763192 :             if (armed() && get_interlock()) {
     918             :                 /*
     919             :                  apply rudder mixing differential thrust
     920             :                  copter frame roll is plane frame yaw as this only
     921             :                  apples to either tilted motors or tailsitters
     922             :                  */
     923      707772 :                 float diff_thrust = get_roll_factor(i) * rudder_dt * 0.5f;
     924      707772 :                 set_actuator_with_slew(_actuator[i], thrust + diff_thrust);
     925             :             } else {
     926             :                 // zero throttle
     927       55420 :                 _actuator[i] = 0.0;
     928             :             }
     929      763192 :             int16_t pwm_output = pwm_min + pwm_range * _actuator[i];
     930      763192 :             rc_write(i, pwm_output);
     931             :         }
     932             :     }
     933      343285 : }
     934             : 
     935             : // get_motor_mask - returns a bitmask of which outputs are being used for motors (1 means being used)
     936             : // this can be used to ensure other pwm outputs (i.e. for servos) do not conflict
     937       32951 : uint32_t AP_MotorsMulticopter::get_motor_mask()
     938             : {
     939       32951 :     return SRV_Channels::get_output_channel_mask(SRV_Channel::k_boost_throttle);
     940             : }
     941             : 
     942             : // save parameters as part of disarming
     943         770 : void AP_MotorsMulticopter::save_params_on_disarm()
     944             : {
     945             :     // save hover throttle
     946         770 :     if (_throttle_hover_learn == HOVER_LEARN_AND_SAVE) {
     947         767 :         _throttle_hover.save();
     948             :     }
     949         770 : }
     950             : 
     951             : // convert to PWM min and max in the motor lib
     952        1001 : void AP_MotorsMulticopter::convert_pwm_min_max_param(int16_t radio_min, int16_t radio_max)
     953             : {
     954        1001 :     if (_pwm_min.configured() || _pwm_max.configured()) {
     955         898 :         return;
     956             :     }
     957         103 :     _pwm_min.set_and_save(radio_min);
     958         103 :     _pwm_max.set_and_save(radio_max);
     959             : }
     960             : 
     961      100514 : bool AP_MotorsMulticopter::arming_checks(size_t buflen, char *buffer) const
     962             : {
     963             :     // run base class checks
     964      100514 :     if (!AP_Motors::arming_checks(buflen, buffer)) {
     965          56 :         return false;
     966             :     }
     967             : 
     968             :     // Check output function is setup for each motor
     969     3315114 :     for (uint8_t i = 0; i < AP_MOTORS_MAX_NUM_MOTORS; i++) {
     970     3214656 :         if (!motor_enabled[i]) {
     971     2807459 :             continue;
     972             :         }
     973             :         uint8_t chan;
     974      407197 :         SRV_Channel::Function function = SRV_Channels::get_motor_function(i);
     975      407197 :         if (!SRV_Channels::find_channel(function, chan)) {
     976           0 :             hal.util->snprintf(buffer, buflen, "no SERVOx_FUNCTION set to Motor%u", i + 1);
     977           0 :             return false;
     978             :         }
     979             :     }
     980             : 
     981             :     // Check param config
     982      100458 :     if (thr_lin.get_spin_min() > 0.3) {
     983           2 :         hal.util->snprintf(buffer, buflen, "%sSPIN_MIN too high %.2f > 0.3", AP_MOTORS_PARAM_PREFIX, thr_lin.get_spin_min());
     984           2 :         return false;
     985             :     }
     986      100456 :     if (_spin_arm > thr_lin.get_spin_min()) {
     987           1 :         hal.util->snprintf(buffer, buflen, "%sSPIN_ARM > %sSPIN_MIN", AP_MOTORS_PARAM_PREFIX, AP_MOTORS_PARAM_PREFIX);
     988           1 :         return false;
     989             :     }
     990      100455 :     if (!check_mot_pwm_params()) {
     991           2 :         hal.util->snprintf(buffer, buflen, "Check %sPWM_MIN and %sPWM_MAX", AP_MOTORS_PARAM_PREFIX, AP_MOTORS_PARAM_PREFIX);
     992           2 :         return false;
     993             :     }
     994             : 
     995      100453 :     return true;
     996             : }
     997             : 
     998             : // return raw throttle out fraction for motor motor_num, returns true if value is valid, false otherwise
     999           0 : bool AP_MotorsMulticopter::get_raw_motor_throttle(uint8_t motor_num, float& thr_out) const
    1000             : {
    1001           0 :     if (motor_num >= AP_MOTORS_MAX_NUM_MOTORS || !motor_enabled[motor_num]) {
    1002           0 :         return false;
    1003             :     }
    1004             : 
    1005           0 :     thr_out = constrain_float(_actuator[motor_num], 0.0f, 1.0f);
    1006           0 :     return true;
    1007             : }
    1008             : 
    1009             : #if APM_BUILD_TYPE(APM_BUILD_UNKNOWN)
    1010             : // Getters for AP_Motors example, not used by vehicles
    1011             : float AP_MotorsMulticopter::get_throttle_avg_max() const
    1012             : {
    1013             :     return _throttle_avg_max;
    1014             : }
    1015             : 
    1016             : int16_t AP_MotorsMulticopter::get_yaw_headroom() const
    1017             : {
    1018             :     return _yaw_headroom;
    1019             : }
    1020             : #endif

Generated by: LCOV version 1.14