LCOV - code coverage report
Current view: top level - AR_Motors - AP_MotorsUGV.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 350 603 58.0 %
Date: 2026-09-09 11:02:08 Functions: 40 42 95.2 %

          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             :    This program is distributed in the hope that it will be useful,
       7             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
       8             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       9             :    GNU General Public License for more details.
      10             :    You should have received a copy of the GNU General Public License
      11             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.
      12             :  */
      13             : 
      14             : #include <AP_HAL/AP_HAL.h>
      15             : #include <AP_Math/AP_Math.h>
      16             : #include <SRV_Channel/SRV_Channel.h>
      17             : #include <GCS_MAVLink/GCS.h>
      18             : #include "AP_MotorsUGV.h"
      19             : #include <AP_Relay/AP_Relay.h>
      20             : #include <AP_BattMonitor/AP_BattMonitor.h>
      21             : #include <AP_Scheduler/AP_Scheduler.h>
      22             : 
      23             : #define SERVO_MAX 4500  // This value represents 45 degrees and is just an arbitrary representation of servo max travel.
      24             : 
      25             : extern const AP_HAL::HAL& hal;
      26             : 
      27             : // singleton instance
      28             : AP_MotorsUGV *AP_MotorsUGV::_singleton;
      29             : 
      30             : // parameters for the motor class
      31             : const AP_Param::GroupInfo AP_MotorsUGV::var_info[] = {
      32             :     // @Param: PWM_TYPE
      33             :     // @DisplayName: Motor Output PWM type
      34             :     // @Description: This selects the output PWM type as regular PWM, OneShot, Brushed motor support using PWM (duty cycle) with separated direction signal, Brushed motor support with separate throttle and direction PWM (duty cycle)
      35             :     // @Values: 0:Normal,1:OneShot,2:OneShot125,3:BrushedWithRelay,4:BrushedBiPolar,5:DShot150,6:DShot300,7:DShot600,8:DShot1200
      36             :     // @User: Advanced
      37             :     // @RebootRequired: True
      38             :     AP_GROUPINFO("PWM_TYPE", 1, AP_MotorsUGV, _pwm_type, PWMType::NORMAL),
      39             : 
      40             :     // @Param: PWM_FREQ
      41             :     // @DisplayName: Motor Output PWM freq for brushed motors
      42             :     // @Description: Motor Output PWM freq for brushed motors
      43             :     // @Units: kHz
      44             :     // @Range: 1 20
      45             :     // @Increment: 1
      46             :     // @User: Advanced
      47             :     // @RebootRequired: True
      48             :     AP_GROUPINFO("PWM_FREQ", 2, AP_MotorsUGV, _pwm_freq, 16),
      49             : 
      50             :     // @Param: SAFE_DISARM
      51             :     // @DisplayName: Motor PWM output disabled when disarmed
      52             :     // @Description: Disables motor PWM output when disarmed
      53             :     // @Values: 0:PWM enabled while disarmed, 1:PWM disabled while disarmed
      54             :     // @User: Advanced
      55             :     AP_GROUPINFO("SAFE_DISARM", 3, AP_MotorsUGV, _disarm_disable_pwm, 0),
      56             : 
      57             :     // @Param: THR_MIN
      58             :     // @DisplayName: Throttle minimum
      59             :     // @Description: Throttle minimum percentage the autopilot will apply. This is useful for handling a deadzone around low throttle and for preventing internal combustion motors cutting out during missions. Must be less than MOT_THR_MAX.
      60             :     // @Units: %
      61             :     // @Range: 0 20
      62             :     // @Increment: 1
      63             :     // @User: Standard
      64             :     AP_GROUPINFO("THR_MIN", 5, AP_MotorsUGV, _throttle_min, 0),
      65             : 
      66             :     // @Param: THR_MAX
      67             :     // @DisplayName: Throttle maximum
      68             :     // @Description: Throttle maximum percentage the autopilot will apply. This can be used to prevent overheating an ESC or motor on an electric rover
      69             :     // @Units: %
      70             :     // @Range: 5 100
      71             :     // @Increment: 1
      72             :     // @User: Standard
      73             :     AP_GROUPINFO("THR_MAX", 6, AP_MotorsUGV, _throttle_max, 100),
      74             : 
      75             :     // @Param: SLEWRATE
      76             :     // @DisplayName: Throttle slew rate
      77             :     // @Description: Throttle slew rate as a percentage of total range per second. A value of 100 allows the motor to change over its full range in one second.  A value of zero disables the limit.  Note some NiMH powered rovers require a lower setting of 40 to reduce current demand to avoid brownouts.
      78             :     // @Units: %/s
      79             :     // @Range: 0 1000
      80             :     // @Increment: 1
      81             :     // @User: Standard
      82             :     AP_GROUPINFO("SLEWRATE", 8, AP_MotorsUGV, _slew_rate, 100),
      83             : 
      84             :     // @Param: THST_EXPO
      85             :     // @DisplayName: Thrust Curve Expo
      86             :     // @Description: Thrust curve exponent (-1 to +1 with 0 being linear)
      87             :     // @Range: -1.0 1.0
      88             :     // @User: Advanced
      89             :     AP_GROUPINFO("THST_EXPO", 9, AP_MotorsUGV, _thrust_curve_expo, 0.0f),
      90             : 
      91             :     // 10 was VEC_THR_BASE
      92             : 
      93             :     // @Param: SPD_SCA_BASE
      94             :     // @DisplayName: Motor speed scaling base speed
      95             :     // @Description: Speed above which steering is scaled down when using regular steering/throttle vehicles.  zero to disable speed scaling
      96             :     // @Units: m/s
      97             :     // @Range: 0 10
      98             :     // @User: Advanced
      99             :     AP_GROUPINFO("SPD_SCA_BASE", 11, AP_MotorsUGV, _speed_scale_base, 1.0f),
     100             : 
     101             :     // @Param: STR_THR_MIX
     102             :     // @DisplayName: Motor steering vs throttle prioritisation
     103             :     // @Description: Steering vs Throttle priorisation.  Higher numbers prioritise steering, lower numbers prioritise throttle.  Only valid for Skid Steering vehicles
     104             :     // @Range: 0.2 1.0
     105             :     // @User: Advanced
     106             :     AP_GROUPINFO("STR_THR_MIX", 12, AP_MotorsUGV, _steering_throttle_mix, 0.5f),
     107             : 
     108             :     // @Param: VEC_ANGLEMAX
     109             :     // @DisplayName: Vector thrust angle max
     110             :     // @Description: The angle between steering's middle position and maximum position when using vectored thrust (boats only)
     111             :     // @Units: deg
     112             :     // @Range: 0 90
     113             :     // @User: Standard
     114             :     AP_GROUPINFO("VEC_ANGLEMAX", 13, AP_MotorsUGV, _vector_angle_max, 0.0f),
     115             : 
     116             :     // @Param: THST_ASYM
     117             :     // @DisplayName: Motor Thrust Asymmetry
     118             :     // @Description: Thrust Asymmetry. Used for skid-steering. 2.0 means your motors move twice as fast forward than they do backwards.
     119             :     // @Range: 1.0 10.0
     120             :     // @User: Advanced
     121             :     AP_GROUPINFO("THST_ASYM", 14, AP_MotorsUGV, _thrust_asymmetry, 1.0f),
     122             : 
     123             :     // @Param: REV_DELAY
     124             :     // @DisplayName: Motor reversal delay
     125             :     // @Description: For reversible motors that need a delay before they can change direction. When greater than zero the throttle will go to zero for this amount of time before outputting the new throttle when the demanded motor direction changes.
     126             :     // @Units: s
     127             :     // @Range: 0.1 1.0
     128             :     // @Increment: 0.1
     129             :     // @User: Standard
     130             :     AP_GROUPINFO("REV_DELAY", 15, AP_MotorsUGV, _reverse_delay, 0),
     131             : 
     132             : #if AP_BATTERY_WATT_MAX_ENABLED
     133             :     // @Param: BAT_WATT_TC
     134             :     // @DisplayName: Battery power limiting time constant
     135             :     // @Description: Time constant used to limit the smooth power throttling.
     136             :     // @Range: 0 10
     137             :     // @Units: s
     138             :     // @User: Advanced
     139             :     AP_GROUPINFO("BAT_WATT_TC", 16, AP_MotorsUGV, _batt_power_time_constant, 2.0f),
     140             : #endif
     141             : 
     142             :     AP_GROUPEND
     143             : };
     144             : 
     145         285 : AP_MotorsUGV::AP_MotorsUGV(AP_WheelRateControl& rate_controller) :
     146         285 :     _rate_controller(rate_controller)
     147             : {
     148         285 :     AP_Param::setup_object_defaults(this, var_info);
     149         285 :     _singleton = this;
     150         285 : }
     151             : 
     152         283 : void AP_MotorsUGV::init(uint8_t frtype)
     153             : {
     154         283 :     _frame_type = frame_type(frtype);
     155             : 
     156             :     // setup for omni vehicles
     157         283 :     if (_frame_type != FRAME_TYPE_UNDEFINED) {
     158           1 :         setup_omni();
     159             :     }
     160             :     
     161             :     // setup servo output
     162         283 :     setup_servo_output();
     163             : 
     164             :     // setup pwm type
     165         283 :     setup_pwm_type();
     166             : 
     167             :     // set safety output
     168         283 :     setup_safety_output();
     169             : 
     170         283 : }
     171             : 
     172         283 : bool AP_MotorsUGV::get_legacy_relay_index(int8_t &index1, int8_t &index2, int8_t &index3, int8_t &index4) const
     173             : {
     174         283 :     if (_pwm_type != PWMType::BRUSHED_WITH_RELAY) {
     175             :         // Relays only used if PWM type is set to brushed with relay
     176         283 :         return false;
     177             :     }
     178             : 
     179             :     // First relay is always used, throttle, throttle left or motor 1
     180           0 :     index1 = 0;
     181             : 
     182             :     // Second relay is used for right throttle on skid steer and motor 2 for omni
     183           0 :     if (have_skid_steering()) {
     184           0 :         index2 = 1;
     185             :     }
     186             : 
     187             :     // Omni can have a variable number of motors
     188           0 :     if (is_omni()) {
     189             :         // Omni has at least 3 motors
     190           0 :         index2 = 2;
     191           0 :         if (_motors_num >= 4) {
     192           0 :             index2 = 3;
     193             :         }
     194             :     }
     195             : 
     196           0 :     return true;
     197             : }
     198             : 
     199             : // setup output in case of main CPU failure
     200        8082 : void AP_MotorsUGV::setup_safety_output()
     201             : {
     202        8082 :     if (_pwm_type == PWMType::BRUSHED_WITH_RELAY) {
     203             :         // set trim to min to set duty cycle range (0 - 100%) to servo range
     204             :         // ignore servo revese flag, it is used by the relay
     205           0 :         SRV_Channels::set_trim_to_min_for(SRV_Channel::k_throttle, true);
     206           0 :         SRV_Channels::set_trim_to_min_for(SRV_Channel::k_throttleLeft, true);
     207           0 :         SRV_Channels::set_trim_to_min_for(SRV_Channel::k_throttleRight, true);
     208             :     }
     209             : 
     210             :     // stop sending pwm if main CPU fails
     211        8082 :     SRV_Channels::set_failsafe_limit(SRV_Channel::k_throttle, SRV_Channel::Limit::ZERO_PWM);
     212        8082 :     SRV_Channels::set_failsafe_limit(SRV_Channel::k_throttleLeft, SRV_Channel::Limit::ZERO_PWM);
     213        8082 :     SRV_Channels::set_failsafe_limit(SRV_Channel::k_throttleRight, SRV_Channel::Limit::ZERO_PWM);
     214        8082 : }
     215             : 
     216             : // setup servo output ranges
     217        8082 : void AP_MotorsUGV::setup_servo_output()
     218             : {
     219             :     // k_steering are limited to -45;45 degree
     220        8082 :     SRV_Channels::set_angle(SRV_Channel::k_steering, SERVO_MAX);
     221             : 
     222             :     // k_throttle are in power percent so -100 ... 100
     223        8082 :     SRV_Channels::set_angle(SRV_Channel::k_throttle, 100);
     224             : 
     225             :     // skid steering left/right throttle as -1000 to 1000 values
     226        8082 :     SRV_Channels::set_angle(SRV_Channel::k_throttleLeft,  1000);
     227        8082 :     SRV_Channels::set_angle(SRV_Channel::k_throttleRight, 1000);
     228             : 
     229             :     // omni motors set in power percent so -100 ... 100
     230       40410 :     for (uint8_t i=0; i<AP_MOTORS_NUM_MOTORS_MAX; i++) {
     231       32328 :         SRV_Channel::Function function = SRV_Channels::get_motor_function(i);
     232       32328 :         SRV_Channels::set_angle(function, 100);
     233             :     }
     234             : 
     235             :     // mainsail range from 0 to 100
     236        8082 :     SRV_Channels::set_range(SRV_Channel::k_mainsail_sheet, 100);
     237             :     // wing sail -100 to 100
     238        8082 :     SRV_Channels::set_angle(SRV_Channel::k_wingsail_elevator, 100);
     239             :     // mast rotation -100 to 100
     240        8082 :     SRV_Channels::set_angle(SRV_Channel::k_mast_rotation, 100);
     241             : 
     242        8082 : }
     243             : 
     244             : // set steering as a value from -4500 to +4500
     245             : //   apply_scaling should be set to false for manual modes where
     246             : //   no scaling by speed or angle should be performed
     247      629044 : void AP_MotorsUGV::set_steering(float steering, bool apply_scaling)
     248             : {
     249      629044 :     _steering = steering;
     250      629044 :     _scale_steering = apply_scaling;
     251      629044 : }
     252             : 
     253             : // set throttle as a value from -100 to 100
     254      629044 : void AP_MotorsUGV::set_throttle(float throttle)
     255             : {
     256             :     // only allow setting throttle if armed
     257      629044 :     if (!hal.util->get_soft_armed()) {
     258      378000 :         return;
     259             :     }
     260             : 
     261             :     // check throttle is between -_throttle_max and  +_throttle_max
     262      251044 :     _throttle = constrain_float(throttle, -_throttle_max, _throttle_max);
     263             : }
     264             : 
     265             : // set lateral input as a value from -100 to +100
     266      362216 : void AP_MotorsUGV::set_lateral(float lateral)
     267             : {
     268      362216 :     _lateral = constrain_float(lateral, -100.0f, 100.0f);
     269      362216 : }
     270             : 
     271             : // set roll input as a value from -1 to +1
     272      362133 : void AP_MotorsUGV::set_roll(float roll)
     273             : {
     274      362133 :     _roll = constrain_float(roll, -1.0f, 1.0f);
     275      362133 : }
     276             : 
     277             : // set pitch input as a value from -1 to +1
     278      362133 : void AP_MotorsUGV::set_pitch(float pitch)
     279             : {
     280      362133 :     _pitch = constrain_float(pitch, -1.0f, 1.0f);
     281      362133 : }
     282             : 
     283             : // set walking_height input as a value from -1 to +1
     284      362133 : void AP_MotorsUGV::set_walking_height(float walking_height)
     285             : {
     286      362133 :     _walking_height = constrain_float(walking_height, -1.0f, 1.0f);
     287      362133 : }
     288             : 
     289             : // set mainsail input as a value from 0 to 100
     290      426905 : void AP_MotorsUGV::set_mainsail(float mainsail)
     291             : {
     292      426905 :     _mainsail = constrain_float(mainsail, 0.0f, 100.0f);
     293      426905 : }
     294             : 
     295             : // set wingsail input as a value from -100 to 100
     296      426905 : void AP_MotorsUGV::set_wingsail(float wingsail)
     297             : {
     298      426905 :     _wingsail = constrain_float(wingsail, -100.0f, 100.0f);
     299      426905 : }
     300             : 
     301             : // set mast rotation input as a value from -100 to 100
     302      426905 : void AP_MotorsUGV::set_mast_rotation(float mast_rotation)
     303             : {
     304      426905 :     _mast_rotation = constrain_float(mast_rotation, -100.0f, 100.0f);
     305      426905 : }
     306             : 
     307             : // get slew limited throttle
     308             : // used by manual mode to avoid bad steering behaviour during transitions from forward to reverse
     309             : // same as private slew_limit_throttle method (see below) but does not update throttle state
     310      991116 : float AP_MotorsUGV::get_slew_limited_throttle(float throttle, float dt) const
     311             : {
     312      991116 :     if (_slew_rate <= 0) {
     313      116970 :         return throttle;
     314             :     }
     315             : 
     316      874146 :     const float throttle_change_max = static_cast<float>(_slew_rate) * dt;
     317      874146 :     return constrain_float(throttle, _throttle_prev - throttle_change_max, _throttle_prev + throttle_change_max);
     318             : }
     319             : 
     320             : /*
     321             :   work out if skid steering is available
     322             :  */
     323     1099408 : bool AP_MotorsUGV::have_skid_steering() const
     324             : {
     325     1099408 :     return (SRV_Channels::function_assigned(SRV_Channel::k_throttleLeft) && SRV_Channels::function_assigned(SRV_Channel::k_throttleRight)) || is_omni();
     326             : }
     327             : 
     328             : // true if the vehicle has a mainsail
     329      644109 : bool AP_MotorsUGV::has_sail() const
     330             : {
     331      644109 :     return SRV_Channels::function_assigned(SRV_Channel::k_mainsail_sheet) || SRV_Channels::function_assigned(SRV_Channel::k_wingsail_elevator) || SRV_Channels::function_assigned(SRV_Channel::k_mast_rotation);
     332             : }
     333             : 
     334      628798 : void AP_MotorsUGV::output(bool armed, float ground_speed, float dt)
     335             : {
     336             :     // soft-armed overrides passed in armed status
     337      628798 :     if (!hal.util->get_soft_armed()) {
     338      378000 :         armed = false;
     339      378000 :         _throttle = 0.0f;
     340             :     }
     341             : 
     342             :     // clear limit flags
     343             :     // output_ methods are responsible for setting them to true if required on each iteration
     344      628798 :     limit.steer_left = limit.steer_right = limit.throttle_lower = limit.throttle_upper = false;
     345             : 
     346             :     // sanity check parameters
     347      628798 :     sanity_check_parameters();
     348             : 
     349             :     // slew limit throttle
     350      628798 :     slew_limit_throttle(dt);
     351             : 
     352             :     // apply power limiting to throttle
     353             : #if AP_BATTERY_WATT_MAX_ENABLED
     354      628798 :     power_limit_throttle(dt);
     355             : #endif
     356             : 
     357             :     // output for regular steering/throttle style frames
     358      628798 :     output_regular(armed, ground_speed, _steering, _throttle);
     359             : 
     360             :     // output for skid steering style frames
     361      628798 :     output_skid_steering(armed, _steering, _throttle, dt);
     362             : 
     363             :     // output for omni frames
     364      628798 :     output_omni(armed, _steering, _throttle, _lateral);
     365             : 
     366             :     // output to sails
     367      628798 :     output_sail();
     368             : 
     369             :     // send values to the PWM timers for output
     370      628798 :     auto &srv = AP::srv();
     371      628798 :     SRV_Channels::calc_pwm();
     372      628798 :     srv.cork();
     373      628798 :     SRV_Channels::output_ch_all();
     374      628798 :     srv.push();
     375      628798 : }
     376             : 
     377             : // test steering or throttle output as a percentage of the total (range -100 to +100)
     378             : // used in response to DO_MOTOR_TEST mavlink command
     379           0 : bool AP_MotorsUGV::output_test_pct(motor_test_order motor_seq, float pct)
     380             : {
     381             :     // check if the motor_seq is valid
     382           0 :     if (motor_seq >= MOTOR_TEST_LAST) {
     383           0 :         return false;
     384             :     }
     385           0 :     pct = constrain_float(pct, -100.0f, 100.0f);
     386             : 
     387           0 :     switch (motor_seq) {
     388           0 :         case MOTOR_TEST_THROTTLE: {
     389           0 :             if (SRV_Channels::function_assigned(SRV_Channel::k_motor1)) {
     390           0 :                 output_throttle(SRV_Channel::k_motor1, pct);
     391             :             }
     392           0 :             if (SRV_Channels::function_assigned(SRV_Channel::k_throttle)) {
     393           0 :                 output_throttle(SRV_Channel::k_throttle, pct);
     394             :             }
     395           0 :             break;
     396             :         }
     397           0 :         case MOTOR_TEST_STEERING: {
     398           0 :             if (SRV_Channels::function_assigned(SRV_Channel::k_motor2)) {
     399           0 :                 output_throttle(SRV_Channel::k_motor2, pct);
     400             :             }
     401           0 :             if (SRV_Channels::function_assigned(SRV_Channel::k_steering)) {
     402           0 :                 SRV_Channels::set_output_scaled(SRV_Channel::k_steering, pct * 45.0f);
     403             :             }
     404           0 :             break;
     405             :         }
     406           0 :         case MOTOR_TEST_THROTTLE_LEFT: {
     407           0 :             if (SRV_Channels::function_assigned(SRV_Channel::k_motor3)) {
     408           0 :                 output_throttle(SRV_Channel::k_motor3, pct);
     409             :             }
     410           0 :             if (SRV_Channels::function_assigned(SRV_Channel::k_throttleLeft)) {
     411           0 :                 output_throttle(SRV_Channel::k_throttleLeft, pct);
     412             :             }
     413           0 :             break;
     414             :         }
     415           0 :         case MOTOR_TEST_THROTTLE_RIGHT: {
     416           0 :             if (SRV_Channels::function_assigned(SRV_Channel::k_motor4)) {
     417           0 :                 output_throttle(SRV_Channel::k_motor4, pct);
     418             :             }
     419           0 :             if (SRV_Channels::function_assigned(SRV_Channel::k_throttleRight)) {
     420           0 :                 output_throttle(SRV_Channel::k_throttleRight, pct);
     421             :             }
     422           0 :             break;
     423             :         }
     424           0 :         case MOTOR_TEST_MAINSAIL: {
     425           0 :             if (SRV_Channels::function_assigned(SRV_Channel::k_mainsail_sheet)) {
     426           0 :                 SRV_Channels::set_output_scaled(SRV_Channel::k_mainsail_sheet, pct);
     427             :             }
     428           0 :             if (SRV_Channels::function_assigned(SRV_Channel::k_wingsail_elevator)) {
     429           0 :                 SRV_Channels::set_output_scaled(SRV_Channel::k_wingsail_elevator, pct);
     430             :             }
     431           0 :             if (SRV_Channels::function_assigned(SRV_Channel::k_mast_rotation)) {
     432           0 :                 SRV_Channels::set_output_scaled(SRV_Channel::k_mast_rotation, pct);
     433             :             }
     434           0 :             break;
     435             :         }
     436           0 :         case MOTOR_TEST_LAST:
     437           0 :             return false;
     438             :     }
     439           0 :     auto &srv = AP::srv();
     440           0 :     SRV_Channels::calc_pwm();
     441           0 :     srv.cork();
     442           0 :     SRV_Channels::output_ch_all();
     443           0 :     srv.push();
     444           0 :     return true;
     445             : }
     446             : 
     447             : // test steering or throttle output using a pwm value
     448         249 : bool AP_MotorsUGV::output_test_pwm(motor_test_order motor_seq, float pwm)
     449             : {
     450             :     // check if the motor_seq is valid
     451         249 :     if (motor_seq > MOTOR_TEST_THROTTLE_RIGHT) {
     452           0 :         return false;
     453             :     }
     454         249 :     switch (motor_seq) {
     455         249 :         case MOTOR_TEST_THROTTLE: {
     456         249 :             if (SRV_Channels::function_assigned(SRV_Channel::k_motor1)) {
     457           0 :                 SRV_Channels::set_output_pwm(SRV_Channel::k_motor1, pwm);
     458             :             }
     459         249 :             if (SRV_Channels::function_assigned(SRV_Channel::k_throttle)) {
     460         249 :                 SRV_Channels::set_output_pwm(SRV_Channel::k_throttle, pwm);
     461             :             }
     462         249 :             break;
     463             :         }
     464           0 :         case MOTOR_TEST_STEERING: {
     465           0 :             if (SRV_Channels::function_assigned(SRV_Channel::k_motor2)) {
     466           0 :                 SRV_Channels::set_output_pwm(SRV_Channel::k_motor2, pwm);
     467             :             }
     468           0 :             if (SRV_Channels::function_assigned(SRV_Channel::k_steering)) {
     469           0 :                 SRV_Channels::set_output_pwm(SRV_Channel::k_steering, pwm);
     470             :             }
     471           0 :             break;
     472             :         }
     473           0 :         case MOTOR_TEST_THROTTLE_LEFT: {
     474           0 :             if (SRV_Channels::function_assigned(SRV_Channel::k_motor3)) {
     475           0 :                 SRV_Channels::set_output_pwm(SRV_Channel::k_motor3, pwm);
     476             :             }
     477           0 :             if (SRV_Channels::function_assigned(SRV_Channel::k_throttleLeft)) {
     478           0 :                 SRV_Channels::set_output_pwm(SRV_Channel::k_throttleLeft, pwm);
     479             :             }
     480           0 :             break;
     481             :         }
     482           0 :         case MOTOR_TEST_THROTTLE_RIGHT: {
     483           0 :             if (SRV_Channels::function_assigned(SRV_Channel::k_motor4)) {
     484           0 :                 SRV_Channels::set_output_pwm(SRV_Channel::k_motor4, pwm);
     485             :             }
     486           0 :             if (SRV_Channels::function_assigned(SRV_Channel::k_throttleRight)) {
     487           0 :                 SRV_Channels::set_output_pwm(SRV_Channel::k_throttleRight, pwm);
     488             :             }
     489           0 :             break;
     490             :         }
     491           0 :         case MOTOR_TEST_MAINSAIL: {
     492           0 :             if (SRV_Channels::function_assigned(SRV_Channel::k_mainsail_sheet)) {
     493           0 :                 SRV_Channels::set_output_pwm(SRV_Channel::k_mainsail_sheet, pwm);
     494             :             }
     495           0 :             if (SRV_Channels::function_assigned(SRV_Channel::k_wingsail_elevator)) {
     496           0 :                 SRV_Channels::set_output_pwm(SRV_Channel::k_wingsail_elevator, pwm);
     497             :             }
     498           0 :             if (SRV_Channels::function_assigned(SRV_Channel::k_mast_rotation)) {
     499           0 :                 SRV_Channels::set_output_pwm(SRV_Channel::k_mast_rotation, pwm);
     500             :             }
     501           0 :             break;
     502             :         }
     503           0 :         default:
     504           0 :             return false;
     505             :     }
     506         249 :     auto &srv = AP::srv();
     507         249 :     SRV_Channels::calc_pwm();
     508         249 :     srv.cork();
     509         249 :     SRV_Channels::output_ch_all();
     510         249 :     srv.push();
     511         249 :     return true;
     512             : }
     513             : 
     514             : //  returns true if checks pass, false if they fail.  report should be true to send text messages to GCS
     515       15226 : bool AP_MotorsUGV::pre_arm_check(bool report) const
     516             : {
     517       15226 :     const bool have_throttle = SRV_Channels::function_assigned(SRV_Channel::k_throttle);
     518       15226 :     const bool have_throttle_left = SRV_Channels::function_assigned(SRV_Channel::k_throttleLeft);
     519       15226 :     const bool have_throttle_right = SRV_Channels::function_assigned(SRV_Channel::k_throttleRight);
     520             : 
     521             :     // check that there's defined outputs, inc scripting and sail
     522       44113 :     if(!have_throttle_left &&
     523       13661 :        !have_throttle_right &&
     524       13661 :        !have_throttle &&
     525          85 :        !SRV_Channels::function_assigned(SRV_Channel::k_steering) &&
     526          85 :        !SRV_Channels::function_assigned(SRV_Channel::k_scripting1) &&
     527       28972 :        !has_sail() &&
     528          85 :        !is_omni()) {
     529           0 :         if (report) {
     530           0 :             GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "PreArm: no motor, sail or scripting outputs defined");
     531             :         }
     532           0 :         return false;
     533             :     }
     534             :     // check if only one of skid-steering output has been configured
     535       15226 :     if (have_throttle_left != have_throttle_right) {
     536           0 :         if (report) {
     537           0 :             GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "PreArm: check skid steering config");
     538             :         }
     539           0 :         return false;
     540             :     }
     541             :     // check if only one of throttle or steering outputs has been configured, if has a sail allow no throttle
     542       15226 :     if ((has_sail() || have_throttle) != SRV_Channels::function_assigned(SRV_Channel::k_steering)) {
     543           0 :         if (report) {
     544           0 :             GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "PreArm: check steering and throttle config");
     545             :         }
     546           0 :         return false;
     547             :     }
     548             :     // check all omni motor outputs have been configured
     549       15481 :     for (uint8_t i=0; i<_motors_num; i++) {
     550         255 :         SRV_Channel::Function function = SRV_Channels::get_motor_function(i);
     551         255 :         if (!SRV_Channels::function_assigned(function)) {
     552           0 :             if (report) {
     553           0 :                 GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "PreArm: servo function %u unassigned", function);
     554             :             }
     555           0 :             return false;
     556             :         }
     557             :     }
     558             : 
     559             :     // Check relays are configured for brushed with relay outputs
     560             : #if AP_RELAY_ENABLED
     561       15226 :     AP_Relay*relay = AP::relay();
     562       15226 :     if ((_pwm_type == PWMType::BRUSHED_WITH_RELAY) && (relay != nullptr)) {
     563             :         // If a output is configured its relay must be enabled
     564             :         struct RelayTable {
     565             :             bool output_assigned;
     566             :             AP_Relay_Params::FUNCTION fun;
     567             :         };
     568             : 
     569           0 :         const RelayTable relay_table[] = {
     570           0 :             { have_throttle || have_throttle_left || (SRV_Channels::function_assigned(SRV_Channel::k_motor1) && (_motors_num >= 1)), AP_Relay_Params::FUNCTION::BRUSHED_REVERSE_1 },
     571           0 :             { have_throttle_right || (SRV_Channels::function_assigned(SRV_Channel::k_motor2) && (_motors_num >= 2)),                 AP_Relay_Params::FUNCTION::BRUSHED_REVERSE_2 },
     572           0 :             { SRV_Channels::function_assigned(SRV_Channel::k_motor3) && (_motors_num >= 3),                                          AP_Relay_Params::FUNCTION::BRUSHED_REVERSE_3 },
     573           0 :             { SRV_Channels::function_assigned(SRV_Channel::k_motor4) && (_motors_num >= 4),                                          AP_Relay_Params::FUNCTION::BRUSHED_REVERSE_4 },
     574           0 :         };
     575             : 
     576           0 :         for (uint8_t i=0; i<ARRAY_SIZE(relay_table); i++) {
     577           0 :             if (relay_table[i].output_assigned && !relay->enabled(relay_table[i].fun)) {
     578           0 :                 if (report) {
     579           0 :                     GCS_SEND_TEXT(MAV_SEVERITY_CRITICAL, "PreArm: relay function %u unassigned", uint8_t(relay_table[i].fun));
     580             :                 }
     581           0 :                 return false;
     582             :             }
     583             :         }
     584             :     }
     585             : #endif
     586             : 
     587       15226 :     return true;
     588             : }
     589             : 
     590             : // sanity check parameters
     591      628798 : void AP_MotorsUGV::sanity_check_parameters()
     592             : {
     593      628798 :     _throttle_max.set(constrain_int16(_throttle_max, 5, 100));
     594      628798 :     _throttle_min.set(constrain_int16(_throttle_min, 0, MIN(20, _throttle_max)));
     595      628798 :     _vector_angle_max.set(constrain_float(_vector_angle_max, 0.0f, 90.0f));
     596      628798 : }
     597             : 
     598             : // setup pwm output type
     599         283 : void AP_MotorsUGV::setup_pwm_type()
     600             : {
     601         283 :     _motor_mask = 0;
     602             : 
     603         283 :     hal.rcout->set_dshot_esc_type(SRV_Channels::get_dshot_esc_type());
     604             : 
     605             :     // work out mask of channels assigned to motors
     606         283 :     _motor_mask |= SRV_Channels::get_output_channel_mask(SRV_Channel::k_throttle);
     607         283 :     _motor_mask |= SRV_Channels::get_output_channel_mask(SRV_Channel::k_throttleLeft);
     608         283 :     _motor_mask |= SRV_Channels::get_output_channel_mask(SRV_Channel::k_throttleRight);
     609         286 :     for (uint8_t i=0; i<_motors_num; i++) {
     610           3 :         _motor_mask |= SRV_Channels::get_output_channel_mask(SRV_Channels::get_motor_function(i));
     611             :     }
     612             : 
     613         283 :     switch (_pwm_type) {
     614           0 :     case PWMType::ONESHOT:
     615           0 :         hal.rcout->set_output_mode(_motor_mask, AP_HAL::RCOutput::MODE_PWM_ONESHOT);
     616           0 :         break;
     617           0 :     case PWMType::ONESHOT125:
     618           0 :         hal.rcout->set_output_mode(_motor_mask, AP_HAL::RCOutput::MODE_PWM_ONESHOT125);
     619           0 :         break;
     620           0 :     case PWMType::BRUSHED_WITH_RELAY:
     621             :     case PWMType::BRUSHED_BIPOLAR:
     622           0 :         hal.rcout->set_output_mode(_motor_mask, AP_HAL::RCOutput::MODE_PWM_BRUSHED);
     623           0 :         hal.rcout->set_freq(_motor_mask, uint16_t(_pwm_freq * 1000));
     624           0 :         break;
     625           0 :     case PWMType::DSHOT150:
     626           0 :         hal.rcout->set_output_mode(_motor_mask, AP_HAL::RCOutput::MODE_PWM_DSHOT150);
     627           0 :         break;
     628           0 :     case PWMType::DSHOT300:
     629           0 :         hal.rcout->set_output_mode(_motor_mask, AP_HAL::RCOutput::MODE_PWM_DSHOT300);
     630           0 :         break;
     631           0 :     case PWMType::DSHOT600:
     632           0 :         hal.rcout->set_output_mode(_motor_mask, AP_HAL::RCOutput::MODE_PWM_DSHOT600);
     633           0 :         break;
     634           0 :     case PWMType::DSHOT1200:
     635           0 :         hal.rcout->set_output_mode(_motor_mask, AP_HAL::RCOutput::MODE_PWM_DSHOT1200);
     636           0 :         break;
     637         283 :     default:
     638             :         // do nothing
     639         283 :         break;
     640             :     }
     641         283 : }
     642             : 
     643             : // setup for frames with omni motors
     644           1 : void AP_MotorsUGV::setup_omni()
     645             : {
     646             :     // remove existing motors
     647           5 :     for (int8_t i=0; i<AP_MOTORS_NUM_MOTORS_MAX; i++) {
     648           4 :         clear_omni_motors(i);
     649             :     }
     650             : 
     651             :     // hard coded factor configuration
     652           1 :     switch (_frame_type) {
     653             : 
     654             :     //   FRAME TYPE NAME
     655           0 :     case FRAME_TYPE_UNDEFINED:
     656           0 :         break;
     657             : 
     658           0 :     case FRAME_TYPE_OMNI3:
     659           0 :         _motors_num = 3;
     660           0 :         add_omni_motor(0, 1.0f, -1.0f, -1.0f);
     661           0 :         add_omni_motor(1, 0.0f, -1.0f, 1.0f);
     662           0 :         add_omni_motor(2, 1.0f, 1.0f, 1.0f);
     663           0 :         break;
     664             : 
     665           0 :     case FRAME_TYPE_OMNIX:
     666           0 :         _motors_num = 4,
     667           0 :         add_omni_motor(0, 1.0f, -1.0f, -1.0f);
     668           0 :         add_omni_motor(1, 1.0f, -1.0f, 1.0f);
     669           0 :         add_omni_motor(2, 1.0f, 1.0f, -1.0f);
     670           0 :         add_omni_motor(3, 1.0f, 1.0f, 1.0f);
     671           0 :         break;
     672             : 
     673           0 :     case FRAME_TYPE_OMNIPLUS:
     674           0 :         _motors_num = 4;
     675           0 :         add_omni_motor(0, 0.0f, 1.0f, 1.0f);
     676           0 :         add_omni_motor(1, 1.0f, 0.0f, 0.0f);
     677           0 :         add_omni_motor(2, 0.0f, -1.0f, 1.0f);
     678           0 :         add_omni_motor(3, 1.0f, 0.0f, 0.0f);
     679           0 :         break;
     680             : 
     681           1 :     case FRAME_TYPE_OMNI3MECANUM:
     682           1 :         _motors_num = 3;
     683           1 :         add_omni_motor(0,  -1.0f,    1.0f,  -0.26795f);
     684           1 :         add_omni_motor(1,  0.73205f, 1.0f,  -0.73205f);
     685           1 :         add_omni_motor(2,  0.26795f, 1.0f,   1.0f);
     686           1 :         break;
     687             :     }
     688           1 : }
     689             : 
     690             : // add omni motor using separate throttle, steering and lateral factors
     691           3 : void AP_MotorsUGV::add_omni_motor(int8_t motor_num, float throttle_factor, float steering_factor, float lateral_factor)
     692             : {
     693             :     // ensure valid motor number is provided
     694           3 :     if (motor_num >= 0 && motor_num < AP_MOTORS_NUM_MOTORS_MAX) {
     695             : 
     696             :         // set throttle, steering and lateral factors
     697           3 :         _throttle_factor[motor_num] = throttle_factor;
     698           3 :         _steering_factor[motor_num] = steering_factor;
     699           3 :         _lateral_factor[motor_num] = lateral_factor;
     700             : 
     701           3 :         add_omni_motor_num(motor_num);
     702             :     }
     703           3 : }
     704             : 
     705             : // add an omni motor and set up default output function
     706           3 : void AP_MotorsUGV::add_omni_motor_num(int8_t motor_num)
     707             : {
     708             :     // ensure a valid motor number is provided
     709           3 :     if (motor_num >= 0 && motor_num < AP_MOTORS_NUM_MOTORS_MAX) {
     710             :         uint8_t chan;
     711           3 :         SRV_Channel::Function function = SRV_Channels::get_motor_function(motor_num);
     712           3 :         SRV_Channels::set_aux_channel_default(function, motor_num);
     713           3 :         if (!SRV_Channels::find_channel(function, chan)) {
     714           0 :             GCS_SEND_TEXT(MAV_SEVERITY_ERROR, "Motors: unable to setup motor %u", motor_num);
     715             :         }
     716             :     }
     717           3 : }
     718             : 
     719             : // disable omni motor and remove all throttle, steering and lateral factor for this motor
     720           4 : void AP_MotorsUGV::clear_omni_motors(int8_t motor_num)
     721             : {
     722             :     // ensure valid motor number is provided
     723           4 :     if (motor_num >= 0 && motor_num < AP_MOTORS_NUM_MOTORS_MAX) {
     724             :         // disable the motor and set factors to zero
     725           4 :         _throttle_factor[motor_num] = 0;
     726           4 :         _steering_factor[motor_num] = 0;
     727           4 :         _lateral_factor[motor_num] = 0;
     728             :     }
     729           4 : }
     730             : 
     731             : // output to regular steering and throttle channels
     732      628798 : void AP_MotorsUGV::output_regular(bool armed, float ground_speed, float steering, float throttle)
     733             : {
     734             :     // output to throttle channels
     735      628798 :     if (armed) {
     736      250798 :         if (_scale_steering) {
     737             :             // vectored thrust handling
     738      213545 :             if (have_vectored_thrust()) {
     739             : 
     740             :                 // normalise desired steering and throttle to ease calculations
     741        2485 :                 float steering_norm = steering / 4500.0f;
     742        2485 :                 const float throttle_norm = throttle * 0.01f;
     743             : 
     744             :                 // steering can never be more than throttle * tan(_vector_angle_max)
     745        2485 :                 const float vector_angle_max_rad = radians(constrain_float(_vector_angle_max, 0.0f, 90.0f));
     746        2485 :                 const float steering_norm_lim = fabsf(throttle_norm * tanf(vector_angle_max_rad));
     747        2485 :                 if (fabsf(steering_norm) > steering_norm_lim) {
     748           0 :                     if (is_positive(steering_norm)) {
     749           0 :                         steering_norm = steering_norm_lim;
     750             :                     }
     751           0 :                     if (is_negative(steering_norm)) {
     752           0 :                         steering_norm = -steering_norm_lim;
     753             :                     }
     754           0 :                     limit.steer_right = true;
     755           0 :                     limit.steer_left = true;
     756             :                 }
     757             : 
     758        2485 :                 if (!is_zero(throttle_norm)) {
     759             :                     // calculate steering angle
     760        2485 :                     float steering_angle_rad = atanf(steering_norm / throttle_norm);
     761             :                     // limit steering angle to vector_angle_max
     762        2485 :                     if (fabsf(steering_angle_rad) > vector_angle_max_rad) {
     763           0 :                         steering_angle_rad = constrain_float(steering_angle_rad, -vector_angle_max_rad, vector_angle_max_rad);
     764           0 :                         limit.steer_right = true;
     765           0 :                         limit.steer_left = true;
     766             :                      }
     767             : 
     768             :                     // convert steering angle to steering output
     769        2485 :                     steering = steering_angle_rad / vector_angle_max_rad * 4500.0f;
     770             : 
     771             :                     // scale up throttle to compensate for steering angle
     772        2485 :                     const float throttle_scaler_inv = cosf(steering_angle_rad);
     773        2485 :                     if (!is_zero(throttle_scaler_inv)) {
     774        2485 :                         throttle /= throttle_scaler_inv;
     775             :                     }
     776             :                 }
     777             :             } else {
     778             :                 // scale steering down as speed increase above MOT_SPD_SCA_BASE (1 m/s default)
     779      211060 :                 if (is_positive(_speed_scale_base) && (fabsf(ground_speed) > _speed_scale_base)) {
     780      133213 :                     steering *= (_speed_scale_base / fabsf(ground_speed));
     781             :                 } else {
     782             :                     // regular steering rover at low speed so set limits to stop I-term build-up in controllers
     783       77847 :                     if (!have_skid_steering()) {
     784       53758 :                         limit.steer_left = true;
     785       53758 :                         limit.steer_right = true;
     786             :                     }
     787             :                 }
     788             :                 // reverse steering direction when backing up
     789      211060 :                 if (is_negative(ground_speed)) {
     790       23459 :                     steering *= -1.0f;
     791             :                 }
     792             :             }
     793             :         } else {
     794             :             // reverse steering direction when backing up
     795       37253 :             if (is_negative(throttle)) {
     796         456 :                 steering *= -1.0f;
     797             :             }
     798             :         }
     799      250798 :         output_throttle(SRV_Channel::k_throttle, throttle);
     800             :     } else {
     801             :         // handle disarmed case
     802      378000 :         if (_disarm_disable_pwm) {
     803           0 :             SRV_Channels::set_output_limit(SRV_Channel::k_throttle, SRV_Channel::Limit::ZERO_PWM);
     804             :         } else {
     805      378000 :             SRV_Channels::set_output_limit(SRV_Channel::k_throttle, SRV_Channel::Limit::TRIM);
     806             :         }
     807             :     }
     808             : 
     809             :     // clear and set limits based on input
     810             :     // we do this here because vectored thrust or speed scaling may have reduced steering request
     811      628798 :     set_limits_from_input(armed, steering, throttle);
     812             : 
     813             :     // constrain steering
     814      628798 :     steering = constrain_float(steering, -4500.0f, 4500.0f);
     815             : 
     816             :     // always allow steering to move
     817      628798 :     SRV_Channels::set_output_scaled(SRV_Channel::k_steering, steering);
     818      628798 : }
     819             : 
     820             : // output to skid steering channels
     821      628798 : void AP_MotorsUGV::output_skid_steering(bool armed, float steering, float throttle, float dt)
     822             : {
     823      628798 :     if (!have_skid_steering()) {
     824      581852 :         return;
     825             :     }
     826             : 
     827             :     // clear and set limits based on input
     828       87671 :     set_limits_from_input(armed, steering, throttle);
     829             : 
     830             :     // constrain steering
     831       87671 :     steering = constrain_float(steering, -4500.0f, 4500.0f);
     832             : 
     833             :     // handle simpler disarmed case
     834       87671 :     if (!armed) {
     835       40725 :         if (_disarm_disable_pwm) {
     836           0 :             SRV_Channels::set_output_limit(SRV_Channel::k_throttleLeft, SRV_Channel::Limit::ZERO_PWM);
     837           0 :             SRV_Channels::set_output_limit(SRV_Channel::k_throttleRight, SRV_Channel::Limit::ZERO_PWM);
     838             :         } else {
     839       40725 :             SRV_Channels::set_output_limit(SRV_Channel::k_throttleLeft, SRV_Channel::Limit::TRIM);
     840       40725 :             SRV_Channels::set_output_limit(SRV_Channel::k_throttleRight, SRV_Channel::Limit::TRIM);
     841             :         }
     842       40725 :         return;
     843             :     }
     844             : 
     845             :     // skid steering mixer
     846       46946 :     float steering_scaled = steering / 4500.0f; // steering scaled -1 to +1
     847       46946 :     float throttle_scaled = throttle * 0.01f;  // throttle scaled -1 to +1
     848             : 
     849             :     // sanitize values for asymmetry of thrust, mixer assumes forward thrust is always larger than reverse
     850       46946 :     const float thrust_asymmetry = MAX(_thrust_asymmetry, 1.0);
     851       46946 :     const float lower_throttle_limit = -1.0 / thrust_asymmetry;
     852             : 
     853             :     // Maximum steering is half way between upper and lower limits
     854       46946 :     const float best_steering_throttle = (1.0 + lower_throttle_limit) * 0.5;
     855             :     float steering_range;
     856       46946 :     if (throttle_scaled < best_steering_throttle) {
     857             :         // steering range is reduced as throttle will never be increased by mixer
     858        2238 :         steering_range = MAX(throttle_scaled,0.0) - lower_throttle_limit;
     859             :     } else {
     860             :         // full range available, throttle can always be lowered down to best_steering_throttle
     861       44708 :         steering_range = 1 - best_steering_throttle;
     862             :     }
     863             : 
     864             :     // apply constraints
     865       46946 :     if (steering_scaled > steering_range) {
     866           0 :         limit.steer_right = true;
     867           0 :         steering_scaled = steering_range;
     868       46946 :     } else if (steering_scaled < -steering_range) {
     869           0 :         limit.steer_left = true;
     870           0 :         steering_scaled = -steering_range;
     871             :     }
     872       46946 :     if (throttle_scaled > 1.0) {
     873           0 :         limit.throttle_upper = true;
     874           0 :         throttle_scaled = 1.0;
     875       46946 :     } else if (throttle_scaled < lower_throttle_limit) {
     876           0 :         limit.throttle_lower = true;
     877           0 :         throttle_scaled = lower_throttle_limit;
     878             :     }
     879             : 
     880             :     // All throttle or all steering will now fit, check if they will both fit together
     881       46946 :     const float max_output = throttle_scaled + fabsf(steering_scaled);
     882       46946 :     const float min_output = throttle_scaled - fabsf(steering_scaled);
     883             : 
     884             :     // check for saturation and scale back throttle and steering proportionally
     885       46946 :     const float saturation_value = MAX(max_output, min_output / lower_throttle_limit);
     886       46946 :     if (saturation_value > 1.0f) {
     887             :         // store pre-scaled values so we can set limit flags afterwards
     888         363 :         const float steering_scaled_orig = steering_scaled;
     889         363 :         const float throttle_scaled_orig = throttle_scaled;
     890             : 
     891         363 :         const float str_thr_mix = constrain_float(_steering_throttle_mix, 0.0f, 1.0f);
     892         363 :         const float fair_scaler = 1.0f / saturation_value;
     893         363 :         if (str_thr_mix >= 0.5f) {
     894             :             // prioritise steering over throttle
     895         363 :             steering_scaled *= linear_interpolate(fair_scaler, 1.0f, str_thr_mix, 0.5f, 1.0f);
     896         363 :             if (throttle_scaled >= best_steering_throttle) {
     897             :                 // constrained by upper limit
     898         363 :                 throttle_scaled = 1.0 - fabsf(steering_scaled);
     899             :             } else {
     900             :                 // constrained by lower limit
     901           0 :                 throttle_scaled = fabsf(steering_scaled) + lower_throttle_limit;
     902             :             }
     903             : 
     904             :         } else {
     905             :             // prioritise throttle over steering
     906           0 :             throttle_scaled *= linear_interpolate(fair_scaler, 1.0f, 0.5f - str_thr_mix, 0.0f, 0.5f);
     907           0 :             const float steering_sign = is_positive(steering_scaled) ? 1.0 : -1.0;
     908           0 :             if (throttle_scaled >= best_steering_throttle) {
     909             :                 // constrained by upper limit
     910           0 :                 steering_scaled = (1.0 - throttle_scaled) * steering_sign;
     911             :             } else {
     912             :                 // constrained by lower limit
     913           0 :                 steering_scaled = (throttle_scaled - lower_throttle_limit) * steering_sign;
     914             :             }
     915             :         }
     916             : 
     917             :         // update limits if either steering or throttle has been reduced
     918         363 :         if (fabsf(steering_scaled) < fabsf(steering_scaled_orig)) {
     919         363 :             limit.steer_left |= is_negative(steering_scaled_orig);
     920         363 :             limit.steer_right |= is_positive(steering_scaled_orig);
     921             :         }
     922         363 :         if (fabsf(throttle_scaled) < fabsf(throttle_scaled_orig)) {
     923         363 :             limit.throttle_lower |= is_negative(throttle_scaled_orig);
     924         363 :             limit.throttle_upper |= is_positive(throttle_scaled_orig);
     925             :         }
     926             :     }
     927             : 
     928             :     // add in throttle and steering
     929       46946 :     float motor_left = throttle_scaled + steering_scaled;
     930       46946 :     float motor_right = throttle_scaled - steering_scaled;
     931             : 
     932             :     // Apply asymmetry correction
     933       46946 :     if (is_negative(motor_right)) {
     934        4325 :         motor_right *= thrust_asymmetry;
     935             :     }
     936       46946 :     if (is_negative(motor_left)) {
     937        8694 :         motor_left *= thrust_asymmetry;
     938             :     }
     939             : 
     940             :     // send pwm value to each motor
     941       46946 :     output_throttle(SRV_Channel::k_throttleLeft, 100.0f * motor_left, dt);
     942       46946 :     output_throttle(SRV_Channel::k_throttleRight, 100.0f * motor_right, dt);
     943             : }
     944             : 
     945             : // output for omni frames
     946      628798 : void AP_MotorsUGV::output_omni(bool armed, float steering, float throttle, float lateral)
     947             : {
     948             :     // exit immediately if the vehicle is not omni
     949      628798 :     if (!is_omni()) {
     950      617905 :         return;
     951             :     }
     952             : 
     953       10893 :     if (armed) {
     954             :         // clear and set limits based on input
     955        8746 :         set_limits_from_input(armed, steering, throttle);
     956             : 
     957             :         // constrain steering
     958        8746 :         steering = constrain_float(steering, -4500.0f, 4500.0f);
     959             : 
     960             :         // scale throttle, steering and lateral inputs to -1 to 1
     961        8746 :         const float scaled_throttle = throttle * 0.01f;
     962        8746 :         const float scaled_steering = steering / 4500.0f;
     963        8746 :         const float scaled_lateral = lateral * 0.01f;
     964             : 
     965             :         // sized to the maximum rather than the runtime motor count: a
     966             :         // variable-length array is non-standard C++, and setup_omni() only
     967             :         // ever sets _motors_num to 3 or 4
     968        8746 :         float thr_str_ltr_out[AP_MOTORS_NUM_MOTORS_MAX] {};
     969        8746 :         float thr_str_ltr_max = 1;
     970       34984 :         for (uint8_t i=0; i<_motors_num; i++) {
     971             :             // Each motor outputs throttle + steering + lateral
     972       26238 :             thr_str_ltr_out[i] = (scaled_throttle * _throttle_factor[i]) +
     973       26238 :                               (scaled_steering * _steering_factor[i]) +
     974       26238 :                               (scaled_lateral * _lateral_factor[i]);
     975             :             // record the largest output above 1
     976       26238 :             if (fabsf(thr_str_ltr_out[i]) > thr_str_ltr_max) {
     977           0 :                 thr_str_ltr_max = fabsf(thr_str_ltr_out[i]);
     978             :             }
     979             :         }
     980             :         // Scale all outputs back evenly such that the largest fits
     981        8746 :         const float output_scale = 1 / thr_str_ltr_max;
     982       34984 :         for (uint8_t i=0; i<_motors_num; i++) {
     983             :             // send output for each motor
     984       26238 :             output_throttle(SRV_Channels::get_motor_function(i), thr_str_ltr_out[i] * 100.0f * output_scale);
     985             :         }
     986        8746 :         if (output_scale < 1.0) {
     987             :             // can't tell which command resulted in the scale back, so limit all
     988           0 :             limit.steer_left = true;
     989           0 :             limit.steer_right = true;
     990           0 :             limit.throttle_lower = true;
     991           0 :             limit.throttle_upper = true;
     992             :         }
     993             :     } else {
     994             :         // handle disarmed case
     995        2147 :         if (_disarm_disable_pwm) {
     996           0 :             for (uint8_t i=0; i<_motors_num; i++) {
     997           0 :                 SRV_Channels::set_output_limit(SRV_Channels::get_motor_function(i), SRV_Channel::Limit::ZERO_PWM);
     998             :             }
     999             :         } else {
    1000        8588 :             for (uint8_t i=0; i<_motors_num; i++) {
    1001        6441 :                 SRV_Channels::set_output_limit(SRV_Channels::get_motor_function(i), SRV_Channel::Limit::TRIM);
    1002             :             }
    1003             :         }
    1004             :     }
    1005             : }
    1006             : 
    1007             : // return power_limit as a number from 0 ~ 1 in the range throttle_min to throttle_max
    1008             : #if AP_BATTERY_WATT_MAX_ENABLED
    1009      628798 : float AP_MotorsUGV::get_power_limit_max_throttle(float dt)
    1010             : {
    1011      628798 :     AP_BattMonitor &battery = AP::battery();
    1012             : 
    1013             :     float _batt_current;
    1014      628798 :     const float watt_max = battery.get_watt_max();
    1015      628798 :     const float _batt_voltage = battery.voltage();
    1016             :     
    1017      628798 :     if (watt_max <= 0 ||  // return maximum if power limiting is disabled
    1018           0 :         !hal.util->get_soft_armed() ||   // remove throttle limit if disarmed
    1019      628798 :         !battery.current_amps(_batt_current) || // no current monitoring is available
    1020           0 :         is_zero(_batt_voltage)) {  // no voltage monitoring is available
    1021      628798 :         _throttle_limit = 1.0f;
    1022      628798 :         return 1.0f;
    1023             :     }
    1024             : 
    1025           0 :     const float power = _batt_voltage * _batt_current;
    1026           0 :     const float power_ratio = power / watt_max;
    1027             : 
    1028           0 :     _throttle_limit += (dt / (dt + _batt_power_time_constant)) * (1.0f - power_ratio);
    1029             : 
    1030             :     // ensure throttle limit is within min and max throttle limits
    1031           0 :     _throttle_limit = constrain_float(_throttle_limit, _throttle_min * 0.01f, _throttle_max * 0.01f);
    1032             : 
    1033           0 :     return _throttle_limit;
    1034             : }
    1035             : #endif
    1036             : 
    1037             : // output throttle value to main throttle channel, left throttle or right throttle.  throttle should be scaled from -100 to 100
    1038      370928 : void AP_MotorsUGV::output_throttle(SRV_Channel::Function function, float throttle, float dt)
    1039             : {
    1040             :     // sanity check servo function
    1041      370928 :     if (function != SRV_Channel::k_throttle && function != SRV_Channel::k_throttleLeft && function != SRV_Channel::k_throttleRight && function != SRV_Channel::k_motor1 && function != SRV_Channel::k_motor2 && function != SRV_Channel::k_motor3 && function!= SRV_Channel::k_motor4) {
    1042           0 :         return;
    1043             :     }
    1044             : 
    1045             :     // constrain and scale output
    1046      370928 :     throttle = get_scaled_throttle(throttle);
    1047             : 
    1048             :     // apply rate control
    1049      370928 :     throttle = get_rate_controlled_throttle(function, throttle, dt);
    1050             : 
    1051             :     // set relay if necessary
    1052             : #if AP_RELAY_ENABLED
    1053      370928 :     AP_Relay*relay = AP::relay();
    1054      370928 :     if ((_pwm_type == PWMType::BRUSHED_WITH_RELAY) && (relay != nullptr)) {
    1055             : 
    1056             :         // find the output channel, if not found return
    1057           0 :         const SRV_Channel *out_chan = SRV_Channels::get_channel_for(function);
    1058           0 :         if (out_chan == nullptr) {
    1059           0 :             return;
    1060             :         }
    1061           0 :         const int8_t reverse_multiplier = out_chan->get_reversed() ? -1 : 1;
    1062           0 :         bool relay_high = is_negative(reverse_multiplier * throttle);
    1063             : 
    1064             :         AP_Relay_Params::FUNCTION relay_function;
    1065           0 :         switch (function) {
    1066           0 :             case SRV_Channel::k_throttle:
    1067             :             case SRV_Channel::k_throttleLeft:
    1068             :             case SRV_Channel::k_motor1:
    1069             :             default:
    1070           0 :                 relay_function = AP_Relay_Params::FUNCTION::BRUSHED_REVERSE_1;
    1071           0 :                 break;
    1072           0 :             case SRV_Channel::k_throttleRight:
    1073             :             case SRV_Channel::k_motor2:
    1074           0 :                 relay_function = AP_Relay_Params::FUNCTION::BRUSHED_REVERSE_2;
    1075           0 :                 break;
    1076           0 :             case SRV_Channel::k_motor3:
    1077           0 :                 relay_function = AP_Relay_Params::FUNCTION::BRUSHED_REVERSE_3;
    1078           0 :                 break;
    1079           0 :             case SRV_Channel::k_motor4:
    1080           0 :                 relay_function = AP_Relay_Params::FUNCTION::BRUSHED_REVERSE_4;
    1081           0 :                 break;
    1082             :         }
    1083           0 :         relay->set(relay_function, relay_high);
    1084             : 
    1085             :         // invert the output to always have positive value calculated by calc_pwm
    1086           0 :         throttle = reverse_multiplier * fabsf(throttle);
    1087             :     }
    1088             : #endif  // AP_RELAY_ENABLED
    1089             : 
    1090      370928 :     if (_reverse_delay > 0) {
    1091           0 :         switch (function) {
    1092           0 :         case SRV_Channel::k_throttle:
    1093           0 :             rev_delay_throttle.output(function, throttle, _reverse_delay);
    1094           0 :             return;
    1095           0 :         case SRV_Channel::k_throttleLeft:
    1096           0 :             rev_delay_throttleLeft.output(function, throttle * 10, _reverse_delay);
    1097           0 :             return;
    1098           0 :         case SRV_Channel::k_throttleRight:
    1099           0 :             rev_delay_throttleRight.output(function, throttle * 10, _reverse_delay);
    1100           0 :             return;
    1101           0 :         default:
    1102             :             // fall through to other non-delayed outputs
    1103           0 :             break;
    1104             :         }
    1105             :     }
    1106             : 
    1107             :     // output to servo channel
    1108      370928 :     switch (function) {
    1109      277036 :         case SRV_Channel::k_throttle:
    1110             :         case SRV_Channel::k_motor1:
    1111             :         case SRV_Channel::k_motor2:
    1112             :         case SRV_Channel::k_motor3:
    1113             :         case SRV_Channel::k_motor4:
    1114      277036 :             SRV_Channels::set_output_scaled(function,  throttle);
    1115      277036 :             break;
    1116       93892 :         case SRV_Channel::k_throttleLeft:
    1117             :         case SRV_Channel::k_throttleRight:
    1118       93892 :             SRV_Channels::set_output_scaled(function,  throttle * 10.0f);
    1119       93892 :             break;
    1120           0 :         default:
    1121             :             // do nothing
    1122           0 :             break;
    1123             :     }
    1124             : }
    1125             : 
    1126             : // output for sailboat's sails
    1127      628798 : void AP_MotorsUGV::output_sail()
    1128             : {
    1129      628798 :     if (!has_sail()) {
    1130      604225 :         return;
    1131             :     }
    1132             : 
    1133       24573 :     SRV_Channels::set_output_scaled(SRV_Channel::k_mainsail_sheet, _mainsail);
    1134       24573 :     SRV_Channels::set_output_scaled(SRV_Channel::k_wingsail_elevator, _wingsail);
    1135       24573 :     SRV_Channels::set_output_scaled(SRV_Channel::k_mast_rotation, _mast_rotation);
    1136             : }
    1137             : 
    1138             : // slew limit throttle for one iteration
    1139      628798 : void AP_MotorsUGV::slew_limit_throttle(float dt)
    1140             : {
    1141      628798 :     const float throttle_orig = _throttle;
    1142      628798 :     _throttle = get_slew_limited_throttle(_throttle, dt);
    1143      628798 :     if (throttle_orig > _throttle) {
    1144         139 :         limit.throttle_upper = true;
    1145      628659 :     } else if (throttle_orig < _throttle) {
    1146         243 :         limit.throttle_lower = true;
    1147             :     }
    1148      628798 :     _throttle_prev = _throttle;
    1149      628798 : }
    1150             : 
    1151             : // apply power limiting to throttle for one iteration
    1152             : #if AP_BATTERY_WATT_MAX_ENABLED
    1153      628798 : void AP_MotorsUGV::power_limit_throttle(float dt)
    1154             : {
    1155      628798 :     const float power_limit_max = get_power_limit_max_throttle(dt);
    1156             :     
    1157      628798 :     const float throttle_max = 100.0f * power_limit_max;
    1158      628798 :     const float throttle_min = -100.0f * power_limit_max;
    1159             :     
    1160      628798 :     if (_throttle > throttle_max) {
    1161           0 :         _throttle = throttle_max;
    1162           0 :         limit.throttle_upper = true;
    1163      628798 :     } else if (_throttle < throttle_min) {
    1164           0 :         _throttle = throttle_min;
    1165           0 :         limit.throttle_lower = true;
    1166             :     }
    1167      628798 : }
    1168             : #endif
    1169             : 
    1170             : // set limits based on steering and throttle input
    1171      725215 : void AP_MotorsUGV::set_limits_from_input(bool armed, float steering, float throttle)
    1172             : {
    1173             :     // set limits based on inputs
    1174      725215 :     limit.steer_left |= !armed || (steering <= -4500.0f);
    1175      725215 :     limit.steer_right |= !armed || (steering >= 4500.0f);
    1176      725215 :     limit.throttle_lower |= !armed || (throttle <= -_throttle_max);
    1177      725215 :     limit.throttle_upper |= !armed || (throttle >= _throttle_max);
    1178      725215 : }
    1179             : 
    1180             : // scale a throttle using the _throttle_min and _thrust_curve_expo parameters.  throttle should be in the range -100 to +100
    1181      370928 : float AP_MotorsUGV::get_scaled_throttle(float throttle) const
    1182             : {
    1183             :     // exit immediately if throttle is zero
    1184      370928 :     if (is_zero(throttle)) {
    1185       65039 :         return throttle;
    1186             :     }
    1187             : 
    1188             :     // scale using throttle_min
    1189      305889 :     if (_throttle_min > 0) {
    1190           0 :         if (is_negative(throttle)) {
    1191           0 :             throttle = -_throttle_min + (throttle * ((100.0f - _throttle_min) * 0.01f));
    1192             :         } else {
    1193           0 :             throttle = _throttle_min + (throttle * ((100.0f - _throttle_min) * 0.01f));
    1194             :         }
    1195             :     }
    1196             : 
    1197             :     // skip further scaling if thrust curve disabled or invalid
    1198      305889 :     if (is_zero(_thrust_curve_expo) || (_thrust_curve_expo > 1.0f) || (_thrust_curve_expo < -1.0f)) {
    1199      305889 :         return throttle;
    1200             :     }
    1201             : 
    1202             :     // calculate scaler
    1203           0 :     const float sign = (throttle < 0.0f) ? -1.0f : 1.0f;
    1204           0 :     const float throttle_pct = constrain_float(throttle, -100.0f, 100.0f) * 0.01f;
    1205           0 :     return 100.0f * sign * ((_thrust_curve_expo - 1.0f) + safe_sqrt((1.0f - _thrust_curve_expo) * (1.0f - _thrust_curve_expo) + 4.0f * _thrust_curve_expo * fabsf(throttle_pct))) / (2.0f * _thrust_curve_expo);
    1206             : }
    1207             : 
    1208             : // use rate controller to achieve desired throttle
    1209      370928 : float AP_MotorsUGV::get_rate_controlled_throttle(SRV_Channel::Function function, float throttle, float dt)
    1210             : {
    1211             :     // require non-zero dt
    1212      370928 :     if (!is_positive(dt)) {
    1213      277036 :         return throttle;
    1214             :     }
    1215             : 
    1216             :     // attempt to rate control left throttle
    1217       93892 :     if ((function == SRV_Channel::k_throttleLeft) && _rate_controller.enabled(0)) {
    1218           0 :         return _rate_controller.get_rate_controlled_throttle(0, throttle, dt);
    1219             :     }
    1220             : 
    1221             :     // rate control right throttle
    1222       93892 :     if ((function == SRV_Channel::k_throttleRight) && _rate_controller.enabled(1)) {
    1223           0 :         return _rate_controller.get_rate_controlled_throttle(1, throttle, dt);
    1224             :     }
    1225             : 
    1226             :     // return throttle unchanged
    1227       93892 :     return throttle;
    1228             : }
    1229             : 
    1230             : // return true if motors are moving
    1231       12542 : bool AP_MotorsUGV::active() const
    1232             : {
    1233             :     // if soft disarmed, motors not active
    1234       12542 :     if (!hal.util->get_soft_armed()) {
    1235        7518 :         return false;
    1236             :     }
    1237             : 
    1238             :     // check throttle is active
    1239        5024 :     if (!is_zero(get_throttle())) {
    1240        3802 :         return true;
    1241             :     }
    1242             : 
    1243             :     // skid-steering vehicles active when steering
    1244        1222 :     if (have_skid_steering() && !is_zero(get_steering())) {
    1245          91 :         return true;
    1246             :     }
    1247             : 
    1248        1131 :     return false;
    1249             : }
    1250             : 
    1251             : // returns true if the configured PWM type is digital and should have fixed endpoints
    1252         283 : bool AP_MotorsUGV::is_digital_pwm_type() const
    1253             : {
    1254         283 :     switch (_pwm_type) {
    1255           0 :     case PWMType::DSHOT150:
    1256             :     case PWMType::DSHOT300:
    1257             :     case PWMType::DSHOT600:
    1258             :     case PWMType::DSHOT1200:
    1259           0 :         return true;
    1260         283 :     case PWMType::NORMAL:
    1261             :     case PWMType::ONESHOT:
    1262             :     case PWMType::ONESHOT125:
    1263             :     case PWMType::BRUSHED_WITH_RELAY:
    1264             :     case PWMType::BRUSHED_BIPOLAR:
    1265         283 :         break;
    1266             :     }
    1267         283 :     return false;
    1268             : }
    1269             : 
    1270             : /*
    1271             :   handle delay on reversal for a throttle
    1272             :  */
    1273           0 : void AP_MotorsUGV::ReverseThrottle::output(SRV_Channel::Function function, float throttle, float delay)
    1274             : {
    1275           0 :     const uint32_t now_ms = AP_HAL::millis();
    1276           0 :     if (is_zero(throttle)) {
    1277             :         // pass through, no change, don't update the last throttle
    1278           0 :     } else if (throttle * last_throttle < 0 &&
    1279           0 :         now_ms - last_output_ms < delay*1000) {
    1280             :         // sign change, add pause
    1281           0 :         throttle = 0;
    1282             :     } else {
    1283           0 :         last_output_ms = now_ms;
    1284           0 :         last_throttle = throttle;
    1285             :     }
    1286           0 :     SRV_Channels::set_output_scaled(function, throttle);
    1287           0 : }
    1288             : 
    1289             : namespace AP {
    1290         566 :     AP_MotorsUGV *motors_ugv()
    1291             :     {
    1292         566 :         return AP_MotorsUGV::get_singleton();
    1293             :     }
    1294             : }
    1295             : 

Generated by: LCOV version 1.14