LCOV - code coverage report
Current view: top level - AP_Motors - AP_MotorsHeli.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 150 228 65.8 %
Date: 2026-09-09 11:02:08 Functions: 19 23 82.6 %

          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 <stdlib.h>
      17             : #include <AP_HAL/AP_HAL.h>
      18             : #include "AP_MotorsHeli.h"
      19             : #include <GCS_MAVLink/GCS.h>
      20             : #include <AP_Logger/AP_Logger.h>
      21             : #include <AC_Autorotation/RSC_Autorotation.h>
      22             : 
      23             : extern const AP_HAL::HAL& hal;
      24             : 
      25             : const AP_Param::GroupInfo AP_MotorsHeli::var_info[] = {
      26             : 
      27             :     // 1 was ROL_MAX which has been replaced by CYC_MAX
      28             : 
      29             :     // 2 was PIT_MAX which has been replaced by CYC_MAX
      30             : 
      31             :     // @Param: COL_MIN
      32             :     // @DisplayName: Minimum Collective Pitch
      33             :     // @Description: Lowest possible servo position in PWM microseconds for the swashplate
      34             :     // @Range: 1000 2000
      35             :     // @Units: PWM
      36             :     // @Increment: 1
      37             :     // @User: Standard
      38             :     AP_GROUPINFO("COL_MIN", 3, AP_MotorsHeli, _collective_min, AP_MOTORS_HELI_COLLECTIVE_MIN),
      39             : 
      40             :     // @Param: COL_MAX
      41             :     // @DisplayName: Maximum Collective Pitch
      42             :     // @Description: Highest possible servo position in PWM microseconds for the swashplate
      43             :     // @Range: 1000 2000
      44             :     // @Units: PWM
      45             :     // @Increment: 1
      46             :     // @User: Standard
      47             :     AP_GROUPINFO("COL_MAX", 4, AP_MotorsHeli, _collective_max, AP_MOTORS_HELI_COLLECTIVE_MAX),
      48             : 
      49             :     // index 5 was COL_MID. Do not use this index in the future.
      50             : 
      51             :     // @Param: SV_MAN
      52             :     // @DisplayName: Manual Servo Mode
      53             :     // @Description: Manual servo override for swash set-up. Must be 0 (Disabled) for flight!
      54             :     // @Values: 0:Disabled,1:Passthrough,2:Max collective,3:Zero thrust collective,4:Min collective
      55             :     // @User: Standard
      56             :     AP_GROUPINFO("SV_MAN",  6, AP_MotorsHeli, _servo_mode, SERVO_CONTROL_MODE_AUTOMATED),
      57             : 
      58             :     // indices 7 and 8 were RSC parameters which were moved to RSC library. Do not use these indices in the future.
      59             : 
      60             :     // index 9 was LAND_COL_MIN. Do not use this index in the future.
      61             : 
      62             :     // indices 10-13 were RSC parameters which were moved to RSC library. Do not use these indices in the future.
      63             : 
      64             :     // index 14 was RSC_POWER_LOW. Do not use this index in the future.
      65             : 
      66             :     // index 15 was RSC_POWER_HIGH. Do not use this index in the future.
      67             : 
      68             :     // @Param: CYC_MAX
      69             :     // @DisplayName: Maximum Cyclic Pitch Angle
      70             :     // @Description: Maximum cyclic pitch angle of the swash plate.  There are no units to this parameter.  This should be adjusted to get the desired cyclic blade pitch for the pitch and roll axes.  Typically this should be 6-7 deg (measured blade pitch angle difference between stick centered and stick max deflection.
      71             :     // @Range: 0 4500
      72             :     // @Increment: 100
      73             :     // @User: Standard
      74             :     AP_GROUPINFO("CYC_MAX", 16, AP_MotorsHeli, _cyclic_max, AP_MOTORS_HELI_SWASH_CYCLIC_MAX),
      75             : 
      76             :     // @Param: SV_TEST
      77             :     // @DisplayName: Boot-up Servo Test Cycles
      78             :     // @Description: Number of cycles to run servo test on boot-up
      79             :     // @Range: 0 10
      80             :     // @Increment: 1
      81             :     // @User: Standard
      82             :     AP_GROUPINFO("SV_TEST",  17, AP_MotorsHeli, _servo_test, 0),
      83             : 
      84             :     // index 18 was RSC_POWER_NEGC. Do not use this index in the future.
      85             : 
      86             :     // index 19 was RSC_SLEWRATE and was moved to RSC library. Do not use this index in the future.
      87             : 
      88             :     // indices 20 to 24 was throttle curve. Do not use this index in the future.
      89             : 
      90             :     // @Group: RSC_
      91             :     // @Path: AP_MotorsHeli_RSC.cpp
      92             :     AP_SUBGROUPINFO(_main_rotor, "RSC_", 25, AP_MotorsHeli, AP_MotorsHeli_RSC),
      93             : 
      94             :     // @Param: COL_HOVER
      95             :     // @DisplayName: Collective Hover Value
      96             :     // @Description: Collective needed to hover expressed as a number from 0 to 1 where 0 is H_COL_MIN and 1 is H_COL_MAX
      97             :     // @Range: 0.3 0.8
      98             :     // @User: Advanced
      99             :     AP_GROUPINFO("COL_HOVER", 26, AP_MotorsHeli, _collective_hover, AP_MOTORS_HELI_COLLECTIVE_HOVER_DEFAULT),
     100             : 
     101             :     // @Param: HOVER_LEARN
     102             :     // @DisplayName: Hover Value Learning
     103             :     // @Description: Enable/Disable automatic learning of hover collective
     104             :     // @Values: 0:Disabled, 1:Learn, 2:Learn and Save
     105             :     // @User: Advanced
     106             :     AP_GROUPINFO("HOVER_LEARN", 27, AP_MotorsHeli, _collective_hover_learn, HOVER_LEARN_AND_SAVE),
     107             : 
     108             :     // @Param: OPTIONS
     109             :     // @DisplayName: Heli_Options
     110             :     // @Description: Bitmask of heli options.  Bit 0 changes how the pitch, roll, and yaw axis integrator term is managed for low speed and takeoff/landing. In AC 4.0 and earlier, scheme uses a leaky integrator for ground speeds less than 5 m/s and won't let the steady state integrator build above ILMI. The integrator is allowed to build to the ILMI value when it is landed.  The other integrator management scheme bases integrator limiting on takeoff and landing.  Whenever the aircraft is landed the integrator is set to zero.  When the aicraft is airborne, the integrator is only limited by IMAX. 
     111             :     // @Bitmask: 0:Use Leaky I
     112             :     // @User: Standard
     113             :     AP_GROUPINFO("OPTIONS", 28, AP_MotorsHeli, _heli_options, (uint8_t)HeliOption::USE_LEAKY_I),
     114             : 
     115             :     // @Param: COL_ANG_MIN
     116             :     // @DisplayName: Collective Blade Pitch Angle Minimum
     117             :     // @Description: Minimum collective blade pitch angle in deg that corresponds to the PWM set for minimum collective pitch (H_COL_MIN).
     118             :     // @Range: -20 0
     119             :     // @Units: deg
     120             :     // @Increment: 0.1
     121             :     // @User: Standard
     122             :     AP_GROUPINFO("COL_ANG_MIN", 29, AP_MotorsHeli, _collective_min_deg, AP_MOTORS_HELI_COLLECTIVE_MIN_DEG),
     123             : 
     124             :     // @Param: COL_ANG_MAX
     125             :     // @DisplayName: Collective Blade Pitch Angle Maximum
     126             :     // @Description: Maximum collective blade pitch angle in deg that corresponds to the PWM set for maximum collective pitch (H_COL_MAX).
     127             :     // @Range: 5 20
     128             :     // @Units: deg
     129             :     // @Increment: 0.1
     130             :     // @User: Standard
     131             :     AP_GROUPINFO("COL_ANG_MAX", 30, AP_MotorsHeli, _collective_max_deg, AP_MOTORS_HELI_COLLECTIVE_MAX_DEG),
     132             : 
     133             :     // @Param: COL_ZERO_THRST
     134             :     // @DisplayName: Collective Blade Pitch at Zero Thrust
     135             :     // @Description: Collective blade pitch angle at zero thrust in degrees. For symetric airfoil blades this value is zero deg. For chambered airfoil blades this value is typically negative.
     136             :     // @Range: -5 0
     137             :     // @Units: deg
     138             :     // @Increment: 0.1
     139             :     // @User: Standard
     140             :     AP_GROUPINFO("COL_ZERO_THRST", 31, AP_MotorsHeli, _collective_zero_thrust_deg, 0.0f),
     141             : 
     142             :     // @Param: COL_LAND_MIN
     143             :     // @DisplayName: Collective Blade Pitch Minimum when Landed
     144             :     // @Description: Minimum collective blade pitch angle when landed in degrees for non-manual collective modes (i.e. modes that use altitude hold).
     145             :     // @Range: -5 0
     146             :     // @Units: deg
     147             :     // @Increment: 0.1
     148             :     // @User: Standard
     149             :     AP_GROUPINFO("COL_LAND_MIN", 32, AP_MotorsHeli, _collective_land_min_deg, AP_MOTORS_HELI_COLLECTIVE_LAND_MIN),
     150             : 
     151             :     // @Group: RSC_AROT_
     152             :     // @Path: ../AC_Autorotation/RSC_Autorotation.cpp
     153             :     AP_SUBGROUPINFO(_main_rotor.autorotation, "RSC_AROT_", 33, AP_MotorsHeli, RSC_Autorotation),
     154             : 
     155             :     AP_GROUPEND
     156             : };
     157             : 
     158             : //
     159             : // public methods
     160             : //
     161             : 
     162             : // init
     163          83 : void AP_MotorsHeli::init(motor_frame_class frame_class, motor_frame_type frame_type)
     164             : {
     165             :     // remember frame class and type
     166          83 :     _frame_type = frame_type;
     167          83 :     _frame_class = frame_class;
     168             : 
     169             :     // set update rate
     170          83 :     set_update_rate(_speed_hz);
     171             : 
     172             :     // load boot-up servo test cycles into counter to be consumed
     173          83 :     _servo_test_cycle_counter = _servo_test;
     174             : 
     175             :     // ensure inputs are not passed through to servos on start-up
     176          83 :     _servo_mode.set(SERVO_CONTROL_MODE_AUTOMATED);
     177             : 
     178             :     // initialise radio passthrough for collective to middle
     179          83 :     _throttle_radio_passthrough = 0.5f;
     180             : 
     181             :     // initialise Servo/PWM ranges and endpoints
     182          83 :     init_outputs();
     183             : 
     184             :     // calculate all scalars
     185          83 :     calculate_scalars();
     186             : 
     187             :     // set flag to true so targets are initialized once aircraft is armed for first time
     188          83 :     _heliflags.init_targets_on_arming = true;
     189             : 
     190          83 :     _mav_type = MAV_TYPE_HELICOPTER;
     191          83 : }
     192             : 
     193             : // calculate_scalars - recalculates various scalers used.
     194     3234062 : void AP_MotorsHeli::calculate_armed_scalars()
     195             : {
     196     3234062 :     _main_rotor.configure_armed();
     197     3234062 : }
     198             : 
     199             : // output_min - sets servos to neutral point with motors stopped
     200         140 : void AP_MotorsHeli::output_min()
     201             : {
     202             :     // move swash to mid
     203         140 :     move_actuators(0.0f,0.0f,0.5f,0.0f);
     204             :     
     205             :     // _spool_state is enforced to SHUT_DOWN when _spool_desired is SHUT_DOWN.
     206         140 :     set_desired_spool_state(DesiredSpoolState::SHUT_DOWN);
     207         140 :     _spool_state = update_spool_state(_spool_desired);
     208         140 :     update_motor_control(_spool_desired);
     209             : 
     210         140 :     output_to_motors();
     211             : 
     212             :     // override limits flags
     213         140 :     limit.set_rpy(true);
     214         140 :     limit.throttle_lower = true;
     215         140 :     limit.throttle_upper = false;
     216         140 : }
     217             : 
     218             : // output - sends commands to the servos
     219     3233979 : void AP_MotorsHeli::output()
     220             : {
     221             :     // update throttle filter
     222     3233979 :     update_throttle_filter();
     223             : 
     224             :     // run spool logic
     225     3233979 :     output_logic();
     226             : 
     227     3233979 :     if (armed()) {
     228             :         // block servo_test from happening at disarm
     229     2321602 :         _servo_test_cycle_counter = 0;
     230     2321602 :         output_armed_stabilizing();
     231             :     } else {
     232      912377 :         output_disarmed();
     233             :     }
     234             : 
     235             :     // helicopters always run stabilizing flight controls
     236     3233979 :     move_actuators(_roll_in, _pitch_in, get_throttle(), _yaw_in);
     237             : 
     238             :     // motor control must be updated after move_actuators because DDFP Tail Rotor output is based on move actuators()
     239     3233979 :     update_motor_control(_spool_desired);
     240             : 
     241     3233979 :     update_turbine_start();
     242             : 
     243     3233979 :     output_to_motors();
     244             : 
     245     3233979 : };
     246             : 
     247             : // sends commands to the motors
     248     2321602 : void AP_MotorsHeli::output_armed_stabilizing()
     249             : {
     250     2321602 :     calculate_armed_scalars();
     251             : 
     252             :     // if manual override active after arming, deactivate it and reinitialize servos
     253     2321602 :     if (_servo_mode != SERVO_CONTROL_MODE_AUTOMATED) {
     254           0 :         reset_flight_controls();
     255             :     }
     256     2321602 : }
     257             : 
     258             : // output_disarmed - sends commands to the motors
     259      912377 : void AP_MotorsHeli::output_disarmed()
     260             : {
     261      912377 :     if (_servo_test_cycle_counter > 0){
     262             :         // set servo_test_flag
     263           0 :         _heliflags.servo_test_running = true;
     264             :         // perform boot-up servo test cycle if enabled
     265           0 :         servo_test();
     266             :     } else {
     267             :         // set servo_test flag
     268      912377 :         _heliflags.servo_test_running = false;
     269             :         // manual override (i.e. when setting up swash)
     270      912377 :         switch (_servo_mode) {
     271           0 :             case SERVO_CONTROL_MODE_MANUAL_PASSTHROUGH:
     272             :                 // pass pilot commands straight through to swash
     273           0 :                 _roll_in = _roll_radio_passthrough;
     274           0 :                 _pitch_in = _pitch_radio_passthrough;
     275           0 :                 _throttle_filter.reset(_throttle_radio_passthrough);
     276           0 :                 _yaw_in = _yaw_radio_passthrough;
     277           0 :                 break;
     278           0 :             case SERVO_CONTROL_MODE_MANUAL_CENTER:
     279             :                 // fixate mid collective
     280           0 :                 _roll_in = 0.0f;
     281           0 :                 _pitch_in = 0.0f;
     282           0 :                 _throttle_filter.reset(_collective_zero_thrust_pct);
     283           0 :                 _yaw_in = 0.0f;
     284           0 :                 break;
     285           0 :             case SERVO_CONTROL_MODE_MANUAL_MAX:
     286             :                 // fixate max collective
     287           0 :                 _roll_in = 0.0f;
     288           0 :                 _pitch_in = 0.0f;
     289           0 :                 _throttle_filter.reset(1.0f);
     290           0 :                 if (_frame_class == MOTOR_FRAME_HELI_DUAL ||
     291           0 :                     _frame_class == MOTOR_FRAME_HELI_QUAD) {
     292           0 :                     _yaw_in = 0;
     293             :                 } else {
     294           0 :                     _yaw_in = 1;
     295             :                 }
     296           0 :                 break;
     297           0 :             case SERVO_CONTROL_MODE_MANUAL_MIN:
     298             :                 // fixate min collective
     299           0 :                 _roll_in = 0.0f;
     300           0 :                 _pitch_in = 0.0f;
     301           0 :                 _throttle_filter.reset(0.0f);
     302           0 :                 if (_frame_class == MOTOR_FRAME_HELI_DUAL ||
     303           0 :                     _frame_class == MOTOR_FRAME_HELI_QUAD) {
     304           0 :                     _yaw_in = 0;
     305             :                 } else {
     306           0 :                     _yaw_in = -1;
     307             :                 }
     308           0 :                 break;
     309           0 :             case SERVO_CONTROL_MODE_MANUAL_OSCILLATE:
     310             :                 // use servo_test function from child classes
     311           0 :                 servo_test();
     312           0 :                 break;
     313      912377 :             default:
     314             :                 // no manual override
     315      912377 :                 break;
     316             :         }
     317             :     }
     318             : 
     319             :     // ensure swash servo endpoints haven't been moved
     320      912377 :     init_outputs();
     321             : 
     322             :     // continuously recalculate scalars to allow setup
     323      912377 :     calculate_scalars();
     324      912377 : }
     325             : 
     326             : // set_desired_spool_state - set desired spool state with safety constraints
     327     3194280 : void AP_MotorsHeli::set_desired_spool_state(DesiredSpoolState desired)
     328             : {
     329             :     // Safety constraint: disarmed vehicles must be shut down
     330     3194280 :     if (!armed()) {
     331      895860 :         _spool_desired = DesiredSpoolState::SHUT_DOWN;
     332      895860 :         return;
     333             :     }
     334             : 
     335             :     // Safety constraint: without interlock, limit to GROUND_IDLE
     336             :     // (allows swash movement for autorotation, but rotor cannot spin up)
     337     2298420 :     if (!get_interlock()) {
     338      153780 :         switch (desired) {
     339           0 :             case DesiredSpoolState::SHUT_DOWN:
     340           0 :                 _spool_desired = DesiredSpoolState::SHUT_DOWN;
     341           0 :                 break;
     342      153780 :             case DesiredSpoolState::GROUND_IDLE:
     343             :             case DesiredSpoolState::THROTTLE_UNLIMITED:
     344      153780 :                 _spool_desired = DesiredSpoolState::GROUND_IDLE;
     345      153780 :                 break;
     346             :         }
     347      153780 :         return;
     348             :     }
     349             : 
     350             :     // No safety constraints active - accept requested state
     351     2144640 :     _spool_desired = desired;
     352             : }
     353             : 
     354             : // run spool logic
     355     3233979 : void AP_MotorsHeli::output_logic()
     356             : {
     357             :     // force desired and current spool mode if disarmed and armed with interlock enabled
     358     3233979 :     if (armed()) {
     359     2321602 :         if (!get_interlock()) {
     360      165505 :             _spool_desired = DesiredSpoolState::GROUND_IDLE;
     361             :         } else {
     362     2156097 :             _heliflags.init_targets_on_arming = false;
     363             :         }
     364             :     } else {
     365      912377 :         _heliflags.init_targets_on_arming = true;
     366      912377 :         _spool_desired = DesiredSpoolState::SHUT_DOWN; // RSC will enforce _spool_state to SHUT_DOWN when spool_desired is SHUT_DOWN
     367             :     }
     368             : 
     369             :     // send desired spool state update to Heli RSC and update outputs
     370             :     // the Heli RSC will return the current spool state which is used to update _spool_state variable
     371             :     // _spool_state is enforced to SHUT_DOWN when _spool_desired is SHUT_DOWN.
     372     3233979 :     _spool_state = update_spool_state(_spool_desired);
     373             : 
     374             :     // Always reset the collective limit flags, they get set in move_actuators() if collective reaches a limit
     375     3233979 :     limit.set_throttle(false);
     376             : 
     377     3233979 :     switch (_spool_state) {
     378      912377 :         case SpoolState::SHUT_DOWN:
     379             :             // Motors should be stationary.
     380             :             // Servos set to their trim values or in a test condition.
     381             : 
     382             :             // set limits flags
     383      912377 :             if (!using_leaky_integrator()) {
     384           0 :                 limit.set_rpy(true);
     385             :             } else {
     386      912377 :                 limit.set_rpy(false);
     387             :             }
     388      912377 :             break;
     389             : 
     390       97503 :         case SpoolState::GROUND_IDLE: {
     391             :             // Motors should be stationary or at ground idle.
     392             :             // set limits flags
     393       97503 :             if (_heliflags.land_complete && !using_leaky_integrator()) {
     394           0 :                 limit.set_rpy(true);
     395             :             } else {
     396       97503 :                 limit.set_rpy(false);
     397             :             }
     398       97503 :             break;
     399             :         }
     400      158332 :         case SpoolState::SPOOLING_UP:
     401             :             // Maximum throttle should move from minimum to maximum.
     402             :             // Servos should exhibit normal flight behavior.
     403             : 
     404             :             // set limits flags
     405      158332 :             if (_heliflags.land_complete && !using_leaky_integrator()) {
     406           0 :                 limit.set_rpy(true);
     407             :             } else {
     408      158332 :                 limit.set_rpy(false);
     409             :             }
     410             : 
     411      158332 :             break;
     412             : 
     413     1995604 :         case SpoolState::THROTTLE_UNLIMITED:
     414             :             // Throttle should exhibit normal flight behavior.
     415             :             // Servos should exhibit normal flight behavior.
     416             : 
     417             :             // set limits flags
     418     1995604 :             if (_heliflags.land_complete && !using_leaky_integrator()) {
     419           0 :                 limit.set_rpy(true);
     420             :             } else {
     421     1995604 :                 limit.set_rpy(false);
     422             :             }
     423             : 
     424     1995604 :             break;
     425             : 
     426       70163 :         case SpoolState::SPOOLING_DOWN:
     427             :             // Maximum throttle should move from maximum to minimum.
     428             :             // Servos should exhibit normal flight behavior.
     429             : 
     430             :             // set limits flags
     431       70163 :             if (_heliflags.land_complete && !using_leaky_integrator()) {
     432           0 :                 limit.set_rpy(true);
     433             :             } else {
     434       70163 :                 limit.set_rpy(false);
     435             :             }
     436       70163 :             break;
     437             :     }
     438             : 
     439     3233979 : }
     440             : 
     441             : // update the throttle input filter
     442     3233979 : void AP_MotorsHeli::update_throttle_filter()
     443             : {
     444     3233979 :     _throttle_filter.apply(_throttle_in,  _dt_s);
     445             : 
     446             :     // constrain filtered throttle
     447     3233979 :     if (_throttle_filter.get() < 0.0f) {
     448     1139235 :         _throttle_filter.reset(0.0f);
     449             :     }
     450     3233979 :     if (_throttle_filter.get() > 1.0f) {
     451       20309 :         _throttle_filter.reset(1.0f);
     452             :     }
     453     3233979 : }
     454             : 
     455             : // reset_flight_controls - resets all controls and scalars to flight status
     456           0 : void AP_MotorsHeli::reset_flight_controls()
     457             : {
     458           0 :     _servo_mode.set(SERVO_CONTROL_MODE_AUTOMATED);
     459           0 :     init_outputs();
     460           0 :     calculate_scalars();
     461           0 : }
     462             : 
     463             : // update the collective input filter.  should be called at 100hz
     464      183677 : void AP_MotorsHeli::update_throttle_hover(float dt)
     465             : {
     466      183677 :     if (_collective_hover_learn != HOVER_LEARN_DISABLED) {
     467             : 
     468             :         // Don't let _collective_hover go below H_COLL_ZERO_THRST
     469      183677 :         float curr_collective = get_throttle();
     470      183677 :         if (curr_collective < _collective_zero_thrust_pct) {
     471           0 :             curr_collective = _collective_zero_thrust_pct;
     472             :         }
     473             : 
     474             :         // we have chosen to constrain the hover collective to be within the range reachable by the third order expo polynomial.
     475      183677 :         _collective_hover.set(constrain_float(_collective_hover + (dt / (dt + AP_MOTORS_HELI_COLLECTIVE_HOVER_TC)) * (curr_collective - _collective_hover), AP_MOTORS_HELI_COLLECTIVE_HOVER_MIN, AP_MOTORS_HELI_COLLECTIVE_HOVER_MAX));
     476             :     }
     477      183677 : }
     478             : 
     479             : // save parameters as part of disarming
     480          58 : void AP_MotorsHeli::save_params_on_disarm()
     481             : {
     482             :     // save hover throttle
     483          58 :     if (_collective_hover_learn == HOVER_LEARN_AND_SAVE) {
     484          58 :         _collective_hover.save();
     485             :     }
     486          58 : }
     487             : 
     488             : // updates the takeoff collective flag
     489     3234119 : void AP_MotorsHeli::update_takeoff_collective_flag(float coll_out)
     490             : {
     491     3234119 :     if (coll_out > _collective_zero_thrust_pct + 0.5f * (_collective_hover - _collective_zero_thrust_pct)) {
     492     1739016 :         _heliflags.takeoff_collective = true;
     493             :     } else {
     494     1495103 :         _heliflags.takeoff_collective = false;
     495             :     }
     496     3234119 : }
     497             : 
     498             : // Determines if _heli_options bit is set
     499     4258859 : bool AP_MotorsHeli::heli_option(HeliOption opt) const
     500             : {
     501     4258859 :     return (_heli_options & (uint8_t)opt);
     502             : }
     503             : 
     504             : // updates the turbine start flag
     505     3233979 : void AP_MotorsHeli::update_turbine_start()
     506             : {
     507     3233979 :     if (_heliflags.start_engine) {
     508       11920 :         _main_rotor.set_turbine_start(true);
     509             :     } else {
     510     3222059 :         _main_rotor.set_turbine_start(false);
     511             :     }
     512     3233979 : }
     513             : 
     514             : // Run arming checks
     515        2339 : bool AP_MotorsHeli::arming_checks(size_t buflen, char *buffer) const
     516             : {
     517             :     // run base class checks
     518        2339 :     if (!AP_Motors::arming_checks(buflen, buffer)) {
     519           0 :         return false;
     520             :     }
     521             : 
     522        2339 :     if (_heliflags.servo_test_running) {
     523           0 :         hal.util->snprintf(buffer, buflen, "Servo Test is still running");
     524           0 :         return false;
     525             :     }
     526             : 
     527             :     // returns false if RSC Mode is not set to a valid control mode
     528        2339 :     if (_main_rotor._rsc_mode.get() <= (int8_t)ROTOR_CONTROL_MODE_DISABLED || _main_rotor._rsc_mode.get() > (int8_t)ROTOR_CONTROL_MODE_AUTOTHROTTLE) {
     529           0 :         hal.util->snprintf(buffer, buflen, "H_RSC_MODE invalid");
     530           0 :         return false;
     531             :     }
     532             : 
     533             :     // returns false if rsc_setpoint is out of range
     534        2339 :     if ( _main_rotor._rsc_setpoint.get() > 100 || _main_rotor._rsc_setpoint.get() < 10){
     535           0 :         hal.util->snprintf(buffer, buflen, "H_RSC_SETPOINT out of range");
     536           0 :         return false;
     537             :     }
     538             : 
     539             :     // returns false if idle output is out of range
     540        2339 :     if ( _main_rotor._idle_output.get() > 100 || _main_rotor._idle_output.get() < 0){
     541           0 :         hal.util->snprintf(buffer, buflen, "H_RSC_IDLE out of range");
     542           0 :         return false;
     543             :     }
     544             : 
     545             :     // returns false if _rsc_critical is not between 0 and 100
     546        2339 :     if (_main_rotor._critical_speed.get() > 100 || _main_rotor._critical_speed.get() < 0) {
     547           0 :         hal.util->snprintf(buffer, buflen, "H_RSC_CRITICAL out of range");
     548           0 :         return false;
     549             :     }
     550             : 
     551             :     // returns false if RSC Runup Time is less than Ramp time as this could cause undesired behaviour of rotor speed estimate
     552        2339 :     if (_main_rotor._runup_time.get() <= _main_rotor._ramp_time.get()){
     553           0 :         hal.util->snprintf(buffer, buflen, "H_RUNUP_TIME too small");
     554           0 :         return false;
     555             :     }
     556             : 
     557             :     // returns false if _collective_min_deg is not default value which indicates users set parameter
     558        2339 :     if (is_equal((float)_collective_min_deg, (float)AP_MOTORS_HELI_COLLECTIVE_MIN_DEG)) {
     559           0 :         hal.util->snprintf(buffer, buflen, "Set H_COL_ANG_MIN to measured min blade pitch in deg");
     560           0 :         return false;
     561             :     }
     562             : 
     563             :     // returns false if _collective_max_deg is not default value which indicates users set parameter
     564        2339 :     if (is_equal((float)_collective_max_deg, (float)AP_MOTORS_HELI_COLLECTIVE_MAX_DEG)) {
     565           0 :         hal.util->snprintf(buffer, buflen, "Set H_COL_ANG_MAX to measured max blade pitch in deg");
     566           0 :         return false;
     567             :     }
     568             : 
     569        2339 :     if (!_main_rotor.autorotation.arming_checks(buflen, buffer)) {
     570           0 :         return false;
     571             :     }
     572             : 
     573        2339 :     return true;
     574             : }
     575             : 
     576             : // Tell user motor test is disabled on heli
     577           0 : bool AP_MotorsHeli::motor_test_checks(size_t buflen, char *buffer) const
     578             : {
     579           0 :     hal.util->snprintf(buffer, buflen, "Disabled on heli");
     580           0 :     return false;
     581             : }
     582             : 
     583             : // get_motor_mask - returns a bitmask of which outputs are being used for motors or servos (1 means being used)
     584             : //  this can be used to ensure other pwm outputs (i.e. for servos) do not conflict
     585           3 : uint32_t AP_MotorsHeli::get_motor_mask()
     586             : {
     587           3 :     return _main_rotor.get_output_mask();
     588             : }
     589             : 
     590             : // set_desired_rotor_speed
     591           0 : void AP_MotorsHeli::set_desired_rotor_speed(float desired_rotor_speed)
     592             : {
     593           0 :     _main_rotor.set_passthru_desired_rotor_speed(desired_rotor_speed);
     594           0 : }
     595             : 
     596             : // Update _heliflags.rotor_runup_complete value writing log event on state change
     597     3234119 : void AP_MotorsHeli::set_rotor_runup_complete(bool new_value)
     598             : {
     599             : #if HAL_LOGGING_ENABLED
     600     3234119 :     if (!_heliflags.rotor_runup_complete && new_value) {
     601          33 :         LOGGER_WRITE_EVENT(LogEvent::ROTOR_RUNUP_COMPLETE);
     602     3234086 :     } else if (_heliflags.rotor_runup_complete && !new_value && !_main_rotor.in_autorotation()) {
     603          33 :         LOGGER_WRITE_EVENT(LogEvent::ROTOR_SPEED_BELOW_CRITICAL);
     604             :     }
     605             : #endif
     606     3234119 :     _heliflags.rotor_runup_complete = new_value;
     607     3234119 : }
     608             : 
     609             : #if HAL_LOGGING_ENABLED
     610             : // Returns the scaling value required to convert the collective angle parameters into the cyclic-output-to-angle conversion
     611       76247 : float AP_MotorsHeli::get_cyclic_angle_scaler(void) const {
     612             :     // We want to use the collective min-max to angle relationship to calculate the cyclic input to angle relationship
     613             :     // First we scale the collective angle range by it's min-max output. Recall that we assume that the maximum possible
     614             :     // collective range is 1000, hence the *1e-3.
     615             :     // The factor 2.0 accounts for the fact that we scale the servo outputs from 0 ~ 1 to -1 ~ 1
     616       76247 :     return ((float)(_collective_max-_collective_min))*1e-3 * (_collective_max_deg.get() - _collective_min_deg.get()) * 2.0;
     617             : }
     618             : #endif
     619             : 
     620             : // Helper function for param conversions to be done in motors class
     621          83 : void AP_MotorsHeli::heli_motors_param_conversions(void)
     622             : {
     623             :     // PARAMETER_CONVERSION - Added: Sep-2024
     624             :     // move autorotation related parameters within the RSC into their own class
     625          83 :     const AP_Param::ConversionInfo rsc_arot_conversion_info[] = {
     626             :         { 90, 108096, AP_PARAM_INT8,  "H_RSC_AROT_ENBL" },
     627             :         { 90, 104000, AP_PARAM_INT8,  "H_RSC_AROT_RAMP" },
     628             :         { 90, 112192, AP_PARAM_INT16,  "H_RSC_AROT_IDLE" },
     629             :     };
     630          83 :     uint8_t table_size = ARRAY_SIZE(rsc_arot_conversion_info);
     631         332 :     for (uint8_t i=0; i<table_size; i++) {
     632         249 :         AP_Param::convert_old_parameter(&rsc_arot_conversion_info[i], 1.0);
     633             :     }
     634          83 : }
     635             : 
     636             : // function to calculate and set the normalised collective position given a desired blade pitch angle (deg)
     637           0 : void AP_MotorsHeli::set_coll_from_ang(float col_ang_deg)
     638             : {
     639           0 :     const float col_norm = (col_ang_deg - _collective_min_deg.get()) / MAX((_collective_max_deg.get() - _collective_min_deg.get()), 1.0);
     640           0 :     set_throttle(constrain_float(col_norm, 0.0, 1.0));
     641           0 : }

Generated by: LCOV version 1.14