/* 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 . */ /* * AP_MotorsTailsitter.cpp - ArduCopter motors library for tailsitters and bicopters * */ #include #include #include "AP_MotorsTailsitter.h" #include extern const AP_HAL::HAL& hal; #define SERVO_OUTPUT_RANGE 4500 // init void AP_MotorsTailsitter::init(motor_frame_class frame_class, motor_frame_type frame_type) { // setup default motor and servo mappings _has_diff_thrust = SRV_Channels::function_assigned(SRV_Channel::k_throttleRight) || SRV_Channels::function_assigned(SRV_Channel::k_throttleLeft); // right throttle defaults to servo output 1 SRV_Channels::set_aux_channel_default(SRV_Channel::k_throttleRight, CH_1); // left throttle defaults to servo output 2 SRV_Channels::set_aux_channel_default(SRV_Channel::k_throttleLeft, CH_2); // right servo defaults to servo output 3 SRV_Channels::set_aux_channel_default(SRV_Channel::k_tiltMotorRight, CH_3); SRV_Channels::set_angle(SRV_Channel::k_tiltMotorRight, SERVO_OUTPUT_RANGE); // left servo defaults to servo output 4 SRV_Channels::set_aux_channel_default(SRV_Channel::k_tiltMotorLeft, CH_4); SRV_Channels::set_angle(SRV_Channel::k_tiltMotorLeft, SERVO_OUTPUT_RANGE); _mav_type = MAV_TYPE_VTOL_DUOROTOR; // record successful initialisation if what we setup was the desired frame_class set_initialised_ok(frame_class == MOTOR_FRAME_TAILSITTER); } /// Constructor AP_MotorsTailsitter::AP_MotorsTailsitter(uint16_t loop_rate, uint16_t speed_hz) : AP_MotorsMulticopter(loop_rate, speed_hz) { set_update_rate(speed_hz); } // set update rate to motors - a value in hertz void AP_MotorsTailsitter::set_update_rate(uint16_t speed_hz) { // record requested speed _speed_hz = speed_hz; SRV_Channels::set_rc_frequency(SRV_Channel::k_throttleLeft, speed_hz); SRV_Channels::set_rc_frequency(SRV_Channel::k_throttleRight, speed_hz); } void AP_MotorsTailsitter::output_to_motors() { if (!initialised_ok()) { return; } switch (_spool_state) { case SpoolState::SHUT_DOWN: _actuator[0] = 0.0f; _actuator[1] = 0.0f; _actuator[2] = 0.0f; _external_min_throttle = 0.0; break; case SpoolState::GROUND_IDLE: set_actuator_with_slew(_actuator[0], actuator_spin_up_to_ground_idle()); set_actuator_with_slew(_actuator[1], actuator_spin_up_to_ground_idle()); set_actuator_with_slew(_actuator[2], actuator_spin_up_to_ground_idle()); _external_min_throttle = 0.0; break; case SpoolState::SPOOLING_UP: case SpoolState::THROTTLE_UNLIMITED: case SpoolState::SPOOLING_DOWN: set_actuator_with_slew(_actuator[0], thrust_to_actuator(_thrust_left)); set_actuator_with_slew(_actuator[1], thrust_to_actuator(_thrust_right)); set_actuator_with_slew(_actuator[2], thrust_to_actuator(_throttle)); break; } SRV_Channels::set_output_pwm(SRV_Channel::k_throttleLeft, output_to_pwm(_actuator[0])); SRV_Channels::set_output_pwm(SRV_Channel::k_throttleRight, output_to_pwm(_actuator[1])); // use set scaled to allow a different PWM range on plane forward throttle, throttle range is 0 to 100 SRV_Channels::set_output_scaled(SRV_Channel::k_throttle, _actuator[2]*100); SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorLeft, _tilt_left*SERVO_OUTPUT_RANGE); SRV_Channels::set_output_scaled(SRV_Channel::k_tiltMotorRight, _tilt_right*SERVO_OUTPUT_RANGE); } // get_motor_mask - returns a bitmask of which outputs are being used for motors (1 means being used) // this can be used to ensure other pwm outputs (i.e. for servos) do not conflict uint32_t AP_MotorsTailsitter::get_motor_mask() { uint32_t motor_mask = 0; uint8_t chan; if (SRV_Channels::find_channel(SRV_Channel::k_throttleLeft, chan)) { motor_mask |= 1U << chan; } if (SRV_Channels::find_channel(SRV_Channel::k_throttleRight, chan)) { motor_mask |= 1U << chan; } // add parent's mask motor_mask |= AP_MotorsMulticopter::get_motor_mask(); return motor_mask; } // calculate outputs to the motors void AP_MotorsTailsitter::output_armed_stabilizing() { float roll_thrust; // roll thrust input value, +/- 1.0 float pitch_thrust; // pitch thrust input value, +/- 1.0 float yaw_thrust; // yaw thrust input value, +/- 1.0 float throttle_thrust; // throttle thrust input value, 0.0 - 1.0 float thrust_max; // highest motor value float thrust_min; // lowest motor value float thr_adj = 0.0f; // the difference between the pilot's desired throttle and throttle_thrust_best_rpy // apply voltage and air pressure compensation const float compensation_gain = get_compensation_gain(); roll_thrust = (_roll_in + _roll_in_ff) * compensation_gain; pitch_thrust = _pitch_in + _pitch_in_ff; yaw_thrust = _yaw_in + _yaw_in_ff; throttle_thrust = get_throttle() * compensation_gain; const float max_boost_throttle = _throttle_avg_max * compensation_gain; // never boost above max, derived from throttle mix params const float min_throttle_out = MIN(_external_min_throttle, max_boost_throttle); const float max_throttle_out = _throttle_thrust_max * compensation_gain; // sanity check throttle is above min and below current limited throttle if (throttle_thrust <= min_throttle_out) { throttle_thrust = min_throttle_out; limit.throttle_lower = true; } if (throttle_thrust >= max_throttle_out) { throttle_thrust = max_throttle_out; limit.throttle_upper = true; } if (roll_thrust >= 1.0) { // cannot split motor outputs by more than 1 roll_thrust = 1; limit.roll = true; } // calculate left and right throttle outputs _thrust_left = throttle_thrust + roll_thrust * 0.5f; _thrust_right = throttle_thrust - roll_thrust * 0.5f; thrust_max = MAX(_thrust_right,_thrust_left); thrust_min = MIN(_thrust_right,_thrust_left); if (thrust_max > 1.0f) { // if max thrust is more than one reduce average throttle thr_adj = 1.0f - thrust_max; limit.throttle_upper = true; } else if (thrust_min < 0.0) { // if min thrust is less than 0 increase average throttle // but never above max boost thr_adj = -thrust_min; if ((throttle_thrust + thr_adj) > max_boost_throttle) { thr_adj = MAX(max_boost_throttle - throttle_thrust, 0.0); // in this case we throw away some roll output, it will be uneven // constraining the lower motor more than the upper // this unbalances torque, but motor torque should have significantly less control power than tilts / control surfaces // so its worth keeping the higher roll control power at a minor cost to yaw limit.roll = true; } limit.throttle_lower = true; } // Add adjustment to reduce average throttle _thrust_left = constrain_float(_thrust_left + thr_adj, 0.0f, 1.0f); _thrust_right = constrain_float(_thrust_right + thr_adj, 0.0f, 1.0f); _throttle = throttle_thrust; // compensation_gain can never be zero // ensure accurate representation of average throttle output, this value is used for notch tracking and control surface scaling if (_has_diff_thrust) { _throttle_out = (throttle_thrust + thr_adj) / compensation_gain; } else { _throttle_out = throttle_thrust / compensation_gain; } // thrust vectoring _tilt_left = pitch_thrust - yaw_thrust; _tilt_right = pitch_thrust + yaw_thrust; } // output_test_seq - spin a motor at the pwm value specified // motor_seq is the motor's sequence number from 1 to the number of motors on the frame // pwm value is an actual pwm value that will be output, normally in the range of 1000 ~ 2000 void AP_MotorsTailsitter::_output_test_seq(uint8_t motor_seq, int16_t pwm) { // output to motors and servos switch (motor_seq) { case 1: // right throttle SRV_Channels::set_output_pwm(SRV_Channel::k_throttleRight, pwm); break; case 2: // right tilt servo SRV_Channels::set_output_pwm(SRV_Channel::k_tiltMotorRight, pwm); break; case 3: // left throttle SRV_Channels::set_output_pwm(SRV_Channel::k_throttleLeft, pwm); break; case 4: // left tilt servo SRV_Channels::set_output_pwm(SRV_Channel::k_tiltMotorLeft, pwm); break; default: // do nothing break; } }