2015-07-22 10:46:53 -03:00
/*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
# include <stdlib.h>
2015-08-15 13:42:46 -03:00
# include <AP_HAL/AP_HAL.h>
2017-01-03 05:56:57 -04:00
# include <SRV_Channel/SRV_Channel.h>
2015-07-22 10:46:53 -03:00
# include "AP_MotorsHeli_Single.h"
2015-11-30 16:16:04 -04:00
# include <GCS_MAVLink/GCS.h>
2015-07-22 10:46:53 -03:00
extern const AP_HAL : : HAL & hal ;
2015-10-25 14:03:46 -03:00
const AP_Param : : GroupInfo AP_MotorsHeli_Single : : var_info [ ] = {
2015-07-22 10:46:53 -03:00
AP_NESTEDGROUPINFO ( AP_MotorsHeli , 0 ) ,
2018-03-08 13:16:31 -04:00
2019-01-12 00:17:15 -04:00
// Indices 1-3 were used by servo position params and should not be used
2018-03-08 13:16:31 -04:00
2015-07-22 10:46:53 -03:00
// @Param: TAIL_TYPE
// @DisplayName: Tail Type
2019-12-10 00:05:35 -04:00
// @Description: Tail type selection. Simpler yaw controller used if external gyro is selected. Direct Drive Variable Pitch is used for tails that have a motor that is governed at constant speed by an ESC. Tail pitch is still accomplished with a servo. Direct Drive Fixed Pitch (DDFP) CW is used for helicopters with a rotor that spins clockwise when viewed from above. Direct Drive Fixed Pitch (DDFP) CCW is used for helicopters with a rotor that spins counter clockwise when viewed from above. In both DDFP cases, no servo is used for the tail and the tail motor esc is controlled by the yaw axis.
2020-04-01 11:40:53 -03:00
// @Values: 0:Servo only,1:Servo with ExtGyro,2:DirectDrive VarPitch,3:DirectDrive FixedPitch CW,4:DirectDrive FixedPitch CCW,5:DDVP with external governor
2015-07-22 10:46:53 -03:00
// @User: Standard
AP_GROUPINFO ( " TAIL_TYPE " , 4 , AP_MotorsHeli_Single , _tail_type , AP_MOTORS_HELI_SINGLE_TAILTYPE_SERVO ) ,
2019-03-16 11:20:04 -03:00
// Indice 5 was used by SWASH_TYPE and should not be used
2015-07-22 10:46:53 -03:00
// @Param: GYR_GAIN
// @DisplayName: External Gyro Gain
2017-05-15 20:21:53 -03:00
// @Description: PWM in microseconds sent to external gyro on ch7 when tail type is Servo w/ ExtGyro
2015-07-22 10:46:53 -03:00
// @Range: 0 1000
// @Units: PWM
// @Increment: 1
// @User: Standard
2015-08-09 08:02:54 -03:00
AP_GROUPINFO ( " GYR_GAIN " , 6 , AP_MotorsHeli_Single , _ext_gyro_gain_std , AP_MOTORS_HELI_SINGLE_EXT_GYRO_GAIN ) ,
2015-07-22 10:46:53 -03:00
2019-01-12 00:17:15 -04:00
// Index 7 was used for phase angle and should not be used
2015-07-22 10:46:53 -03:00
2022-12-21 20:53:04 -04:00
// Indice 8 was used by COLYAW and should not be used
2015-07-22 10:46:53 -03:00
// @Param: FLYBAR_MODE
// @DisplayName: Flybar Mode Selector
// @Description: Flybar present or not. Affects attitude controller used during ACRO flight mode
2016-03-17 02:03:36 -03:00
// @Values: 0:NoFlybar,1:Flybar
2015-07-22 10:46:53 -03:00
// @User: Standard
AP_GROUPINFO ( " FLYBAR_MODE " , 9 , AP_MotorsHeli_Single , _flybar_mode , AP_MOTORS_HELI_NOFLYBAR ) ,
2018-03-08 13:16:31 -04:00
2015-07-22 10:46:53 -03:00
// @Param: TAIL_SPEED
2019-11-15 17:54:18 -04:00
// @DisplayName: DDVP Tail ESC speed
// @Description: Direct drive, variable pitch tail ESC speed in percent output to the tail motor esc (HeliTailRSC Servo) when motor interlock enabled (throttle hold off).
// @Range: 0 100
// @Units: %
2015-07-22 10:46:53 -03:00
// @Increment: 1
// @User: Standard
2018-03-08 13:16:31 -04:00
AP_GROUPINFO ( " TAIL_SPEED " , 10 , AP_MotorsHeli_Single , _direct_drive_tailspeed , AP_MOTORS_HELI_SINGLE_DDVP_SPEED_DEFAULT ) ,
2015-07-22 10:46:53 -03:00
2015-08-09 08:02:54 -03:00
// @Param: GYR_GAIN_ACRO
2019-12-07 18:50:33 -04:00
// @DisplayName: ACRO External Gyro Gain
2017-05-15 20:21:53 -03:00
// @Description: PWM in microseconds sent to external gyro on ch7 when tail type is Servo w/ ExtGyro. A value of zero means to use H_GYR_GAIN
2015-08-09 08:02:54 -03:00
// @Range: 0 1000
// @Units: PWM
// @Increment: 1
// @User: Standard
AP_GROUPINFO ( " GYR_GAIN_ACRO " , 11 , AP_MotorsHeli_Single , _ext_gyro_gain_acro , 0 ) ,
2016-02-08 07:11:55 -04:00
2019-03-16 11:20:04 -03:00
// Indices 16-19 were used by RSC_PWM_MIN, RSC_PWM_MAX, RSC_PWM_REV, and COL_CTRL_DIR and should not be used
2018-03-28 12:20:59 -03:00
2019-09-12 17:07:11 -03:00
// @Param: SW_TYPE
2019-12-07 18:50:33 -04:00
// @DisplayName: Swashplate Type
2019-09-12 17:07:11 -03:00
// @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
// @Values: 0:H3 Generic,1:H1 non-CPPM,2:H3_140,3:H3_120,4:H4_90,5:H4_45
// @User: Standard
// @Param: SW_COL_DIR
2019-12-07 18:50:33 -04:00
// @DisplayName: Collective Direction
2019-09-12 17:07:11 -03:00
// @Description: Direction collective moves for positive pitch. 0 for Normal, 1 for Reversed
// @Values: 0:Normal,1:Reversed
// @User: Standard
// @Param: SW_LIN_SVO
2019-12-07 18:50:33 -04:00
// @DisplayName: Linearize Swash Servos
// @Description: This linearizes the swashplate 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.
2019-09-12 17:07:11 -03:00
// @Values: 0:Disabled,1:Enabled
// @User: Standard
// @Param: SW_H3_ENABLE
2019-12-07 18:50:33 -04:00
// @DisplayName: H3 Generic Enable
// @Description: Automatically set when H3 generic swash type is selected for swashplate. Do not set manually.
2019-09-12 17:07:11 -03:00
// @Values: 0:Disabled,1:Enabled
// @User: Advanced
// @Param: SW_H3_SV1_POS
2019-12-07 18:50:33 -04:00
// @DisplayName: H3 Generic Servo 1 Position
2019-09-12 17:07:11 -03:00
// @Description: Azimuth position on swashplate for servo 1 with the front of the heli being 0 deg
// @Range: -180 180
// @Units: deg
// @User: Advanced
// @Param: SW_H3_SV2_POS
2019-12-07 18:50:33 -04:00
// @DisplayName: H3 Generic Servo 2 Position
// @Description: Azimuth position on swashplate for servo 2 with the front of the heli being 0 deg
2019-09-12 17:07:11 -03:00
// @Range: -180 180
// @Units: deg
// @User: Advanced
// @Param: SW_H3_SV3_POS
2019-12-07 18:50:33 -04:00
// @DisplayName: H3 Generic Servo 3 Position
// @Description: Azimuth position on swashplate for servo 3 with the front of the heli being 0 deg
2019-09-12 17:07:11 -03:00
// @Range: -180 180
// @Units: deg
// @User: Advanced
2021-02-07 14:30:05 -04:00
2019-09-12 17:07:11 -03:00
// @Param: SW_H3_PHANG
2019-12-07 18:50:33 -04:00
// @DisplayName: H3 Generic Phase Angle Comp
2019-09-12 17:07:11 -03:00
// @Description: Only for H3 swashplate. If pitching the swash forward induces a roll, this can be correct the problem
// @Range: -30 30
// @Units: deg
// @User: Advanced
// @Increment: 1
2019-03-16 11:20:04 -03:00
AP_SUBGROUPINFO ( _swashplate , " SW_ " , 20 , AP_MotorsHeli_Single , AP_MotorsHeli_Swash ) ,
2019-01-12 00:17:15 -04:00
2022-12-21 20:53:04 -04:00
// @Param: COL2YAW
// @DisplayName: Collective-Yaw Mixing
// @Description: Feed-forward compensation to automatically add rudder input when collective pitch is increased. Can be positive or negative depending on mechanics.
// @Range: -2 2
// @Increment: 0.1
// @User: Standard
AP_GROUPINFO ( " COL2YAW " , 21 , AP_MotorsHeli_Single , _collective_yaw_scale , 0 ) ,
2023-05-24 21:11:08 -03:00
// @Param: DDFP_THST_EXPO
// @DisplayName: DDFP Tail Rotor Thrust Curve Expo
// @Description: Tail rotor DDFP motor thrust curve exponent (0.0 for linear to 1.0 for second order curve)
// @Range: -1 1
// @User: Standard
// @Param: DDFP_SPIN_MIN
// @DisplayName: DDFP Tail Rotor Motor Spin minimum
2023-08-05 13:35:50 -03:00
// @Description: Point at which the thrust starts expressed as a number from 0 to 1 in the entire output range.
2023-05-24 21:11:08 -03:00
// @Values: 0.0:Low, 0.15:Default, 0.3:High
// @User: Standard
// @Param: DDFP_SPIN_MAX
// @DisplayName: DDFP Tail Rotor Motor Spin maximum
// @Description: Point at which the thrust saturates expressed as a number from 0 to 1 in the entire output range
// @Values: 0.9:Low, 0.95:Default, 1.0:High
// @User: Standard
// @Param: DDFP_BAT_IDX
// @DisplayName: DDFP Tail Rotor Battery compensation index
// @Description: Which battery monitor should be used for doing compensation
// @Values: 0:First battery, 1:Second battery
// @User: Standard
// @Param: DDFP_BAT_V_MAX
// @DisplayName: Battery voltage compensation maximum voltage
// @Description: Battery voltage compensation maximum voltage (voltage above this will have no additional scaling effect on thrust). Recommend 4.2 * cell count, 0 = Disabled
// @Range: 6 53
// @Units: V
// @User: Standard
// @Param: DDFP_BAT_V_MIN
// @DisplayName: Battery voltage compensation minimum voltage
// @Description: Battery voltage compensation minimum voltage (voltage below this will have no additional scaling effect on thrust). Recommend 3.3 * cell count, 0 = Disabled
// @Range: 6 42
// @Units: V
// @User: Standard
2023-06-03 00:30:17 -03:00
AP_SUBGROUPINFO ( thr_lin , " DDFP_ " , 22 , AP_MotorsHeli_Single , Thrust_Linearization ) ,
2023-05-24 21:11:08 -03:00
2015-07-22 10:46:53 -03:00
AP_GROUPEND
} ;
2018-07-11 16:33:00 -03:00
# define YAW_SERVO_MAX_ANGLE 4500
2015-07-22 10:46:53 -03:00
// set update rate to motors - a value in hertz
void AP_MotorsHeli_Single : : set_update_rate ( uint16_t speed_hz )
{
// record requested speed
_speed_hz = speed_hz ;
// setup fast channels
2023-05-04 21:04:41 -03:00
uint32_t mask = ( 1U < < AP_MOTORS_MOT_4 ) | _swashplate . get_output_mask ( ) ;
2016-01-04 06:24:06 -04:00
rc_set_freq ( mask , _speed_hz ) ;
2015-07-22 10:46:53 -03:00
}
2015-08-12 12:41:40 -03:00
// init_outputs - initialise Servo/PWM ranges and endpoints
2023-06-30 15:32:10 -03:00
void AP_MotorsHeli_Single : : init_outputs ( )
2015-08-12 12:41:40 -03:00
{
2019-05-03 02:27:09 -03:00
if ( ! initialised_ok ( ) ) {
2018-07-11 16:33:00 -03:00
// yaw servo
add_motor_num ( CH_4 ) ;
// initialize main rotor servo
_main_rotor . init_servo ( ) ;
2020-04-01 11:40:53 -03:00
if ( _tail_type = = AP_MOTORS_HELI_SINGLE_TAILTYPE_DIRECTDRIVE_VARPITCH | | _tail_type = = AP_MOTORS_HELI_SINGLE_TAILTYPE_DIRECTDRIVE_VARPIT_EXT_GOV ) {
2017-11-25 20:15:42 -04:00
_tail_rotor . init_servo ( ) ;
2018-07-11 16:33:00 -03:00
} else if ( _tail_type = = AP_MOTORS_HELI_SINGLE_TAILTYPE_SERVO_EXTGYRO ) {
// external gyro output
add_motor_num ( AP_MOTORS_HELI_SINGLE_EXTGYRO ) ;
2017-01-03 05:56:57 -04:00
}
}
2019-02-03 19:19:13 -04:00
if ( _tail_type = = AP_MOTORS_HELI_SINGLE_TAILTYPE_SERVO_EXTGYRO ) {
2018-07-11 16:33:00 -03:00
// External Gyro uses PWM output thus servo endpoints are forced
SRV_Channels : : set_output_min_max ( SRV_Channels : : get_motor_function ( AP_MOTORS_HELI_SINGLE_EXTGYRO ) , 1000 , 2000 ) ;
}
2023-06-04 06:34:22 -03:00
if ( _tail_type ! = AP_MOTORS_HELI_SINGLE_TAILTYPE_DIRECTDRIVE_VARPITCH & & _tail_type ! = AP_MOTORS_HELI_SINGLE_TAILTYPE_DIRECTDRIVE_VARPIT_EXT_GOV ) {
2023-05-24 21:11:08 -03:00
// yaw servo is an angle from -4500 to 4500
SRV_Channels : : set_angle ( SRV_Channel : : k_motor4 , YAW_SERVO_MAX_ANGLE ) ;
}
2015-08-12 12:41:40 -03:00
2022-05-11 09:36:39 -03:00
set_initialised_ok ( _frame_class = = MOTOR_FRAME_HELI ) ;
2015-07-22 10:46:53 -03:00
}
// set_desired_rotor_speed
2016-02-03 04:59:44 -04:00
void AP_MotorsHeli_Single : : set_desired_rotor_speed ( float desired_speed )
2015-07-22 10:46:53 -03:00
{
_main_rotor . set_desired_speed ( desired_speed ) ;
2018-03-08 13:16:31 -04:00
// always send desired speed to tail rotor control, will do nothing if not DDVP not enabled
2019-08-07 23:52:17 -03:00
_tail_rotor . set_desired_speed ( _direct_drive_tailspeed * 0.01f ) ;
2015-07-22 10:46:53 -03:00
}
2015-11-14 18:14:03 -04:00
// calculate_scalars - recalculates various scalers used.
void AP_MotorsHeli_Single : : calculate_armed_scalars ( )
{
2019-02-14 20:28:48 -04:00
// Set rsc mode specific parameters
2021-01-20 17:09:07 -04:00
if ( _main_rotor . _rsc_mode . get ( ) = = ROTOR_CONTROL_MODE_THROTTLECURVE | | _main_rotor . _rsc_mode . get ( ) = = ROTOR_CONTROL_MODE_AUTOTHROTTLE ) {
2019-08-07 23:52:17 -03:00
_main_rotor . set_throttle_curve ( ) ;
}
// keeps user from changing RSC mode while armed
if ( _main_rotor . _rsc_mode . get ( ) ! = _main_rotor . get_control_mode ( ) ) {
_main_rotor . reset_rsc_mode_param ( ) ;
_heliflags . save_rsc_mode = true ;
}
// saves rsc mode parameter when disarmed if it had been reset while armed
2019-05-03 02:27:09 -03:00
if ( _heliflags . save_rsc_mode & & ! armed ( ) ) {
2019-08-07 23:52:17 -03:00
_main_rotor . _rsc_mode . save ( ) ;
_heliflags . save_rsc_mode = false ;
2019-02-14 20:28:48 -04:00
}
2022-03-24 12:39:41 -03:00
2020-04-01 11:40:53 -03:00
// allow use of external governor autorotation bailout
2022-03-24 12:39:41 -03:00
if ( _heliflags . in_autorotation ) {
_main_rotor . set_autorotation_flag ( _heliflags . in_autorotation ) ;
// set bailout ramp time
_main_rotor . use_bailout_ramp_time ( _heliflags . enable_bailout ) ;
2023-02-18 00:49:06 -04:00
if ( _tail_type = = AP_MOTORS_HELI_SINGLE_TAILTYPE_DIRECTDRIVE_VARPITCH | | _tail_type = = AP_MOTORS_HELI_SINGLE_TAILTYPE_DIRECTDRIVE_VARPIT_EXT_GOV ) {
2021-02-08 13:21:02 -04:00
_tail_rotor . set_autorotation_flag ( _heliflags . in_autorotation ) ;
2022-03-24 12:39:41 -03:00
_tail_rotor . use_bailout_ramp_time ( _heliflags . enable_bailout ) ;
2020-04-01 11:40:53 -03:00
}
2022-03-24 12:39:41 -03:00
} else {
_main_rotor . set_autorotation_flag ( false ) ;
2023-02-18 00:49:06 -04:00
if ( _tail_type = = AP_MOTORS_HELI_SINGLE_TAILTYPE_DIRECTDRIVE_VARPITCH | | _tail_type = = AP_MOTORS_HELI_SINGLE_TAILTYPE_DIRECTDRIVE_VARPIT_EXT_GOV ) {
_tail_rotor . set_autorotation_flag ( false ) ;
}
2020-04-01 11:40:53 -03:00
}
2019-02-14 20:28:48 -04:00
}
2015-11-14 18:14:03 -04:00
2015-08-12 12:41:40 -03:00
// calculate_scalars - recalculates various scalers used.
void AP_MotorsHeli_Single : : calculate_scalars ( )
2015-07-22 10:46:53 -03:00
{
2021-10-21 19:03:36 -03:00
// range check collective min, max and zero
2015-08-12 12:41:40 -03:00
if ( _collective_min > = _collective_max ) {
2022-07-05 00:08:56 -03:00
_collective_min . set ( AP_MOTORS_HELI_COLLECTIVE_MIN ) ;
_collective_max . set ( AP_MOTORS_HELI_COLLECTIVE_MAX ) ;
2015-08-12 12:41:40 -03:00
}
2022-07-05 00:08:56 -03:00
_collective_zero_thrust_deg . set ( constrain_float ( _collective_zero_thrust_deg , _collective_min_deg , _collective_max_deg ) ) ;
2021-10-21 19:03:36 -03:00
2022-07-05 00:08:56 -03:00
_collective_land_min_deg . set ( constrain_float ( _collective_land_min_deg , _collective_min_deg , _collective_max_deg ) ) ;
2021-10-21 19:03:36 -03:00
2021-10-25 23:25:59 -03:00
if ( ! is_equal ( ( float ) _collective_max_deg , ( float ) _collective_min_deg ) ) {
// calculate collective zero thrust point as a number from 0 to 1
2021-10-29 09:43:52 -03:00
_collective_zero_thrust_pct = ( _collective_zero_thrust_deg - _collective_min_deg ) / ( _collective_max_deg - _collective_min_deg ) ;
2021-10-25 23:25:59 -03:00
// calculate collective land min point as a number from 0 to 1
_collective_land_min_pct = ( _collective_land_min_deg - _collective_min_deg ) / ( _collective_max_deg - _collective_min_deg ) ;
} else {
2021-10-29 09:43:52 -03:00
_collective_zero_thrust_pct = 0.0f ;
2021-10-25 23:25:59 -03:00
_collective_land_min_pct = 0.0f ;
}
2015-08-12 12:41:40 -03:00
2019-01-12 00:17:15 -04:00
// configure swashplate and update scalars
2019-03-16 11:20:04 -03:00
_swashplate . configure ( ) ;
2015-08-12 12:41:40 -03:00
// send setpoints to main rotor controller and trigger recalculation of scalars
2019-08-07 23:52:17 -03:00
_main_rotor . set_control_mode ( static_cast < RotorControlMode > ( _main_rotor . _rsc_mode . get ( ) ) ) ;
2015-11-14 18:14:03 -04:00
calculate_armed_scalars ( ) ;
2018-03-08 13:16:31 -04:00
// send setpoints to DDVP rotor controller and trigger recalculation of scalars
2020-04-01 11:40:53 -03:00
if ( _tail_type = = AP_MOTORS_HELI_SINGLE_TAILTYPE_DIRECTDRIVE_VARPITCH | | _tail_type = = AP_MOTORS_HELI_SINGLE_TAILTYPE_DIRECTDRIVE_VARPIT_EXT_GOV ) {
2021-01-20 17:09:07 -04:00
_tail_rotor . set_control_mode ( ROTOR_CONTROL_MODE_SETPOINT ) ;
2019-08-07 23:52:17 -03:00
_tail_rotor . set_ramp_time ( _main_rotor . _ramp_time . get ( ) ) ;
_tail_rotor . set_runup_time ( _main_rotor . _runup_time . get ( ) ) ;
_tail_rotor . set_critical_speed ( _main_rotor . _critical_speed . get ( ) ) ;
_tail_rotor . set_idle_output ( _main_rotor . _idle_output . get ( ) ) ;
2023-02-18 00:49:06 -04:00
_tail_rotor . set_arot_idle_output ( _main_rotor . _arot_idle_output . get ( ) ) ;
_tail_rotor . set_rsc_arot_man_enable ( _main_rotor . _rsc_arot_man_enable . get ( ) ) ;
_tail_rotor . set_rsc_arot_engage_time ( _main_rotor . _rsc_arot_engage_time . get ( ) ) ;
2015-08-11 15:31:20 -03:00
} else {
2015-08-28 03:23:26 -03:00
_tail_rotor . set_control_mode ( ROTOR_CONTROL_MODE_DISABLED ) ;
2015-08-11 15:31:20 -03:00
_tail_rotor . set_ramp_time ( 0 ) ;
_tail_rotor . set_runup_time ( 0 ) ;
_tail_rotor . set_critical_speed ( 0 ) ;
2015-08-11 21:20:28 -03:00
_tail_rotor . set_idle_output ( 0 ) ;
2023-02-18 00:49:06 -04:00
_tail_rotor . set_arot_idle_output ( 0 ) ;
_tail_rotor . set_rsc_arot_man_enable ( 0 ) ;
_tail_rotor . set_rsc_arot_engage_time ( 0 ) ;
2015-07-22 10:46:53 -03:00
}
}
// get_motor_mask - returns a bitmask of which outputs are being used for motors or servos (1 means being used)
// this can be used to ensure other pwm outputs (i.e. for servos) do not conflict
2021-12-10 12:45:20 -04:00
uint32_t AP_MotorsHeli_Single : : get_motor_mask ( )
2015-07-22 10:46:53 -03:00
{
2023-05-05 16:06:42 -03:00
return _main_rotor . get_output_mask ( ) | _tail_rotor . get_output_mask ( ) ;
2015-07-22 10:46:53 -03:00
}
2015-08-12 14:22:39 -03:00
// update_motor_controls - sends commands to motor controllers
2015-08-28 03:23:26 -03:00
void AP_MotorsHeli_Single : : update_motor_control ( RotorControlState state )
2015-08-11 16:12:16 -03:00
{
2015-08-12 14:22:39 -03:00
// Send state update to motors
_tail_rotor . output ( state ) ;
_main_rotor . output ( state ) ;
2015-08-11 16:12:16 -03:00
2015-10-14 12:19:16 -03:00
if ( state = = ROTOR_CONTROL_STOP ) {
// set engine run enable aux output to not run position to kill engine when disarmed
2019-11-24 21:52:18 -04:00
SRV_Channels : : set_output_limit ( SRV_Channel : : k_engine_run_enable , SRV_Channel : : Limit : : MIN ) ;
2015-10-14 12:19:16 -03:00
} else {
// else if armed, set engine run enable output to run position
2019-11-24 21:52:18 -04:00
SRV_Channels : : set_output_limit ( SRV_Channel : : k_engine_run_enable , SRV_Channel : : Limit : : MAX ) ;
2015-10-14 12:19:16 -03:00
}
2015-08-12 14:22:39 -03:00
// Check if both rotors are run-up, tail rotor controller always returns true if not enabled
_heliflags . rotor_runup_complete = ( _main_rotor . is_runup_complete ( ) & & _tail_rotor . is_runup_complete ( ) ) ;
2021-12-05 19:57:05 -04:00
// Check if both rotors are spooled down, tail rotor controller always returns true if not enabled
_heliflags . rotor_spooldown_complete = ( _main_rotor . is_spooldown_complete ( ) ) ;
2015-07-22 10:46:53 -03:00
}
//
2015-08-12 12:41:40 -03:00
// move_actuators - moves swash plate and tail rotor
2015-07-22 10:46:53 -03:00
// - expected ranges:
2016-02-02 08:24:39 -04:00
// roll : -1 ~ +1
// pitch: -1 ~ +1
// collective: 0 ~ 1
// yaw: -1 ~ +1
2015-07-22 10:46:53 -03:00
//
2016-02-02 08:24:39 -04:00
void AP_MotorsHeli_Single : : move_actuators ( float roll_out , float pitch_out , float coll_in , float yaw_out )
2015-07-22 10:46:53 -03:00
{
2016-02-02 08:24:39 -04:00
float yaw_offset = 0.0f ;
2015-07-22 10:46:53 -03:00
// initialize limits flag
limit . throttle_lower = false ;
limit . throttle_upper = false ;
2017-08-27 20:51:33 -03:00
if ( _heliflags . inverted_flight ) {
coll_in = 1 - coll_in ;
}
2021-02-07 14:30:05 -04:00
2014-09-18 22:54:26 -03:00
// rescale roll_out and pitch_out into the min and max ranges to provide linear motion
2015-08-12 12:41:40 -03:00
// across the input range instead of stopping when the input hits the constrain value
2014-09-18 22:54:26 -03:00
// these calculations are based on an assumption of the user specified cyclic_max
2016-02-02 08:24:39 -04:00
// coming into this equation at 4500 or less
2016-04-16 06:58:46 -03:00
float total_out = norm ( pitch_out , roll_out ) ;
2014-09-18 22:54:26 -03:00
2016-02-02 08:24:39 -04:00
if ( total_out > ( _cyclic_max / 4500.0f ) ) {
float ratio = ( float ) ( _cyclic_max / 4500.0f ) / total_out ;
2014-09-18 22:54:26 -03:00
roll_out * = ratio ;
pitch_out * = ratio ;
2019-07-27 02:37:31 -03:00
limit . roll = true ;
limit . pitch = true ;
2015-08-12 12:41:40 -03:00
}
2015-07-22 10:46:53 -03:00
2015-08-12 12:41:40 -03:00
// constrain collective input
2016-02-02 08:24:39 -04:00
float collective_out = coll_in ;
if ( collective_out < = 0.0f ) {
collective_out = 0.0f ;
2015-08-12 12:41:40 -03:00
limit . throttle_lower = true ;
}
2016-02-02 08:24:39 -04:00
if ( collective_out > = 1.0f ) {
collective_out = 1.0f ;
2015-08-12 12:41:40 -03:00
limit . throttle_upper = true ;
}
2015-07-22 10:46:53 -03:00
2015-08-12 12:41:40 -03:00
// ensure not below landed/landing collective
2021-10-21 19:03:36 -03:00
if ( _heliflags . landing_collective & & collective_out < _collective_land_min_pct & & ! _heliflags . in_autorotation ) {
collective_out = _collective_land_min_pct ;
2015-08-12 12:41:40 -03:00
limit . throttle_lower = true ;
2020-12-20 17:42:06 -04:00
}
2021-10-29 09:43:52 -03:00
// updates below land min collective flag
2021-10-21 19:03:36 -03:00
if ( collective_out < = _collective_land_min_pct ) {
2021-10-29 09:43:52 -03:00
_heliflags . below_land_min_coll = true ;
2020-12-20 17:42:06 -04:00
} else {
2021-10-29 09:43:52 -03:00
_heliflags . below_land_min_coll = false ;
2015-08-12 12:41:40 -03:00
}
2015-07-22 10:46:53 -03:00
2020-12-01 17:35:45 -04:00
// updates takeoff collective flag based on 50% hover collective
update_takeoff_collective_flag ( collective_out ) ;
2020-11-15 19:32:31 -04:00
2019-11-28 16:23:47 -04:00
// if servo output not in manual mode and heli is not in autorotation, process pre-compensation factors
if ( _servo_mode = = SERVO_CONTROL_MODE_AUTOMATED & & ! _heliflags . in_autorotation ) {
2015-07-22 10:46:53 -03:00
// rudder feed forward based on collective
2015-08-10 17:25:28 -03:00
// the feed-forward is not required when the motor is stopped or at idle, and thus not creating torque
2015-07-22 10:46:53 -03:00
// also not required if we are using external gyro
2023-04-29 19:10:43 -03:00
if ( ( get_control_output ( ) > _main_rotor . get_idle_output ( ) ) & & _tail_type ! = AP_MOTORS_HELI_SINGLE_TAILTYPE_SERVO_EXTGYRO ) {
2022-12-21 20:53:04 -04:00
// sanity check collective_yaw_scale
_collective_yaw_scale . set ( constrain_float ( _collective_yaw_scale , - AP_MOTORS_HELI_SINGLE_COLYAW_RANGE , AP_MOTORS_HELI_SINGLE_COLYAW_RANGE ) ) ;
// This feedforward compensation follows the hover performance theory that hover power required
// is a function of gross weight to the 3/2 power
yaw_offset = _collective_yaw_scale * powf ( fabsf ( collective_out - _collective_zero_thrust_pct ) , 1.5f ) ;
2015-07-22 10:46:53 -03:00
}
2015-08-12 12:41:40 -03:00
} else {
2016-02-02 08:24:39 -04:00
yaw_offset = 0.0f ;
2015-07-22 10:46:53 -03:00
}
2015-08-11 21:20:28 -03:00
// feed power estimate into main rotor controller
// ToDo: include tail rotor power?
// ToDo: add main rotor cyclic power?
2018-03-23 01:09:14 -03:00
_main_rotor . set_collective ( fabsf ( collective_out ) ) ;
2015-08-11 21:20:28 -03:00
2018-03-28 12:20:59 -03:00
// scale collective pitch for swashplate servos
2018-03-23 01:09:14 -03:00
float collective_scalar = ( ( float ) ( _collective_max - _collective_min ) ) * 0.001f ;
2018-03-28 12:20:59 -03:00
float collective_out_scaled = collective_out * collective_scalar + ( _collective_min - 1000 ) * 0.001f ;
2023-05-04 20:41:08 -03:00
// Caculate servo positions from swashplate library
_swashplate . calculate ( roll_out , pitch_out , collective_out_scaled ) ;
2015-09-28 19:03:32 -03:00
2015-07-22 10:46:53 -03:00
// update the yaw rate using the tail rotor/servo
2015-08-06 05:25:31 -03:00
move_yaw ( yaw_out + yaw_offset ) ;
2015-07-22 10:46:53 -03:00
}
2015-08-06 05:25:31 -03:00
// move_yaw
2016-02-02 08:25:31 -04:00
void AP_MotorsHeli_Single : : move_yaw ( float yaw_out )
2015-07-22 10:46:53 -03:00
{
2016-02-02 08:25:31 -04:00
// sanity check yaw_out
if ( yaw_out < - 1.0f ) {
yaw_out = - 1.0f ;
limit . yaw = true ;
}
if ( yaw_out > 1.0f ) {
yaw_out = 1.0f ;
2015-07-22 10:46:53 -03:00
limit . yaw = true ;
}
2018-12-28 02:32:05 -04:00
_servo4_out = yaw_out ;
}
void AP_MotorsHeli_Single : : output_to_motors ( )
{
2019-05-03 02:27:09 -03:00
if ( ! initialised_ok ( ) ) {
2018-12-28 02:32:05 -04:00
return ;
}
2023-05-04 20:41:08 -03:00
// Write swashplate outputs
_swashplate . output ( ) ;
2019-12-10 00:05:35 -04:00
if ( _tail_type ! = AP_MOTORS_HELI_SINGLE_TAILTYPE_DIRECTDRIVE_FIXEDPITCH_CW & & _tail_type ! = AP_MOTORS_HELI_SINGLE_TAILTYPE_DIRECTDRIVE_FIXEDPITCH_CCW ) {
2018-12-28 02:32:05 -04:00
rc_write_angle ( AP_MOTORS_MOT_4 , _servo4_out * YAW_SERVO_MAX_ANGLE ) ;
2017-09-20 09:59:20 -03:00
}
2015-07-22 10:46:53 -03:00
if ( _tail_type = = AP_MOTORS_HELI_SINGLE_TAILTYPE_SERVO_EXTGYRO ) {
// output gain to exernal gyro
2015-08-09 08:02:54 -03:00
if ( _acro_tail & & _ext_gyro_gain_acro > 0 ) {
2018-07-11 16:33:00 -03:00
rc_write ( AP_MOTORS_HELI_SINGLE_EXTGYRO , 1000 + _ext_gyro_gain_acro ) ;
2015-08-09 08:02:54 -03:00
} else {
2018-07-11 16:33:00 -03:00
rc_write ( AP_MOTORS_HELI_SINGLE_EXTGYRO , 1000 + _ext_gyro_gain_std ) ;
2015-08-09 08:02:54 -03:00
}
2015-07-22 10:46:53 -03:00
}
2018-12-28 02:32:05 -04:00
2019-12-10 00:05:35 -04:00
if ( _tail_type = = AP_MOTORS_HELI_SINGLE_TAILTYPE_DIRECTDRIVE_FIXEDPITCH_CCW ) {
_servo4_out = - _servo4_out ;
}
2023-05-24 21:11:08 -03:00
if ( _tail_type = = AP_MOTORS_HELI_SINGLE_TAILTYPE_DIRECTDRIVE_FIXEDPITCH_CW | | _tail_type = = AP_MOTORS_HELI_SINGLE_TAILTYPE_DIRECTDRIVE_FIXEDPITCH_CCW ) {
// calc filtered battery voltage and lift_max
thr_lin . update_lift_max_from_batt_voltage ( ) ;
}
2019-04-09 09:15:45 -03:00
switch ( _spool_state ) {
case SpoolState : : SHUT_DOWN :
2018-12-28 02:32:05 -04:00
// sends minimum values out to the motors
update_motor_control ( ROTOR_CONTROL_STOP ) ;
2019-12-10 00:05:35 -04:00
if ( _tail_type = = AP_MOTORS_HELI_SINGLE_TAILTYPE_DIRECTDRIVE_FIXEDPITCH_CW | | _tail_type = = AP_MOTORS_HELI_SINGLE_TAILTYPE_DIRECTDRIVE_FIXEDPITCH_CCW ) {
2023-05-24 21:11:08 -03:00
rc_write ( AP_MOTORS_MOT_4 , calculate_ddfp_output ( 0.0f ) ) ;
2018-12-28 02:32:05 -04:00
}
break ;
2019-04-09 09:15:45 -03:00
case SpoolState : : GROUND_IDLE :
2018-12-28 02:32:05 -04:00
// sends idle output to motors when armed. rotor could be static or turning (autorotation)
update_motor_control ( ROTOR_CONTROL_IDLE ) ;
2019-12-10 00:05:35 -04:00
if ( _tail_type = = AP_MOTORS_HELI_SINGLE_TAILTYPE_DIRECTDRIVE_FIXEDPITCH_CW | | _tail_type = = AP_MOTORS_HELI_SINGLE_TAILTYPE_DIRECTDRIVE_FIXEDPITCH_CCW ) {
2023-05-24 21:11:08 -03:00
rc_write ( AP_MOTORS_MOT_4 , calculate_ddfp_output ( 0.0f ) ) ;
2018-12-28 02:32:05 -04:00
}
break ;
2019-04-09 09:15:45 -03:00
case SpoolState : : SPOOLING_UP :
case SpoolState : : THROTTLE_UNLIMITED :
2018-12-28 02:32:05 -04:00
// set motor output based on thrust requests
update_motor_control ( ROTOR_CONTROL_ACTIVE ) ;
2019-12-10 00:05:35 -04:00
if ( _tail_type = = AP_MOTORS_HELI_SINGLE_TAILTYPE_DIRECTDRIVE_FIXEDPITCH_CW | | _tail_type = = AP_MOTORS_HELI_SINGLE_TAILTYPE_DIRECTDRIVE_FIXEDPITCH_CCW ) {
2023-06-04 06:34:22 -03:00
SRV_Channel * c = SRV_Channels : : srv_channel ( AP_MOTORS_MOT_4 ) ;
if ( c ! = nullptr ) {
_ddfp_pwm_min = c - > get_output_min ( ) ;
_ddfp_pwm_max = c - > get_output_max ( ) ;
_ddfp_pwm_trim = c - > get_trim ( ) ;
}
2023-05-24 21:11:08 -03:00
float servo_out = 0.0f ;
2023-06-04 06:34:22 -03:00
if ( is_positive ( ( float ) ( _ddfp_pwm_max - _ddfp_pwm_min ) ) ) {
float servo4_trim = constrain_float ( ( _ddfp_pwm_trim - 1000 ) / ( _ddfp_pwm_max - _ddfp_pwm_min ) , 0.0f , 1.0f ) ;
if ( is_positive ( _servo4_out ) ) {
servo_out = ( 1.0f - servo4_trim ) * _servo4_out + servo4_trim ;
} else {
servo_out = servo4_trim * _servo4_out + servo4_trim ;
}
2023-05-24 21:11:08 -03:00
} else {
2023-06-04 06:34:22 -03:00
// if servo pwm min and max are bad, convert servo4_out from -1 to 1 to 0 to 1
servo_out = 0.5f * ( _servo4_out + 1.0f ) ;
2023-06-03 00:30:17 -03:00
// this should never happen
INTERNAL_ERROR ( AP_InternalError : : error_t : : flow_of_control ) ;
2023-05-24 21:11:08 -03:00
}
2018-12-28 02:32:05 -04:00
// output yaw servo to tail rsc
2023-06-04 06:34:22 -03:00
rc_write ( AP_MOTORS_MOT_4 , calculate_ddfp_output ( thr_lin . thrust_to_actuator ( constrain_float ( servo_out , 0.0f , 1.0f ) ) ) ) ;
2018-12-28 02:32:05 -04:00
}
break ;
2019-04-09 09:15:45 -03:00
case SpoolState : : SPOOLING_DOWN :
2018-12-28 02:32:05 -04:00
// sends idle output to motors and wait for rotor to stop
update_motor_control ( ROTOR_CONTROL_IDLE ) ;
2019-12-10 00:05:35 -04:00
if ( _tail_type = = AP_MOTORS_HELI_SINGLE_TAILTYPE_DIRECTDRIVE_FIXEDPITCH_CW | | _tail_type = = AP_MOTORS_HELI_SINGLE_TAILTYPE_DIRECTDRIVE_FIXEDPITCH_CCW ) {
2023-05-24 21:11:08 -03:00
rc_write ( AP_MOTORS_MOT_4 , calculate_ddfp_output ( 0.0f ) ) ;
2018-12-28 02:32:05 -04:00
}
break ;
}
2015-07-22 10:46:53 -03:00
}
2023-05-24 21:11:08 -03:00
// calculate the motor output for DDFP tails from yaw_out
uint16_t AP_MotorsHeli_Single : : calculate_ddfp_output ( float yaw_out )
{
uint16_t ret = _ddfp_pwm_min + ( _ddfp_pwm_max - _ddfp_pwm_min ) * yaw_out ;
return ret ;
}
2015-10-21 17:00:36 -03:00
// servo_test - move servos through full range of movement
void AP_MotorsHeli_Single : : servo_test ( )
{
2022-12-06 21:03:36 -04:00
_servo_test_cycle_time + = _dt ;
2015-10-30 14:38:04 -03:00
if ( ( _servo_test_cycle_time > = 0.0f & & _servo_test_cycle_time < 0.5f ) | | // Tilt swash back
( _servo_test_cycle_time > = 6.0f & & _servo_test_cycle_time < 6.5f ) ) {
2022-12-06 21:03:36 -04:00
_pitch_test + = 2.0 * _dt ;
_oscillate_angle + = 8 * M_PI * _dt ;
2016-02-05 21:38:18 -04:00
_yaw_test = 0.5f * sinf ( _oscillate_angle ) ;
2015-10-30 14:38:04 -03:00
} else if ( ( _servo_test_cycle_time > = 0.5f & & _servo_test_cycle_time < 4.5f ) | | // Roll swash around
( _servo_test_cycle_time > = 6.5f & & _servo_test_cycle_time < 10.5f ) ) {
2022-12-06 21:03:36 -04:00
_oscillate_angle + = 0.5 * M_PI * _dt ;
2016-02-05 21:38:18 -04:00
_roll_test = sinf ( _oscillate_angle ) ;
_pitch_test = cosf ( _oscillate_angle ) ;
_yaw_test = sinf ( _oscillate_angle ) ;
2015-10-30 14:38:04 -03:00
} else if ( ( _servo_test_cycle_time > = 4.5f & & _servo_test_cycle_time < 5.0f ) | | // Return swash to level
( _servo_test_cycle_time > = 10.5f & & _servo_test_cycle_time < 11.0f ) ) {
2022-12-06 21:03:36 -04:00
_pitch_test - = 2.0 * _dt ;
_oscillate_angle + = 8 * M_PI * _dt ;
2016-02-05 21:38:18 -04:00
_yaw_test = 0.5f * sinf ( _oscillate_angle ) ;
2015-10-30 14:38:04 -03:00
} else if ( _servo_test_cycle_time > = 5.0f & & _servo_test_cycle_time < 6.0f ) { // Raise swash to top
2022-12-06 21:03:36 -04:00
_collective_test + = _dt ;
_oscillate_angle + = 2 * M_PI * _dt ;
2016-02-05 21:38:18 -04:00
_yaw_test = sinf ( _oscillate_angle ) ;
2015-10-30 14:38:04 -03:00
} else if ( _servo_test_cycle_time > = 11.0f & & _servo_test_cycle_time < 12.0f ) { // Lower swash to bottom
2022-12-06 21:03:36 -04:00
_collective_test - = _dt ;
_oscillate_angle + = 2 * M_PI * _dt ;
2016-02-05 21:38:18 -04:00
_yaw_test = sinf ( _oscillate_angle ) ;
2015-10-30 14:38:04 -03:00
} else { // reset cycle
_servo_test_cycle_time = 0.0f ;
_oscillate_angle = 0.0f ;
_collective_test = 0.0f ;
_roll_test = 0.0f ;
_pitch_test = 0.0f ;
_yaw_test = 0.0f ;
2015-10-21 20:47:24 -03:00
// decrement servo test cycle counter at the end of the cycle
if ( _servo_test_cycle_counter > 0 ) {
_servo_test_cycle_counter - - ;
}
2015-10-21 17:00:36 -03:00
}
// over-ride servo commands to move servos through defined ranges
2018-09-23 17:12:09 -03:00
_throttle_filter . reset ( constrain_float ( _collective_test , 0.0f , 1.0f ) ) ;
_roll_in = constrain_float ( _roll_test , - 1.0f , 1.0f ) ;
_pitch_in = constrain_float ( _pitch_test , - 1.0f , 1.0f ) ;
_yaw_in = constrain_float ( _yaw_test , - 1.0f , 1.0f ) ;
2015-10-21 17:00:36 -03:00
}
2015-11-30 16:16:04 -04:00
2023-07-18 17:16:48 -03:00
// Run arming checks
bool AP_MotorsHeli_Single : : arming_checks ( size_t buflen , char * buffer ) const
2015-11-30 16:16:04 -04:00
{
2023-07-18 17:16:48 -03:00
// run base class checks
if ( ! AP_MotorsHeli : : arming_checks ( buflen , buffer ) ) {
return false ;
}
2019-08-07 23:52:17 -03:00
// returns false if direct drive tailspeed is outside of range
2023-07-18 17:16:48 -03:00
if ( ( _direct_drive_tailspeed < 0 ) | | ( _direct_drive_tailspeed > 100 ) ) {
hal . util - > snprintf ( buffer , buflen , " H_TAIL_SPEED out of range " ) ;
2019-08-07 23:52:17 -03:00
return false ;
}
2019-01-12 00:17:15 -04:00
// returns false if Phase Angle is outside of range for H3 swashplate
2019-03-16 11:20:04 -03:00
if ( _swashplate . get_swash_type ( ) = = SWASHPLATE_TYPE_H3 & & ( _swashplate . get_phase_angle ( ) > 30 | | _swashplate . get_phase_angle ( ) < - 30 ) ) {
2023-07-18 17:16:48 -03:00
hal . util - > snprintf ( buffer , buflen , " H_SW_H3_PHANG out of range " ) ;
2015-11-30 16:16:04 -04:00
return false ;
}
// returns false if Acro External Gyro Gain is outside of range
2023-07-18 17:16:48 -03:00
if ( ( _ext_gyro_gain_acro < 0 ) | | ( _ext_gyro_gain_acro > 1000 ) ) {
hal . util - > snprintf ( buffer , buflen , " H_GYR_GAIN_ACRO out of range " ) ;
2015-11-30 16:16:04 -04:00
return false ;
}
// returns false if Standard External Gyro Gain is outside of range
2023-07-18 17:16:48 -03:00
if ( ( _ext_gyro_gain_std < 0 ) | | ( _ext_gyro_gain_std > 1000 ) ) {
hal . util - > snprintf ( buffer , buflen , " H_GYR_GAIN out of range " ) ;
2015-11-30 16:16:04 -04:00
return false ;
}
2023-07-18 17:16:48 -03:00
return true ;
2015-11-30 16:16:04 -04:00
}