LCOV - code coverage report
Current view: top level - AP_Motors - AP_MotorsHeli_Dual.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 195 0.0 %
Date: 2026-09-09 11:02:08 Functions: 0 14 0.0 %

          Line data    Source code
       1             : /*
       2             :  *  This program is free software: you can redistribute it and/or modify
       3             :  *  it under the terms of the GNU General Public License as published by
       4             :  *  the Free Software Foundation, either version 3 of the License, or
       5             :  *  (at your option) any later version.
       6             :  *
       7             :  *  This program is distributed in the hope that it will be useful,
       8             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
       9             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      10             :  *  GNU General Public License for more details.
      11             :  *
      12             :  *  You should have received a copy of the GNU General Public License
      13             :  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
      14             :  */
      15             : 
      16             : #include <stdlib.h>
      17             : #include <AP_HAL/AP_HAL.h>
      18             : #include "AP_MotorsHeli_Dual.h"
      19             : #include <GCS_MAVLink/GCS.h>
      20             : 
      21             : extern const AP_HAL::HAL& hal;
      22             : 
      23             : const AP_Param::GroupInfo AP_MotorsHeli_Dual::var_info[] = {
      24             :     AP_NESTEDGROUPINFO(AP_MotorsHeli, 0),
      25             : 
      26             :     // Indices 1-6 were used by servo position params and should not be used
      27             : 
      28             :     // Indices 7-8 were used by phase angle params and should not be used
      29             : 
      30             :     // @Param: DUAL_MODE
      31             :     // @DisplayName: Dual Mode
      32             :     // @Description: Sets the dual mode of the heli, either as tandem, transverse, or intermeshing/coaxial.
      33             :     // @Values: 0:Longitudinal, 1:Transverse, 2:Intermeshing/Coaxial
      34             :     // @User: Standard
      35             :     AP_GROUPINFO("DUAL_MODE", 9, AP_MotorsHeli_Dual, _dual_mode, AP_MOTORS_HELI_DUAL_MODE_TANDEM),
      36             : 
      37             :     // @Param: DCP_SCALER
      38             :     // @DisplayName: Differential-Collective-Pitch Scaler
      39             :     // @Description: Scaling factor applied to the differential-collective-pitch
      40             :     // @Range: 0 1
      41             :     // @User: Standard
      42             :     AP_GROUPINFO("DCP_SCALER", 10, AP_MotorsHeli_Dual, _dcp_scaler, AP_MOTORS_HELI_DUAL_DCP_SCALER),
      43             : 
      44             :     // @Param: DCP_YAW
      45             :     // @DisplayName: Differential-Collective-Pitch Yaw Mixing
      46             :     // @Description: Feed-forward compensation to automatically add yaw input when differential collective pitch is applied.  Disabled for intermeshing mode.
      47             :     // @Range: -10 10
      48             :     // @Increment: 0.1
      49             :     // @User: Standard
      50             :     AP_GROUPINFO("DCP_YAW", 11, AP_MotorsHeli_Dual, _dcp_yaw_effect, 0),
      51             : 
      52             :     // @Param: YAW_SCALER
      53             :     // @DisplayName: Scaler for yaw mixing
      54             :     // @Description: Scaler for mixing yaw into roll or pitch.
      55             :     // @Range: -10 10
      56             :     // @Increment: 0.1
      57             :     // @User: Standard
      58             :     AP_GROUPINFO("YAW_SCALER", 12, AP_MotorsHeli_Dual, _yaw_scaler, 1.0f),
      59             : 
      60             :     // Indices 13-15 were used by RSC_PWM_MIN, RSC_PWM_MAX and RSC_PWM_REV and should not be used
      61             : 
      62             :     // @Param: COL2_MIN
      63             :     // @DisplayName: Swash 2 Minimum Collective Pitch
      64             :     // @Description: Lowest possible servo position in PWM microseconds for swashplate 2
      65             :     // @Range: 1000 2000
      66             :     // @Units: PWM
      67             :     // @Increment: 1
      68             :     // @User: Standard
      69             :     AP_GROUPINFO("COL2_MIN", 16, AP_MotorsHeli_Dual, _collective2_min, AP_MOTORS_HELI_DUAL_COLLECTIVE2_MIN),
      70             : 
      71             :     // @Param: COL2_MAX
      72             :     // @DisplayName: Swash 2 Maximum Collective Pitch
      73             :     // @Description: Highest possible servo position in PWM microseconds for swashplate 2
      74             :     // @Range: 1000 2000
      75             :     // @Units: PWM
      76             :     // @Increment: 1
      77             :     // @User: Standard
      78             :     AP_GROUPINFO("COL2_MAX", 17, AP_MotorsHeli_Dual, _collective2_max, AP_MOTORS_HELI_DUAL_COLLECTIVE2_MAX),
      79             : 
      80             :     // Indice 18 was used by COL2_MID and should not be used
      81             : 
      82             :     // Indice 19 was used by COL_CTRL_DIR and should not be used
      83             : 
      84             :     // @Param: SW_TYPE
      85             :     // @DisplayName: Swash 1 Type
      86             :     // @Description: H3 is generic, three-servo only. H3_120/H3_140 plates have Motor1 left side, Motor2 right side, Motor3 elevator in rear. HR3_120/HR3_140 have Motor1 right side, Motor2 left side, Motor3 elevator in front - use H3_120/H3_140 and reverse servo and collective directions as necessary. For all H3_90 swashplates use H4_90 and don't use servo output for the missing servo. For H4-90 Motors1&2 are left/right respectively, Motors3&4 are rear/front respectively. For H4-45 Motors1&2 are LF/RF, Motors3&4 are LR/RR 
      87             :     // @Values: 0:H3 Generic,1:H1 non-CPPM,2:H3_140,3:H3_120,4:H4_90,5:H4_45
      88             :     // @User: Standard
      89             : 
      90             :     // @Param: SW_COL_DIR
      91             :     // @DisplayName: Swash 1 Collective Direction
      92             :     // @Description: Direction collective moves for positive pitch. 0 for Normal, 1 for Reversed
      93             :     // @Values: 0:Normal,1:Reversed
      94             :     // @User: Standard
      95             : 
      96             :     // @Param: SW_LIN_SVO
      97             :     // @DisplayName: Linearize Swash 1 Servos
      98             :     // @Description: This linearizes the swashplate 1 servo's mechanical output to account for nonlinear output due to arm rotation.  This requires a specific setup procedure to work properly.  The servo arm must be centered on the mechanical throw at the servo trim position and the servo trim position kept as close to 1500 as possible. Leveling the swashplate can only be done through the pitch links.  See the ardupilot wiki for more details on setup.
      99             :     // @Values: 0:Disabled,1:Enabled
     100             :     // @User: Standard
     101             : 
     102             :     // @Param: SW_PHANG
     103             :     // @DisplayName: Swash 1 Phase Angle Compensation
     104             :     // @Description: Phase angle compensation can be used to correct control coupling issues.  If pitching the swash forward induces a roll, this can be correct the problem.
     105             :     // @Range: -30 30
     106             :     // @Units: deg
     107             :     // @User: Advanced
     108             :     // @Increment: 1
     109             : 
     110             :     // @Param: SW_H3_ENABLE
     111             :     // @DisplayName: Swash 1 H3 Generic Enable
     112             :     // @Description: Automatically set when H3 generic swash type is selected for swashplate 1. Do not set manually.
     113             :     // @Values: 0:Disabled,1:Enabled
     114             :     // @User: Advanced
     115             : 
     116             :     // @Param: SW_H3_SV1_POS
     117             :     // @DisplayName: Swash 1 H3 Generic Servo 1 Position
     118             :     // @Description: Azimuth position on swashplate for servo 1 with the front of the heli being 0 deg
     119             :     // @Range: -180 180
     120             :     // @Units: deg
     121             :     // @User: Advanced
     122             : 
     123             :     // @Param: SW_H3_SV2_POS
     124             :     // @DisplayName: Swash 1 H3 Generic Servo 2 Position
     125             :     // @Description: Azimuth position on swashplate 1 for servo 2 with the front of the heli being 0 deg
     126             :     // @Range: -180 180
     127             :     // @Units: deg
     128             :     // @User: Advanced
     129             : 
     130             :     // @Param: SW_H3_SV3_POS
     131             :     // @DisplayName: Swash 1 H3 Generic Servo 3 Position
     132             :     // @Description: Azimuth position on swashplate 1 for servo 3 with the front of the heli being 0 deg
     133             :     // @Range: -180 180
     134             :     // @Units: deg
     135             :     // @User: Advanced
     136             :     AP_SUBGROUPINFO(_swashplate1, "SW_", 20, AP_MotorsHeli_Dual, AP_MotorsHeli_Swash),
     137             : 
     138             :     // @Param: SW2_TYPE
     139             :     // @DisplayName: Swash 2 Type
     140             :     // @Description: H3 is generic, three-servo only. H3_120/H3_140 plates have Motor1 left side, Motor2 right side, Motor3 elevator in rear. HR3_120/HR3_140 have Motor1 right side, Motor2 left side, Motor3 elevator in front - use H3_120/H3_140 and reverse servo and collective directions as necessary. For all H3_90 swashplates use H4_90 and don't use servo output for the missing servo. For H4-90 Motors1&2 are left/right respectively, Motors3&4 are rear/front respectively. For H4-45 Motors1&2 are LF/RF, Motors3&4 are LR/RR 
     141             :     // @Values: 0:H3 Generic,1:H1 non-CPPM,2:H3_140,3:H3_120,4:H4_90,5:H4_45
     142             :     // @User: Standard
     143             : 
     144             :     // @Param: SW2_COL_DIR
     145             :     // @DisplayName: Swash 2 Collective Direction
     146             :     // @Description: Direction collective moves for positive pitch. 0 for Normal, 1 for Reversed
     147             :     // @Values: 0:Normal,1:Reversed
     148             :     // @User: Standard
     149             : 
     150             :     // @Param: SW2_LIN_SVO
     151             :     // @DisplayName: Linearize Swash 2 Servos
     152             :     // @Description: This linearizes the swashplate 2 servo's mechanical output to account for nonlinear output due to arm rotation.  This requires a specific setup procedure to work properly.  The servo arm must be centered on the mechanical throw at the servo trim position and the servo trim position kept as close to 1500 as possible. Leveling the swashplate can only be done through the pitch links.  See the ardupilot wiki for more details on setup.
     153             :     // @Values: 0:Disabled,1:Enabled
     154             :     // @User: Standard
     155             : 
     156             :     // @Param: SW2_PHANG
     157             :     // @DisplayName: Swash 2 Phase Angle Compensation
     158             :     // @Description: Phase angle compensation can be used to correct control coupling issues.  If pitching the swash forward induces a roll, this can be correct the problem.
     159             :     // @Range: -30 30
     160             :     // @Units: deg
     161             :     // @User: Advanced
     162             :     // @Increment: 1
     163             : 
     164             :     // @Param: SW2_H3_ENABLE
     165             :     // @DisplayName: Swash 2 H3 Generic Enable
     166             :     // @Description: Automatically set when H3 generic swash type is selected for swashplate 2. Do not set manually.
     167             :     // @Values: 0:Disabled,1:Enabled
     168             :     // @User: Advanced
     169             : 
     170             :     // @Param: SW2_H3_SV1_POS
     171             :     // @DisplayName: Swash 2 H3 Generic Servo 1 Position
     172             :     // @Description: Azimuth position on swashplate for servo 1 with the front of the heli being 0 deg
     173             :     // @Range: -180 180
     174             :     // @Units: deg
     175             :     // @User: Advanced
     176             : 
     177             :     // @Param: SW2_H3_SV2_POS
     178             :     // @DisplayName: Swash 2 H3 Generic Servo 2 Position
     179             :     // @Description: Azimuth position on swashplate 2 for servo 2 with the front of the heli being 0 deg
     180             :     // @Range: -180 180
     181             :     // @Units: deg
     182             :     // @User: Advanced
     183             : 
     184             :     // @Param: SW2_H3_SV3_POS
     185             :     // @DisplayName: Swash 2 H3 Generic Servo 3 Position
     186             :     // @Description: Azimuth position on swashplate 2 for servo 3 with the front of the heli being 0 deg
     187             :     // @Range: -180 180
     188             :     // @Units: deg
     189             :     // @User: Advanced
     190             :     AP_SUBGROUPINFO(_swashplate2, "SW2_", 21, AP_MotorsHeli_Dual, AP_MotorsHeli_Swash),
     191             : 
     192             :     // @Param: DCP_TRIM
     193             :     // @DisplayName: Differential Collective Pitch Trim
     194             :     // @Description: Removes I term bias due to center of gravity offsets or discrepancies between rotors in swashplate setup. If DCP axis has I term bias while hovering in calm winds, use value of bias in DCP_TRIM to re-center I term.
     195             :     // @Range: -0.2 0.2
     196             :     // @Increment: 0.01
     197             :     // @User: Standard
     198             :     AP_GROUPINFO("DCP_TRIM", 22, AP_MotorsHeli_Dual, _dcp_trim, 0.0f),
     199             : 
     200             :     // @Param: YAW_REV_EXPO
     201             :     // @DisplayName: Yaw reverser expo
     202             :     // @Description: For intermeshing mode only. Yaw revereser smoothing exponent, smoothen transition near zero collective region. Increase this parameter to shink smoothing range. Set to -1 to disable reverser. 
     203             :     // @Range: -1 1000
     204             :     // @Increment: 1.0
     205             :     // @User: Standard
     206             :     AP_GROUPINFO("YAW_REV_EXPO", 23, AP_MotorsHeli_Dual, _yaw_rev_expo, -1),
     207             : 
     208             :     AP_GROUPEND
     209             : };
     210             : 
     211             : // set update rate to motors - a value in hertz
     212           0 : void AP_MotorsHeli_Dual::set_update_rate( uint16_t speed_hz )
     213             : {
     214             :     // record requested speed
     215           0 :     _speed_hz = speed_hz;
     216             : 
     217             :     // setup fast channels
     218           0 :     uint32_t mask = _swashplate1.get_output_mask() | _swashplate2.get_output_mask();
     219             : 
     220           0 :     rc_set_freq(mask, _speed_hz);
     221           0 : }
     222             : 
     223             : // init_outputs
     224           0 : void AP_MotorsHeli_Dual::init_outputs()
     225             : {
     226           0 :     if (!initialised_ok()) {
     227             :         // set rotor servo range
     228           0 :         _main_rotor.initialize();
     229             :     }
     230             : 
     231           0 :     set_initialised_ok(_frame_class == MOTOR_FRAME_HELI_DUAL);
     232           0 : }
     233             : 
     234             : // calculate_scalars
     235           0 : void AP_MotorsHeli_Dual::calculate_scalars()
     236             : {
     237             :     // range check collective min, max and mid
     238           0 :     if( _collective_min >= _collective_max ) {
     239           0 :         _collective_min.set(AP_MOTORS_HELI_COLLECTIVE_MIN);
     240           0 :         _collective_max.set(AP_MOTORS_HELI_COLLECTIVE_MAX);
     241             :     }
     242             : 
     243             : 
     244             :     // range check collective min, max and mid for rear swashplate
     245           0 :     if( _collective2_min >= _collective2_max ) {
     246           0 :         _collective2_min.set(AP_MOTORS_HELI_DUAL_COLLECTIVE2_MIN);
     247           0 :         _collective2_max.set(AP_MOTORS_HELI_DUAL_COLLECTIVE2_MAX);
     248             :     }
     249             : 
     250           0 :     _collective_zero_thrust_deg.set(constrain_float(_collective_zero_thrust_deg, _collective_min_deg, _collective_max_deg));
     251             : 
     252           0 :     _collective_land_min_deg.set(constrain_float(_collective_land_min_deg, _collective_min_deg, _collective_max_deg));
     253             : 
     254           0 :     if (!is_equal((float)_collective_max_deg, (float)_collective_min_deg)) {
     255             :         // calculate collective zero thrust point as a number from 0 to 1
     256           0 :         _collective_zero_thrust_pct = (_collective_zero_thrust_deg-_collective_min_deg)/(_collective_max_deg-_collective_min_deg);
     257             : 
     258             :         // calculate collective land min point as a number from 0 to 1
     259           0 :         _collective_land_min_pct = (_collective_land_min_deg-_collective_min_deg)/(_collective_max_deg-_collective_min_deg);
     260             :     } else {
     261           0 :         _collective_zero_thrust_pct = 0.0f;
     262           0 :         _collective_land_min_pct = 0.0f;
     263             :     }
     264             : 
     265           0 :     _collective2_zero_thrst_pct = _collective_zero_thrust_pct;
     266             : 
     267             :     // configure swashplate 1 and update scalars
     268           0 :     _swashplate1.configure();
     269             : 
     270             :     // configure swashplate 2 and update scalars
     271           0 :     _swashplate2.configure();
     272             : 
     273             :     // configure main rotor and update scalars
     274           0 :     _main_rotor.configure();
     275             : 
     276             :     // calculate armed scalars for both main rotors
     277           0 :     calculate_armed_scalars();
     278             : 
     279           0 : }
     280             : 
     281             : // Mix and output swashplates for tandem
     282           0 : void AP_MotorsHeli_Dual::mix_tandem(float pitch_input, float roll_input, float yaw_input, float collective1_input, float collective2_input)
     283             : {
     284             :     // Differential cyclic roll is used for yaw and combined for roll
     285           0 :     const float swash1_roll = roll_input + _yaw_scaler * yaw_input;
     286           0 :     const float swash2_roll = roll_input - _yaw_scaler * yaw_input;
     287             : 
     288             :     // cyclic is not used for pitch control
     289           0 :     const float swash_pitch = 0.0;
     290             : 
     291             :     // Differential collective for pitch and combined for thrust
     292           0 :     const float swash1_coll =  0.45 * _dcp_scaler * (pitch_input + constrain_float(_dcp_trim, -0.2, 0.2)) + collective1_input;
     293           0 :     const float swash2_coll = -0.45 * _dcp_scaler * (pitch_input + constrain_float(_dcp_trim, -0.2, 0.2)) + collective2_input;
     294             : 
     295             :     // Calculate servo positions in swashplate library
     296           0 :     _swashplate1.calculate(swash1_roll, swash_pitch, swash1_coll);
     297           0 :     _swashplate2.calculate(swash2_roll, swash_pitch, swash2_coll);
     298           0 : }
     299             : 
     300             : // Mix and output swashplates for transverse
     301           0 : void AP_MotorsHeli_Dual::mix_transverse(float pitch_input, float roll_input, float yaw_input, float collective1_input, float collective2_input)
     302             : {
     303             :     // cyclic is not used for roll control
     304           0 :     const float swash_roll = 0.0;
     305             : 
     306             :     // Differential cyclic pitch is used for yaw
     307           0 :     const float swash1_pitch = pitch_input - _yaw_scaler * yaw_input;
     308           0 :     const float swash2_pitch = pitch_input + _yaw_scaler * yaw_input;
     309             : 
     310             :     // Differential collective for roll and combined for thrust
     311           0 :     const float swash1_coll =  0.45 * _dcp_scaler * (roll_input + constrain_float(_dcp_trim, -0.2, 0.2)) + collective1_input;
     312           0 :     const float swash2_coll = -0.45 * _dcp_scaler * (roll_input + constrain_float(_dcp_trim, -0.2, 0.2)) + collective2_input;
     313             : 
     314             :     // Calculate servo positions in swashplate library
     315           0 :     _swashplate1.calculate(swash_roll, swash1_pitch, swash1_coll);
     316           0 :     _swashplate2.calculate(swash_roll, swash2_pitch, swash2_coll);
     317           0 : }
     318             : 
     319             : // Mix and output swashplates for intermeshing
     320           0 : void AP_MotorsHeli_Dual::mix_intermeshing(float pitch_input, float roll_input, float yaw_input, float collective1_input, float collective2_input)
     321             : {
     322             :     // Direct roll control on both swash plates
     323           0 :     const float swash_roll = roll_input;
     324             : 
     325             :     // Differential cyclic pitch is used for yaw and combined for pitch
     326           0 :     const float swash1_pitch = pitch_input - _yaw_scaler * yaw_input;
     327           0 :     const float swash2_pitch = pitch_input + _yaw_scaler * yaw_input;
     328             : 
     329             :     // Differential collective for yaw and combined for thrust
     330           0 :     const float swash1_coll =   0.45 * _dcp_scaler * yaw_input + collective1_input;
     331           0 :     const float swash2_coll =  -0.45 * _dcp_scaler * yaw_input + collective2_input;
     332             : 
     333             :     // Calculate servo positions in swashplate library
     334           0 :     _swashplate1.calculate(swash_roll, swash1_pitch, swash1_coll);
     335           0 :     _swashplate2.calculate(swash_roll, swash2_pitch, swash2_coll);
     336           0 : }
     337             : 
     338             : // update_motor_controls - sends commands to motor controllers
     339           0 : void AP_MotorsHeli_Dual::update_motor_control(AP_MotorsHeli_RSC::DesiredRSCSpoolState state)
     340             : {
     341             :     // Send state update to motors
     342           0 :     _main_rotor.update(_dt_s);
     343             : 
     344           0 :     if (state == AP_MotorsHeli_RSC::DesiredRSCSpoolState::SHUT_DOWN) {
     345             :         // set engine run enable aux output to not run position to kill engine when disarmed
     346           0 :         SRV_Channels::set_output_limit(SRV_Channel::k_engine_run_enable, SRV_Channel::Limit::MIN);
     347             :     } else {
     348             :         // else if armed, set engine run enable output to run position
     349           0 :         SRV_Channels::set_output_limit(SRV_Channel::k_engine_run_enable, SRV_Channel::Limit::MAX);
     350             :     }
     351           0 : }
     352             : 
     353             : // update_spool_state - updates the spool state based on the desired state
     354           0 : AP_Motors::SpoolState AP_MotorsHeli_Dual::update_spool_state(AP_MotorsHeli_RSC::DesiredRSCSpoolState state)
     355             : {
     356             : 
     357           0 :     SpoolState main_rotor_state = _main_rotor.update_spool_state(state, _dt_s);
     358             : 
     359             :     // Check if main rotor is run-up complete.  Tail rotor run-up is not included in check because currently
     360             :     // the tail rotor in DDVP uses the same ramp and runup time as the main rotor.  This may need changed if 
     361             :     // the RSC is used for DDFP tail rotors.
     362           0 :     set_rotor_runup_complete(main_rotor_state == AP_MotorsHeli_RSC::RSCSpoolState::THROTTLE_UNLIMITED);
     363             : 
     364           0 :     return main_rotor_state;
     365             : 
     366             : }
     367             : 
     368             : //
     369             : // move_actuators - moves swash plate to attitude of parameters passed in
     370             : //                - expected ranges:
     371             : //                       roll : -1 ~ +1
     372             : //                       pitch: -1 ~ +1
     373             : //                       collective: 0 ~ 1
     374             : //                       yaw:   -1 ~ +1
     375             : //
     376           0 : void AP_MotorsHeli_Dual::move_actuators(float roll_out, float pitch_out, float collective_in, float yaw_out)
     377             : {
     378           0 :     if (_dual_mode == AP_MOTORS_HELI_DUAL_MODE_TRANSVERSE || _dual_mode == AP_MOTORS_HELI_DUAL_MODE_INTERMESHING) {
     379           0 :         if (pitch_out < -_cyclic_max/4500.0f) {
     380           0 :             pitch_out = -_cyclic_max/4500.0f;
     381           0 :             limit.pitch = true;
     382             :         }
     383             : 
     384           0 :         if (pitch_out > _cyclic_max/4500.0f) {
     385           0 :             pitch_out = _cyclic_max/4500.0f;
     386           0 :             limit.pitch = true;
     387             :         }
     388             :     }
     389           0 :     if (_dual_mode != AP_MOTORS_HELI_DUAL_MODE_TRANSVERSE) {
     390           0 :         if (roll_out < -_cyclic_max/4500.0f) {
     391           0 :             roll_out = -_cyclic_max/4500.0f;
     392           0 :             limit.roll = true;
     393             :         }
     394             : 
     395           0 :         if (roll_out > _cyclic_max/4500.0f) {
     396           0 :             roll_out = _cyclic_max/4500.0f;
     397           0 :             limit.roll = true;
     398             :         }
     399             :     }
     400             : 
     401             :     // constrain collective input
     402           0 :     float collective_out = collective_in;
     403           0 :     if (collective_out <= 0.0f) {
     404           0 :         collective_out = 0.0f;
     405           0 :         limit.throttle_lower = true;
     406             :     }
     407           0 :     if (collective_out >= 1.0f) {
     408           0 :         collective_out = 1.0f;
     409           0 :         limit.throttle_upper = true;
     410             :     }
     411             : 
     412             :     // ensure not below landed/landing collective
     413           0 :     if (_heliflags.landing_collective && collective_out < _collective_land_min_pct) {
     414           0 :         collective_out = _collective_land_min_pct;
     415           0 :         limit.throttle_lower = true;
     416             :     }
     417             : 
     418             :     // updates below land min collective flag
     419           0 :     _heliflags.below_land_min_coll = !is_positive(collective_out - _collective_land_min_pct);
     420             : 
     421             :     // updates takeoff collective flag based on 50% hover collective
     422           0 :     update_takeoff_collective_flag(collective_out);
     423             : 
     424             :     // Set rear collective to midpoint if required
     425           0 :     float collective2_out = collective_out;
     426           0 :     if (_servo_mode == SERVO_CONTROL_MODE_MANUAL_CENTER) {
     427           0 :         collective2_out = _collective2_zero_thrst_pct;
     428             :     }
     429             : 
     430             :     // if servo output not in manual mode, process pre-compensation factors
     431           0 :     if (_servo_mode == SERVO_CONTROL_MODE_AUTOMATED) {
     432             :         // add differential collective pitch yaw compensation
     433           0 :         float yaw_compensation = 0.0f;
     434             : 
     435           0 :         if (_dual_mode == AP_MOTORS_HELI_DUAL_MODE_INTERMESHING) {
     436             :             // for intermeshing, reverse yaw in negative collective region and smoothen transition near zero collective
     437           0 :             if (_yaw_rev_expo > 0.01f) {
     438             :                 // yaw_compensation range: (-1,1) S-shaped curve (Logistic Model) 1/(1 + e^kt)
     439           0 :                 yaw_compensation = 1.0f - (2.0f / (1.0f + powf(2.7182818f , _yaw_rev_expo * (collective_out-_collective_zero_thrust_pct))));
     440           0 :                 yaw_out = yaw_out * yaw_compensation;
     441             :             }
     442             :         } else {
     443           0 :             if (_dual_mode == AP_MOTORS_HELI_DUAL_MODE_TRANSVERSE) {
     444           0 :                 yaw_compensation = _dcp_yaw_effect * roll_out;
     445             :             } else { // AP_MOTORS_HELI_DUAL_MODE_TANDEM
     446           0 :                 yaw_compensation = _dcp_yaw_effect * pitch_out;
     447             :             }
     448           0 :             yaw_out = yaw_out + yaw_compensation;
     449             :         }
     450             :     }
     451             : 
     452             :     // scale yaw and update limits
     453           0 :     if (yaw_out < -_cyclic_max/4500.0f) {
     454           0 :         yaw_out = -_cyclic_max/4500.0f;
     455           0 :         limit.yaw = true;
     456             :     }
     457           0 :     if (yaw_out > _cyclic_max/4500.0f) {
     458           0 :         yaw_out = _cyclic_max/4500.0f;
     459           0 :         limit.yaw = true;
     460             :     }
     461             : 
     462             :     // scale collective pitch for front swashplate (servos 1,2,3)
     463           0 :     float collective_scaler = ((float)(_collective_max-_collective_min))*0.001f;
     464           0 :     float collective_out_scaled = collective_out * collective_scaler + (_collective_min - 1000)*0.001f;
     465             : 
     466             :     // scale collective pitch for rear swashplate (servos 4,5,6)
     467           0 :     float collective2_scaler = ((float)(_collective2_max-_collective2_min))*0.001f;
     468           0 :     float collective2_out_scaled = collective2_out * collective2_scaler + (_collective2_min - 1000)*0.001f;
     469             : 
     470             :     // feed power estimate into main rotor controller
     471             :     // ToDo: add main rotor cyclic power?
     472           0 :     _main_rotor.set_collective(fabsf(collective_out));
     473             : 
     474             :     // Mix swash plate
     475           0 :     switch (_dual_mode) {
     476           0 :         case AP_MOTORS_HELI_DUAL_MODE_TANDEM:
     477             :         default:
     478           0 :             mix_tandem(pitch_out, roll_out, yaw_out, collective_out_scaled, collective2_out_scaled);
     479           0 :             break;
     480             : 
     481           0 :         case AP_MOTORS_HELI_DUAL_MODE_TRANSVERSE:
     482           0 :             mix_transverse(pitch_out, roll_out, yaw_out, collective_out_scaled, collective2_out_scaled);
     483           0 :             break;
     484             : 
     485           0 :         case AP_MOTORS_HELI_DUAL_MODE_INTERMESHING:
     486           0 :             mix_intermeshing(pitch_out, roll_out, yaw_out, collective_out_scaled, collective2_out_scaled);
     487           0 :             break;
     488             : 
     489             :     }
     490             : 
     491           0 : }
     492             : 
     493           0 : void AP_MotorsHeli_Dual::output_to_motors()
     494             : {
     495           0 :     if (!initialised_ok()) {
     496           0 :         return;
     497             :     }
     498             : 
     499             :     // Write swashplate outputs
     500           0 :     _swashplate1.output();
     501           0 :     _swashplate2.output();
     502             : 
     503           0 :     _main_rotor.output_to_servo();
     504             : 
     505             : }
     506             : 
     507             : // servo_test - move servos through full range of movement
     508           0 : void AP_MotorsHeli_Dual::servo_test()
     509             : {
     510             :     // this test cycle is equivalent to that of AP_MotorsHeli_Single, but excluding
     511             :     // mixing of yaw, as that physical movement is represented by pitch and roll
     512             : 
     513           0 :     _servo_test_cycle_time += _dt_s;
     514             : 
     515           0 :     if ((_servo_test_cycle_time >= 0.0f && _servo_test_cycle_time < 0.5f)||                                   // Tilt swash back
     516           0 :         (_servo_test_cycle_time >= 6.0f && _servo_test_cycle_time < 6.5f)){
     517           0 :         _pitch_test += 2.0 * _dt_s;
     518           0 :         _oscillate_angle += 8 * M_PI * _dt_s;
     519           0 :     } else if ((_servo_test_cycle_time >= 0.5f && _servo_test_cycle_time < 4.5f)||                            // Roll swash around
     520           0 :                (_servo_test_cycle_time >= 6.5f && _servo_test_cycle_time < 10.5f)){
     521           0 :         _oscillate_angle += 0.5 * M_PI * _dt_s;
     522           0 :         _roll_test = sinf(_oscillate_angle);
     523           0 :         _pitch_test = cosf(_oscillate_angle);
     524           0 :     } else if ((_servo_test_cycle_time >= 4.5f && _servo_test_cycle_time < 5.0f)||                            // Return swash to level
     525           0 :                (_servo_test_cycle_time >= 10.5f && _servo_test_cycle_time < 11.0f)){
     526           0 :         _pitch_test -= 2.0 * _dt_s;
     527           0 :         _oscillate_angle += 8 * M_PI * _dt_s;
     528           0 :     } else if (_servo_test_cycle_time >= 5.0f && _servo_test_cycle_time < 6.0f){                              // Raise swash to top
     529           0 :         _collective_test +=  _dt_s;
     530           0 :         _oscillate_angle += 2 * M_PI * _dt_s;
     531           0 :     } else if (_servo_test_cycle_time >= 11.0f && _servo_test_cycle_time < 12.0f){                            // Lower swash to bottom
     532           0 :         _collective_test -=  _dt_s;
     533           0 :         _oscillate_angle += 2 * M_PI * _dt_s;
     534             :     } else {                                                                                                  // reset cycle
     535           0 :         _servo_test_cycle_time = 0.0f;
     536           0 :         _oscillate_angle = 0.0f;
     537           0 :         _collective_test = 0.0f;
     538           0 :         _roll_test = 0.0f;
     539           0 :         _pitch_test = 0.0f;
     540             :         // decrement servo test cycle counter at the end of the cycle
     541           0 :         if (_servo_test_cycle_counter > 0){
     542           0 :             _servo_test_cycle_counter--;
     543             :         }
     544             :     }
     545             : 
     546             :     // over-ride servo commands to move servos through defined ranges
     547             : 
     548           0 :     _throttle_filter.reset(constrain_float(_collective_test, 0.0f, 1.0f));
     549           0 :     _roll_in = constrain_float(_roll_test, -1.0f, 1.0f);
     550           0 :     _pitch_in = constrain_float(_pitch_test, -1.0f, 1.0f);
     551           0 : }
     552             : 
     553             : // Run arming checks
     554           0 : bool AP_MotorsHeli_Dual::arming_checks(size_t buflen, char *buffer) const
     555             : {
     556             :     // run base class checks
     557           0 :     if (!AP_MotorsHeli::arming_checks(buflen, buffer)) {
     558           0 :         return false;
     559             :     }
     560             : 
     561             :     // returns false if Phase Angle is outside of range for H3 swashplate 1
     562           0 :     if (_swashplate1.get_phase_angle() > 30 || _swashplate1.get_phase_angle() < -30){
     563           0 :         hal.util->snprintf(buffer, buflen, "H_SW_PHANG out of range");
     564           0 :         return false;
     565             :     }
     566             : 
     567             :     // returns false if Phase Angle is outside of range for H3 swashplate 2
     568           0 :     if (_swashplate2.get_phase_angle() > 30 || _swashplate2.get_phase_angle() < -30){
     569           0 :         hal.util->snprintf(buffer, buflen, "H_SW2_PHANG out of range");
     570           0 :         return false;
     571             :     }
     572             : 
     573           0 :     return true;
     574             : }
     575             : 
     576             : // Helper function for param conversions which are easier to be done in the motors class
     577             : // Called from system.cpp
     578           0 : void AP_MotorsHeli_Dual::heli_motors_param_conversions(void)
     579             : {
     580             :     // Run common conversions from base class
     581           0 :     AP_MotorsHeli::heli_motors_param_conversions();
     582             : 
     583           0 :     if (_swashplate1.get_swash_type() == SWASHPLATE_TYPE_H3) {
     584             :         // PARAMETER_CONVERSION - Added: Mar-2026
     585             :         // Previous versions had phase angle for only the generic H3 swashplate.  Phase angle is now available for all swashplate 
     586             :         // types but needs to be converted for users of the generic H3 type.  Convert old H3 phase angle parameter to new parameter
     587             :         // applicable to all swashplate types.
     588           0 :         const AP_Param::ConversionInfo sw1_phase_conversion_info[] = {
     589             :             { 90, 532, AP_PARAM_INT16,  "H_SW_PHANG" },
     590             :         };
     591           0 :         uint8_t table_size = ARRAY_SIZE(sw1_phase_conversion_info);
     592           0 :         for (uint8_t i=0; i<table_size; i++) {
     593           0 :             AP_Param::convert_old_parameter(&sw1_phase_conversion_info[i], 1.0);
     594             :         }
     595             :     }
     596           0 :     if (_swashplate2.get_swash_type() == SWASHPLATE_TYPE_H3) {
     597             :         // PARAMETER_CONVERSION - Added: Mar-2026
     598             :         // Previous versions had phase angle for only the generic H3 swashplate.  Phase angle is now available for all swashplate 
     599             :         // types but needs to be converted for users of the generic H3 type.  Convert old H3 phase angle parameter to new parameter
     600             :         // applicable to all swashplate types.
     601           0 :         const AP_Param::ConversionInfo sw2_phase_conversion_info[] = {
     602             :             { 90, 533, AP_PARAM_INT16,  "H_SW2_PHANG" },
     603             :         };
     604           0 :         uint8_t table_size = ARRAY_SIZE(sw2_phase_conversion_info);
     605           0 :         for (uint8_t i=0; i<table_size; i++) {
     606           0 :             AP_Param::convert_old_parameter(&sw2_phase_conversion_info[i], 1.0);
     607             :         }
     608             :     }
     609             : 
     610           0 : }
     611             : 
     612             : #if HAL_LOGGING_ENABLED
     613             : // heli motors logging - called at 10 Hz
     614           0 : void AP_MotorsHeli_Dual::Log_Write(void)
     615             : {
     616             :     // write swashplate log
     617           0 :     _swashplate1.write_log(get_cyclic_angle_scaler(), _collective_min_deg.get(), _collective_max_deg.get(), _collective_min.get(), _collective_max.get());
     618           0 :     _swashplate2.write_log(get_cyclic_angle_scaler(), _collective_min_deg.get(), _collective_max_deg.get(), _collective2_min.get(), _collective2_max.get());
     619             : 
     620             :     // write RSC log
     621           0 :     _main_rotor.write_log();
     622           0 : }
     623             : #endif

Generated by: LCOV version 1.14