Path: blob/master/libraries/AC_AutoTune/AC_AutoTune_Heli.cpp
9575 views
/*1This program is free software: you can redistribute it and/or modify2it under the terms of the GNU General Public License as published by3the Free Software Foundation, either version 3 of the License, or4(at your option) any later version.56This program is distributed in the hope that it will be useful,7but WITHOUT ANY WARRANTY; without even the implied warranty of8MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9GNU General Public License for more details.1011You should have received a copy of the GNU General Public License12along with this program. If not, see <http://www.gnu.org/licenses/>.13*/14/*15support for autotune of helicopters. Based on original autotune code from ArduCopter, written by Leonard Hall16Converted to a library by Andrew Tridgell, and rewritten to include helicopters by Bill Geyer17*/1819#include "AC_AutoTune_config.h"2021#if AC_AUTOTUNE_ENABLED2223#include "AC_AutoTune_Heli.h"2425#include <AP_Logger/AP_Logger.h>26#include <GCS_MAVLink/GCS.h>2728#define AUTOTUNE_TESTING_STEP_TIMEOUT_MS 5000U // timeout for tuning mode's testing step2930#define AUTOTUNE_RD_STEP 0.0005f // minimum increment when increasing/decreasing Rate D term31#define AUTOTUNE_RP_STEP 0.005f // minimum increment when increasing/decreasing Rate P term32#define AUTOTUNE_SP_STEP 0.05f // minimum increment when increasing/decreasing Stab P term33#define AUTOTUNE_PI_RATIO_FOR_TESTING 0.1f // I is set 10x smaller than P during testing34#define AUTOTUNE_PI_RATIO_FINAL 1.0f // I is set 1x P after testing35#define AUTOTUNE_YAW_PI_RATIO_FINAL 0.1f // I is set 1x P after testing36#define AUTOTUNE_RD_MAX 0.020f // maximum Rate D value37#define AUTOTUNE_RP_MIN 0.02f // minimum Rate P value38#define AUTOTUNE_RP_MAX 0.3f // maximum Rate P value39#define AUTOTUNE_SP_MAX 10.0f // maximum Stab P value40#define AUTOTUNE_SP_MIN 3.0f // maximum Stab P value41#define AUTOTUNE_RFF_MAX 0.5f // maximum Stab P value42#define AUTOTUNE_RFF_MIN 0.025f // maximum Stab P value43#define AUTOTUNE_RD_BACKOFF 1.0f // Rate D gains are reduced to 50% of their maximum value discovered during tuning44#define AUTOTUNE_RP_BACKOFF 1.0f // Rate P gains are reduced to 97.5% of their maximum value discovered during tuning45#define AUTOTUNE_SP_BACKOFF 1.0f // Stab P gains are reduced to 90% of their maximum value discovered during tuning46#define AUTOTUNE_ACCEL_RP_BACKOFF 1.0f // back off from maximum acceleration47#define AUTOTUNE_ACCEL_Y_BACKOFF 1.0f // back off from maximum acceleration4849#define AUTOTUNE_HELI_TARGET_ANGLE_RLLPIT_CD 1500 // target roll/pitch angle during AUTOTUNE FeedForward rate test50#define AUTOTUNE_HELI_TARGET_RATE_RLLPIT_CDS 5000 // target roll/pitch rate during AUTOTUNE FeedForward rate test51#define AUTOTUNE_FFI_RATIO_FOR_TESTING 0.5f // I is set 2x smaller than VFF during testing52#define AUTOTUNE_FFI_RATIO_FINAL 0.5f // I is set 0.5x VFF after testing53#define AUTOTUNE_RP_ACCEL_MIN 20000.0f // Minimum acceleration for Roll and Pitch54#define AUTOTUNE_Y_ACCEL_MIN 10000.0f // Minimum acceleration for Yaw5556#define AUTOTUNE_SEQ_BITMASK_VFF 157#define AUTOTUNE_SEQ_BITMASK_RATE_D 258#define AUTOTUNE_SEQ_BITMASK_ANGLE_P 459#define AUTOTUNE_SEQ_BITMASK_MAX_GAIN 860#define AUTOTUNE_SEQ_BITMASK_TUNE_CHECK 166162// angle limits preserved from previous behaviour as Multi changed:63#define AUTOTUNE_ANGLE_TARGET_MAX_RP_CD 2000 // target angle during TESTING_RATE step that will cause us to move to next step64#define AUTOTUNE_ANGLE_TARGET_MIN_RP_CD 1000 // minimum target angle during TESTING_RATE step that will cause us to move to next step65#define AUTOTUNE_ANGLE_TARGET_MAX_Y_CD 3000 // target angle during TESTING_RATE step that will cause us to move to next step66#define AUTOTUNE_ANGLE_TARGET_MIN_Y_CD 500 // target angle during TESTING_RATE step that will cause us to move to next step67#define AUTOTUNE_ANGLE_MAX_RP_CD 3000 // maximum allowable angle in degrees during testing68#define AUTOTUNE_ANGLE_NEG_RPY_CD 1000 // maximum allowable angle in degrees during testing6970const AP_Param::GroupInfo AC_AutoTune_Heli::var_info[] = {7172// @Param: AXES73// @DisplayName: Autotune axis bitmask74// @Description: 1-byte bitmap of axes to autotune75// @Bitmask: 0:Roll,1:Pitch,2:Yaw76// @User: Standard77AP_GROUPINFO("AXES", 1, AC_AutoTune_Heli, axis_bitmask, 1),7879// @Param: SEQ80// @DisplayName: AutoTune Sequence Bitmask81// @Description: 2-byte bitmask to select what tuning should be performed. Max gain automatically performed if Rate D is selected. Values: 7:All,1:VFF Only,2:Rate D/Rate P Only(incl max gain),4:Angle P Only,8:Max Gain Only,16:Tune Check,3:VFF and Rate D/Rate P(incl max gain),5:VFF and Angle P,6:Rate D/Rate P(incl max gain) and angle P82// @Bitmask: 0:VFF,1:Rate D/Rate P(incl max gain),2:Angle P,3:Max Gain Only,4:Tune Check83// @User: Standard84AP_GROUPINFO("SEQ", 2, AC_AutoTune_Heli, seq_bitmask, 3),8586// @Param: FRQ_MIN87// @DisplayName: AutoTune minimum sweep frequency88// @Description: Defines the start frequency for sweeps and dwells89// @Range: 10 3090// @User: Standard91AP_GROUPINFO("FRQ_MIN", 3, AC_AutoTune_Heli, min_sweep_freq, 10.0f),9293// @Param: FRQ_MAX94// @DisplayName: AutoTune maximum sweep frequency95// @Description: Defines the end frequency for sweeps and dwells96// @Range: 50 12097// @User: Standard98AP_GROUPINFO("FRQ_MAX", 4, AC_AutoTune_Heli, max_sweep_freq, 70.0f),99100// @Param: GN_MAX101// @DisplayName: AutoTune maximum response gain102// @Description: Defines the response gain (output/input) to tune103// @Range: 1 2.5104// @User: Standard105AP_GROUPINFO("GN_MAX", 5, AC_AutoTune_Heli, max_resp_gain, 1.0f),106107// @Param: VELXY_P108// @DisplayName: AutoTune velocity xy P gain109// @Description: Velocity xy P gain used to hold position during Max Gain, Rate P, and Rate D frequency sweeps110// @Range: 0 1111// @User: Standard112AP_GROUPINFO("VELXY_P", 6, AC_AutoTune_Heli, vel_hold_gain, 0.1f),113114// @Param: ACC_MAX115// @DisplayName: AutoTune maximum allowable angular acceleration116// @Description: maximum angular acceleration in deg/s/s allowed during autotune maneuvers117// @Range: 1 4000118// @User: Standard119// @Units: deg/s/s120AP_GROUPINFO("ACC_MAX", 7, AC_AutoTune_Heli, accel_max_degss, 0.0f),121122// @Param: RAT_MAX123// @DisplayName: Autotune maximum allowable angular rate124// @Description: maximum angular rate in deg/s allowed during autotune maneuvers125// @Range: 0 500126// @User: Standard127// @Units: deg/s128AP_GROUPINFO("RAT_MAX", 8, AC_AutoTune_Heli, rate_max_degs, 0.0f),129130AP_GROUPEND131};132133// constructor134AC_AutoTune_Heli::AC_AutoTune_Heli()135{136tune_seq[0] = TuneType::TUNE_COMPLETE;137AP_Param::setup_object_defaults(this, var_info);138}139140// Prepares all tuning state variables and target values for a new test.141void AC_AutoTune_Heli::test_init()142{143AC_AutoTune_FreqResp::ResponseType resp_type = AC_AutoTune_FreqResp::ResponseType::RATE;144FreqRespCalcType calc_type = RATE;145FreqRespInput freq_resp_input = TARGET;146float freq_resp_amplitude = 5.0f; // amplitude in deg147float filter_freq = 10.0f;148switch (tune_type) {149case TuneType::RATE_FF_UP:150if (!is_positive(next_test_freq)) {151start_freq = 0.25f * M_2PI;152} else {153start_freq = next_test_freq;154}155stop_freq = start_freq;156filter_freq = start_freq;157158attitude_control->bf_feedforward(false);159160// variables needed to initialize frequency response object and test method161resp_type = AC_AutoTune_FreqResp::ResponseType::RATE;162calc_type = RATE;163freq_resp_input = TARGET;164pre_calc_cycles = 1.0f;165num_dwell_cycles = 3;166break;167case TuneType::MAX_GAINS:168// initialize start frequency for sweep169if (!is_positive(next_test_freq)) {170start_freq = min_sweep_freq;171stop_freq = max_sweep_freq;172sweep_complete = true;173} else {174start_freq = next_test_freq;175stop_freq = start_freq;176test_accel_max_cdss = 0.0f;177}178filter_freq = start_freq;179180attitude_control->bf_feedforward(false);181182// variables needed to initialize frequency response object and test method183resp_type = AC_AutoTune_FreqResp::ResponseType::RATE;184calc_type = RATE;185freq_resp_input = MOTOR;186pre_calc_cycles = 6.25f;187num_dwell_cycles = 6;188break;189case TuneType::RATE_P_UP:190case TuneType::RATE_D_UP:191// initialize start frequency192if (!is_positive(next_test_freq)) {193// continue using frequency where testing left off with RATE_D_UP completed194if (curr_data.phase > 150.0f && curr_data.phase < 180.0f && tune_type == TuneType::RATE_P_UP) {195start_freq = curr_data.freq;196// start with freq found for sweep where phase was 180 deg197} else if (!is_zero(sweep_tgt.ph180.freq)) {198start_freq = sweep_tgt.ph180.freq;199// otherwise start at min freq to step up in dwell frequency until phase > 160 deg200} else {201start_freq = min_sweep_freq;202}203} else {204start_freq = next_test_freq;205}206stop_freq = start_freq;207filter_freq = start_freq;208209attitude_control->bf_feedforward(false);210211// variables needed to initialize frequency response object and test method212resp_type = AC_AutoTune_FreqResp::ResponseType::RATE;213calc_type = RATE;214freq_resp_input = TARGET;215pre_calc_cycles = 6.25f;216num_dwell_cycles = 6;217break;218case TuneType::ANGLE_P_UP:219// initialize start frequency for sweep220if (!is_positive(next_test_freq)) {221start_freq = min_sweep_freq;222stop_freq = max_sweep_freq;223sweep_complete = true;224} else {225start_freq = next_test_freq;226stop_freq = start_freq;227test_accel_max_cdss = 0.0f;228}229filter_freq = start_freq;230attitude_control->bf_feedforward(false);231232// variables needed to initialize frequency response object and test method233resp_type = AC_AutoTune_FreqResp::ResponseType::ANGLE;234calc_type = DRB;235freq_resp_input = TARGET;236pre_calc_cycles = 6.25f;237num_dwell_cycles = 6;238break;239case TuneType::TUNE_CHECK:240// initialize start frequency241start_freq = min_sweep_freq;242stop_freq = max_sweep_freq;243test_accel_max_cdss = 0.0f;244filter_freq = start_freq;245246// variables needed to initialize frequency response object and test method247resp_type = AC_AutoTune_FreqResp::ResponseType::ANGLE;248calc_type = ANGLE;249freq_resp_input = TARGET;250break;251default:252break;253}254255if (!is_equal(start_freq,stop_freq)) {256input_type = AC_AutoTune_FreqResp::InputType::SWEEP;257} else {258input_type = AC_AutoTune_FreqResp::InputType::DWELL;259}260261262// initialize dwell test method263dwell_test_init(start_freq, stop_freq, freq_resp_amplitude, filter_freq, freq_resp_input, calc_type, resp_type, input_type);264}265266// run tests for each tune type267void AC_AutoTune_Heli::test_run(AxisType test_axis, const float dir_sign)268{269// if tune complete or beyond frequency range or no max allowed gains then exit testing270if (tune_type == TuneType::TUNE_COMPLETE ||271((tune_type == TuneType::RATE_P_UP || tune_type == TuneType::RATE_D_UP) && (max_rate_p.max_allowed <= 0.0f || max_rate_d.max_allowed <= 0.0f)) ||272((tune_type == TuneType::MAX_GAINS || tune_type == TuneType::RATE_P_UP || tune_type == TuneType::RATE_D_UP || tune_type == TuneType::ANGLE_P_UP) && exceeded_freq_range(start_freq))){273274load_gains(GainType::ORIGINAL);275276// hold level attitude277attitude_control->input_euler_angle_roll_pitch_yaw_rad(desired_roll_rad, desired_pitch_rad, desired_yaw_rad, true);278279if ((tune_type == TuneType::RATE_P_UP || tune_type == TuneType::RATE_D_UP) && (max_rate_p.max_allowed <= 0.0f || max_rate_d.max_allowed <= 0.0f)) {280GCS_SEND_TEXT(MAV_SEVERITY_INFO, "AutoTune: Max Gain Determination Failed");281mode = TuneMode::FAILED;282LOGGER_WRITE_EVENT(LogEvent::AUTOTUNE_FAILED);283update_gcs(AUTOTUNE_MESSAGE_FAILED);284} else if ((tune_type == TuneType::MAX_GAINS || tune_type == TuneType::RATE_P_UP || tune_type == TuneType::RATE_D_UP || tune_type == TuneType::ANGLE_P_UP) && exceeded_freq_range(start_freq)){285GCS_SEND_TEXT(MAV_SEVERITY_INFO, "AutoTune: Exceeded frequency range");286mode = TuneMode::FAILED;287LOGGER_WRITE_EVENT(LogEvent::AUTOTUNE_FAILED);288update_gcs(AUTOTUNE_MESSAGE_FAILED);289} else if (tune_type == TuneType::TUNE_COMPLETE) {290success_counter = AUTOTUNE_SUCCESS_COUNT;291step = Step::UPDATE_GAINS;292}293return;294}295296dwell_test_run(curr_data);297298}299300// heli specific gcs announcements301void AC_AutoTune_Heli::do_gcs_announcements()302{303const uint32_t now_ms = AP_HAL::millis();304if (now_ms - last_announce_ms < AUTOTUNE_ANNOUNCE_INTERVAL_MS) {305return;306}307308GCS_SEND_TEXT(MAV_SEVERITY_INFO, "AutoTune: %s %s", get_axis_name(), get_tune_type_name());309send_step_string();310switch (tune_type) {311case TuneType::RATE_FF_UP:312case TuneType::RATE_D_UP:313case TuneType::RATE_P_UP:314case TuneType::MAX_GAINS:315case TuneType::ANGLE_P_UP:316case TuneType::TUNE_CHECK:317if (is_equal(start_freq,stop_freq)) {318GCS_SEND_TEXT(MAV_SEVERITY_INFO, "AutoTune: Dwell");319} else {320GCS_SEND_TEXT(MAV_SEVERITY_INFO, "AutoTune: Sweep");321if (settle_time == 0) {322GCS_SEND_TEXT(MAV_SEVERITY_INFO, "AutoTune: freq=%f gain=%f phase=%f", (double)(curr_test.freq), (double)(curr_test.gain), (double)(curr_test.phase));323}324}325break;326default:327break;328}329330last_announce_ms = now_ms;331}332333// send post test updates to user334void AC_AutoTune_Heli::do_post_test_gcs_announcements() {335float tune_rp = 0.0f;336float tune_rd = 0.0f;337float tune_rff = 0.0f;338float tune_sp = 0.0f;339float tune_accel_radss = 0.0f;340341switch (axis) {342case AxisType::ROLL:343tune_rp = tune_roll_rp;344tune_rd = tune_roll_rd;345tune_rff = tune_roll_rff;346tune_sp = tune_roll_sp;347tune_accel_radss = tune_roll_accel_radss;348break;349case AxisType::PITCH:350tune_rp = tune_pitch_rp;351tune_rd = tune_pitch_rd;352tune_rff = tune_pitch_rff;353tune_sp = tune_pitch_sp;354tune_accel_radss = tune_pitch_accel_radss;355break;356case AxisType::YAW:357case AxisType::YAW_D:358tune_rp = tune_yaw_rp;359tune_rd = tune_yaw_rd;360tune_rff = tune_yaw_rff;361tune_sp = tune_yaw_sp;362tune_accel_radss = tune_yaw_accel_radss;363break;364}365366if (step == Step::UPDATE_GAINS) {367switch (tune_type) {368case TuneType::RATE_FF_UP:369case TuneType::RATE_P_UP:370case TuneType::RATE_D_UP:371case TuneType::ANGLE_P_UP:372case TuneType::MAX_GAINS:373if (is_equal(start_freq,stop_freq)) {374// announce results of dwell375GCS_SEND_TEXT(MAV_SEVERITY_INFO, "AutoTune: freq=%f gain=%f", (double)(curr_data.freq), (double)(curr_data.gain));376GCS_SEND_TEXT(MAV_SEVERITY_INFO, "AutoTune: ph=%f", (double)(curr_data.phase));377if (tune_type == TuneType::RATE_P_UP) {378GCS_SEND_TEXT(MAV_SEVERITY_INFO, "AutoTune: rate_p=%f", (double)(tune_rp));379} else if (tune_type == TuneType::RATE_D_UP) {380GCS_SEND_TEXT(MAV_SEVERITY_INFO, "AutoTune: rate_d=%f", (double)(tune_rd));381} else if (tune_type == TuneType::RATE_FF_UP) {382GCS_SEND_TEXT(MAV_SEVERITY_INFO, "AutoTune: rate_ff=%f", (double)(tune_rff));383} else if (tune_type == TuneType::ANGLE_P_UP) {384GCS_SEND_TEXT(MAV_SEVERITY_INFO, "AutoTune: angle_p=%f tune_accel=%f max_accel=%f", (double)(tune_sp), (double)(rad_to_cd(tune_accel_radss)), (double)(test_accel_max_cdss));385}386} else {387GCS_SEND_TEXT(MAV_SEVERITY_INFO, "AutoTune: max_freq=%f max_gain=%f", (double)(sweep_tgt.maxgain.freq), (double)(sweep_tgt.maxgain.gain));388GCS_SEND_TEXT(MAV_SEVERITY_INFO, "AutoTune: ph180_freq=%f ph180_gain=%f", (double)(sweep_tgt.ph180.freq), (double)(sweep_tgt.ph180.gain));389}390break;391default:392break;393}394}395}396397// backup_gains_and_initialise - store current gains as originals398// called before tuning starts to backup original gains399void AC_AutoTune_Heli::backup_gains_and_initialise()400{401AC_AutoTune::backup_gains_and_initialise();402403// initializes dwell test sequence for rate_p_up and rate_d_up tests for tradheli404next_test_freq = 0.0f;405start_freq = 0.0f;406stop_freq = 0.0f;407408orig_bf_feedforward = attitude_control->get_bf_feedforward();409410// backup original pids and initialise tuned pid values411orig_roll_rp = attitude_control->get_rate_roll_pid().kP();412orig_roll_ri = attitude_control->get_rate_roll_pid().kI();413orig_roll_rd = attitude_control->get_rate_roll_pid().kD();414orig_roll_rff = attitude_control->get_rate_roll_pid().ff();415orig_roll_fltt = attitude_control->get_rate_roll_pid().filt_T_hz();416orig_roll_smax = attitude_control->get_rate_roll_pid().slew_limit();417orig_roll_sp = attitude_control->get_angle_roll_p().kP();418orig_roll_accel_radss = attitude_control->get_accel_roll_max_radss();419orig_roll_rate_rads = attitude_control->get_ang_vel_roll_max_rads();420tune_roll_rp = attitude_control->get_rate_roll_pid().kP();421tune_roll_rd = attitude_control->get_rate_roll_pid().kD();422tune_roll_rff = attitude_control->get_rate_roll_pid().ff();423tune_roll_sp = attitude_control->get_angle_roll_p().kP();424tune_roll_accel_radss = attitude_control->get_accel_roll_max_radss();425426orig_pitch_rp = attitude_control->get_rate_pitch_pid().kP();427orig_pitch_ri = attitude_control->get_rate_pitch_pid().kI();428orig_pitch_rd = attitude_control->get_rate_pitch_pid().kD();429orig_pitch_rff = attitude_control->get_rate_pitch_pid().ff();430orig_pitch_fltt = attitude_control->get_rate_pitch_pid().filt_T_hz();431orig_pitch_smax = attitude_control->get_rate_pitch_pid().slew_limit();432orig_pitch_sp = attitude_control->get_angle_pitch_p().kP();433orig_pitch_accel_radss = attitude_control->get_accel_pitch_max_radss();434orig_pitch_rate_rads = attitude_control->get_ang_vel_pitch_max_rads();435tune_pitch_rp = attitude_control->get_rate_pitch_pid().kP();436tune_pitch_rd = attitude_control->get_rate_pitch_pid().kD();437tune_pitch_rff = attitude_control->get_rate_pitch_pid().ff();438tune_pitch_sp = attitude_control->get_angle_pitch_p().kP();439tune_pitch_accel_radss = attitude_control->get_accel_pitch_max_radss();440441orig_yaw_rp = attitude_control->get_rate_yaw_pid().kP();442orig_yaw_ri = attitude_control->get_rate_yaw_pid().kI();443orig_yaw_rd = attitude_control->get_rate_yaw_pid().kD();444orig_yaw_rff = attitude_control->get_rate_yaw_pid().ff();445orig_yaw_fltt = attitude_control->get_rate_yaw_pid().filt_T_hz();446orig_yaw_smax = attitude_control->get_rate_yaw_pid().slew_limit();447orig_yaw_rLPF = attitude_control->get_rate_yaw_pid().filt_E_hz();448orig_yaw_accel_radss = attitude_control->get_accel_yaw_max_radss();449orig_yaw_sp = attitude_control->get_angle_yaw_p().kP();450orig_yaw_rate_rads = attitude_control->get_ang_vel_yaw_max_rads();451tune_yaw_rp = attitude_control->get_rate_yaw_pid().kP();452tune_yaw_rd = attitude_control->get_rate_yaw_pid().kD();453tune_yaw_rff = attitude_control->get_rate_yaw_pid().ff();454tune_yaw_rLPF = attitude_control->get_rate_yaw_pid().filt_E_hz();455tune_yaw_sp = attitude_control->get_angle_yaw_p().kP();456tune_yaw_accel_radss = attitude_control->get_accel_yaw_max_radss();457458LOGGER_WRITE_EVENT(LogEvent::AUTOTUNE_INITIALISED);459}460461// load_orig_gains - set gains to their original values462// called by stop and failed functions463void AC_AutoTune_Heli::load_orig_gains()464{465attitude_control->use_sqrt_controller(true);466attitude_control->bf_feedforward(orig_bf_feedforward);467if (roll_enabled()) {468load_gain_set(AxisType::ROLL, orig_roll_rp, orig_roll_ri, orig_roll_rd, orig_roll_rff, orig_roll_sp, orig_roll_accel_radss, orig_roll_fltt, 0.0f, orig_roll_smax, orig_roll_rate_rads);469}470if (pitch_enabled()) {471load_gain_set(AxisType::PITCH, orig_pitch_rp, orig_pitch_ri, orig_pitch_rd, orig_pitch_rff, orig_pitch_sp, orig_pitch_accel_radss, orig_pitch_fltt, 0.0f, orig_pitch_smax, orig_pitch_rate_rads);472}473if (yaw_enabled()) {474load_gain_set(AxisType::YAW, orig_yaw_rp, orig_yaw_ri, orig_yaw_rd, orig_yaw_rff, orig_yaw_sp, orig_yaw_accel_radss, orig_yaw_fltt, orig_yaw_rLPF, orig_yaw_smax, orig_yaw_rate_rads);475}476}477478// load_tuned_gains - load tuned gains479void AC_AutoTune_Heli::load_tuned_gains()480{481attitude_control->use_sqrt_controller(true);482if (!attitude_control->get_bf_feedforward()) {483attitude_control->bf_feedforward(true);484attitude_control->set_accel_roll_max_radss(0.0f);485attitude_control->set_accel_pitch_max_radss(0.0f);486}487if ((axes_completed & AUTOTUNE_AXIS_BITMASK_ROLL) && roll_enabled()) {488load_gain_set(AxisType::ROLL, tune_roll_rp, tune_roll_rff*AUTOTUNE_FFI_RATIO_FINAL, tune_roll_rd, tune_roll_rff, tune_roll_sp, tune_roll_accel_radss, orig_roll_fltt, 0.0f, orig_roll_smax, orig_roll_rate_rads);489}490if ((axes_completed & AUTOTUNE_AXIS_BITMASK_PITCH) && pitch_enabled()) {491load_gain_set(AxisType::PITCH, tune_pitch_rp, tune_pitch_rff*AUTOTUNE_FFI_RATIO_FINAL, tune_pitch_rd, tune_pitch_rff, tune_pitch_sp, tune_pitch_accel_radss, orig_pitch_fltt, 0.0f, orig_pitch_smax, orig_pitch_rate_rads);492}493if ((axes_completed & AUTOTUNE_AXIS_BITMASK_YAW) && yaw_enabled() && !is_zero(tune_yaw_rp)) {494load_gain_set(AxisType::YAW, tune_yaw_rp, tune_yaw_rp*AUTOTUNE_YAW_PI_RATIO_FINAL, tune_yaw_rd, tune_yaw_rff, tune_yaw_sp, tune_yaw_accel_radss, orig_yaw_fltt, tune_yaw_rLPF, orig_yaw_smax, orig_yaw_rate_rads);495}496}497498// load_intra_test_gains - gains used between tests499// called during testing mode's update-gains step to set gains ahead of return-to-level step500void AC_AutoTune_Heli::load_intra_test_gains()501{502// we are restarting tuning so reset gains to tuning-start gains (i.e. low I term)503// sanity check the gains504attitude_control->use_sqrt_controller(true);505attitude_control->bf_feedforward(true);506if (roll_enabled()) {507load_gain_set(AxisType::ROLL, orig_roll_rp, orig_roll_rff * AUTOTUNE_FFI_RATIO_FOR_TESTING, orig_roll_rd, orig_roll_rff, orig_roll_sp, orig_roll_accel_radss, orig_roll_fltt, 0.0f, orig_roll_smax, orig_roll_rate_rads);508}509if (pitch_enabled()) {510load_gain_set(AxisType::PITCH, orig_pitch_rp, orig_pitch_rff * AUTOTUNE_FFI_RATIO_FOR_TESTING, orig_pitch_rd, orig_pitch_rff, orig_pitch_sp, orig_pitch_accel_radss, orig_pitch_fltt, 0.0f, orig_pitch_smax, orig_pitch_rate_rads);511}512if (yaw_enabled()) {513load_gain_set(AxisType::YAW, orig_yaw_rp, orig_yaw_rp*AUTOTUNE_PI_RATIO_FOR_TESTING, orig_yaw_rd, orig_yaw_rff, orig_yaw_sp, orig_yaw_accel_radss, orig_yaw_fltt, orig_yaw_rLPF, orig_yaw_smax, orig_yaw_rate_rads);514}515}516517// load_test_gains - load the to-be-tested gains for a single axis518// called by control_attitude() just before it beings testing a gain519void AC_AutoTune_Heli::load_test_gains()520{521attitude_control->use_sqrt_controller(true);522float rate_p, rate_i, rate_d, rate_test_max_rads, accel_test_max_radss;523switch (axis) {524case AxisType::ROLL:525526if (tune_type == TuneType::TUNE_CHECK) {527rate_test_max_rads = orig_roll_rate_rads;528accel_test_max_radss = tune_roll_accel_radss;529} else {530// have attitude controller not perform rate limiting and angle P scaling based on accel max531rate_test_max_rads = 0.0;532accel_test_max_radss = 0.0;533}534if (tune_type == TuneType::ANGLE_P_UP || tune_type == TuneType::TUNE_CHECK) {535rate_i = tune_roll_rff*AUTOTUNE_FFI_RATIO_FINAL;536} else {537// freeze integrator to hold trim by making i term small during rate controller tuning538rate_i = 0.01f * orig_roll_ri;539}540if (tune_type == TuneType::MAX_GAINS && !is_zero(tune_roll_rff)) {541rate_p = 0.0f;542rate_d = 0.0f;543} else {544rate_p = tune_roll_rp;545rate_d = tune_roll_rd;546}547load_gain_set(AxisType::ROLL, rate_p, rate_i, rate_d, tune_roll_rff, tune_roll_sp, accel_test_max_radss, orig_roll_fltt, 0.0f, 0.0f, rate_test_max_rads);548break;549case AxisType::PITCH:550if (tune_type == TuneType::TUNE_CHECK) {551rate_test_max_rads = orig_pitch_rate_rads;552accel_test_max_radss = tune_pitch_accel_radss;553} else {554// have attitude controller not perform rate limiting and angle P scaling based on accel max555rate_test_max_rads = 0.0;556accel_test_max_radss = 0.0;557}558if (tune_type == TuneType::ANGLE_P_UP || tune_type == TuneType::TUNE_CHECK) {559rate_i = tune_pitch_rff*AUTOTUNE_FFI_RATIO_FINAL;560} else {561// freeze integrator to hold trim by making i term small during rate controller tuning562rate_i = 0.01f * orig_pitch_ri;563}564if (tune_type == TuneType::MAX_GAINS && !is_zero(tune_pitch_rff)) {565rate_p = 0.0f;566rate_d = 0.0f;567} else {568rate_p = tune_pitch_rp;569rate_d = tune_pitch_rd;570}571load_gain_set(AxisType::PITCH, rate_p, rate_i, rate_d, tune_pitch_rff, tune_pitch_sp, accel_test_max_radss, orig_pitch_fltt, 0.0f, 0.0f, rate_test_max_rads);572break;573case AxisType::YAW:574case AxisType::YAW_D:575if (tune_type == TuneType::TUNE_CHECK) {576rate_test_max_rads = orig_yaw_rate_rads;577accel_test_max_radss = tune_yaw_accel_radss;578} else {579// have attitude controller not perform rate limiting and angle P scaling based on accel max580rate_test_max_rads = 0.0;581accel_test_max_radss = 0.0;582}583if (tune_type == TuneType::ANGLE_P_UP || tune_type == TuneType::TUNE_CHECK) {584rate_i = tune_yaw_rp*AUTOTUNE_YAW_PI_RATIO_FINAL;585} else {586// freeze integrator to hold trim by making i term small during rate controller tuning587rate_i = 0.01f * orig_yaw_ri;588}589load_gain_set(AxisType::YAW, tune_yaw_rp, rate_i, tune_yaw_rd, tune_yaw_rff, tune_yaw_sp, accel_test_max_radss, orig_yaw_fltt, tune_yaw_rLPF, 0.0f, rate_test_max_rads);590break;591}592}593594// load gains595void AC_AutoTune_Heli::load_gain_set(AxisType s_axis, float rate_p, float rate_i, float rate_d, float rate_ff, float angle_p, float max_accel_radss, float rate_fltt, float rate_flte, float smax, float max_rate_rads)596{597switch (s_axis) {598case AxisType::ROLL:599attitude_control->get_rate_roll_pid().set_kP(rate_p);600attitude_control->get_rate_roll_pid().set_kI(rate_i);601attitude_control->get_rate_roll_pid().set_kD(rate_d);602attitude_control->get_rate_roll_pid().set_ff(rate_ff);603attitude_control->get_rate_roll_pid().set_filt_T_hz(rate_fltt);604attitude_control->get_rate_roll_pid().set_slew_limit(smax);605attitude_control->get_angle_roll_p().set_kP(angle_p);606attitude_control->set_accel_roll_max_radss(max_accel_radss);607attitude_control->set_ang_vel_roll_max_rads(max_rate_rads);608break;609case AxisType::PITCH:610attitude_control->get_rate_pitch_pid().set_kP(rate_p);611attitude_control->get_rate_pitch_pid().set_kI(rate_i);612attitude_control->get_rate_pitch_pid().set_kD(rate_d);613attitude_control->get_rate_pitch_pid().set_ff(rate_ff);614attitude_control->get_rate_pitch_pid().set_filt_T_hz(rate_fltt);615attitude_control->get_rate_pitch_pid().set_slew_limit(smax);616attitude_control->get_angle_pitch_p().set_kP(angle_p);617attitude_control->set_accel_pitch_max_radss(max_accel_radss);618attitude_control->set_ang_vel_pitch_max_rads(max_rate_rads);619break;620case AxisType::YAW:621case AxisType::YAW_D:622attitude_control->get_rate_yaw_pid().set_kP(rate_p);623attitude_control->get_rate_yaw_pid().set_kI(rate_i);624attitude_control->get_rate_yaw_pid().set_kD(rate_d);625attitude_control->get_rate_yaw_pid().set_ff(rate_ff);626attitude_control->get_rate_yaw_pid().set_filt_T_hz(rate_fltt);627attitude_control->get_rate_yaw_pid().set_slew_limit(smax);628attitude_control->get_rate_yaw_pid().set_filt_E_hz(rate_flte);629attitude_control->get_angle_yaw_p().set_kP(angle_p);630attitude_control->set_accel_yaw_max_radss(max_accel_radss);631attitude_control->set_ang_vel_yaw_max_rads(max_rate_rads);632break;633}634}635636// save_tuning_gains - save the final tuned gains for each axis637// save discovered gains to eeprom if autotuner is enabled (i.e. switch is in the high position)638void AC_AutoTune_Heli::save_tuning_gains()639{640// see if we successfully completed tuning of at least one axis641if (axes_completed == 0) {642return;643}644645if (!attitude_control->get_bf_feedforward()) {646attitude_control->bf_feedforward_save(true);647attitude_control->save_accel_roll_max_radss(0.0f);648attitude_control->save_accel_pitch_max_radss(0.0f);649}650651// sanity check the rate P values652if ((axes_completed & AUTOTUNE_AXIS_BITMASK_ROLL) && roll_enabled()) {653load_gain_set(AxisType::ROLL, tune_roll_rp, tune_roll_rff*AUTOTUNE_FFI_RATIO_FINAL, tune_roll_rd, tune_roll_rff, tune_roll_sp, tune_roll_accel_radss, orig_roll_fltt, 0.0f, orig_roll_smax, orig_roll_rate_rads);654// save rate roll gains655attitude_control->get_rate_roll_pid().save_gains();656657// save stabilize roll658attitude_control->get_angle_roll_p().save_gains();659660// resave pids to originals in case the autotune is run again661orig_roll_rp = attitude_control->get_rate_roll_pid().kP();662orig_roll_ri = attitude_control->get_rate_roll_pid().kI();663orig_roll_rd = attitude_control->get_rate_roll_pid().kD();664orig_roll_rff = attitude_control->get_rate_roll_pid().ff();665orig_roll_sp = attitude_control->get_angle_roll_p().kP();666orig_roll_accel_radss = attitude_control->get_accel_roll_max_radss();667}668669if ((axes_completed & AUTOTUNE_AXIS_BITMASK_PITCH) && pitch_enabled()) {670load_gain_set(AxisType::PITCH, tune_pitch_rp, tune_pitch_rff*AUTOTUNE_FFI_RATIO_FINAL, tune_pitch_rd, tune_pitch_rff, tune_pitch_sp, tune_pitch_accel_radss, orig_pitch_fltt, 0.0f, orig_pitch_smax, orig_pitch_rate_rads);671// save rate pitch gains672attitude_control->get_rate_pitch_pid().save_gains();673674// save stabilize pitch675attitude_control->get_angle_pitch_p().save_gains();676677// resave pids to originals in case the autotune is run again678orig_pitch_rp = attitude_control->get_rate_pitch_pid().kP();679orig_pitch_ri = attitude_control->get_rate_pitch_pid().kI();680orig_pitch_rd = attitude_control->get_rate_pitch_pid().kD();681orig_pitch_rff = attitude_control->get_rate_pitch_pid().ff();682orig_pitch_sp = attitude_control->get_angle_pitch_p().kP();683orig_pitch_accel_radss = attitude_control->get_accel_pitch_max_radss();684}685686if ((axes_completed & AUTOTUNE_AXIS_BITMASK_YAW) && yaw_enabled() && !is_zero(tune_yaw_rp)) {687load_gain_set(AxisType::YAW, tune_yaw_rp, tune_yaw_rp*AUTOTUNE_YAW_PI_RATIO_FINAL, tune_yaw_rd, tune_yaw_rff, tune_yaw_sp, tune_yaw_accel_radss, orig_yaw_fltt, orig_yaw_rLPF, orig_yaw_smax, orig_yaw_rate_rads);688// save rate yaw gains689attitude_control->get_rate_yaw_pid().save_gains();690691// save stabilize yaw692attitude_control->get_angle_yaw_p().save_gains();693694// resave pids to originals in case the autotune is run again695orig_yaw_rp = attitude_control->get_rate_yaw_pid().kP();696orig_yaw_ri = attitude_control->get_rate_yaw_pid().kI();697orig_yaw_rd = attitude_control->get_rate_yaw_pid().kD();698orig_yaw_rff = attitude_control->get_rate_yaw_pid().ff();699orig_yaw_rLPF = attitude_control->get_rate_yaw_pid().filt_E_hz();700orig_yaw_sp = attitude_control->get_angle_yaw_p().kP();701orig_yaw_accel_radss = attitude_control->get_accel_yaw_max_radss();702}703704// update GCS and log save gains event705update_gcs(AUTOTUNE_MESSAGE_SAVED_GAINS);706LOGGER_WRITE_EVENT(LogEvent::AUTOTUNE_SAVEDGAINS);707708reset();709}710711// report final gains for a given axis to GCS712void AC_AutoTune_Heli::report_final_gains(AxisType test_axis) const713{714switch (test_axis) {715case AxisType::ROLL:716report_axis_gains("Roll", tune_roll_rp, tune_roll_rff*AUTOTUNE_FFI_RATIO_FINAL, tune_roll_rd, tune_roll_rff, tune_roll_sp, tune_roll_accel_radss);717break;718case AxisType::PITCH:719report_axis_gains("Pitch", tune_pitch_rp, tune_pitch_rff*AUTOTUNE_FFI_RATIO_FINAL, tune_pitch_rd, tune_pitch_rff, tune_pitch_sp, tune_pitch_accel_radss);720break;721case AxisType::YAW:722case AxisType::YAW_D:723report_axis_gains("Yaw", tune_yaw_rp, tune_yaw_rp*AUTOTUNE_YAW_PI_RATIO_FINAL, tune_yaw_rd, tune_yaw_rff, tune_yaw_sp, tune_yaw_accel_radss);724break;725}726}727728// report gain formatting helper729void AC_AutoTune_Heli::report_axis_gains(const char* axis_string, float rate_P, float rate_I, float rate_D, float rate_ff, float angle_P, float max_accel_radss) const730{731GCS_SEND_TEXT(MAV_SEVERITY_NOTICE,"AutoTune: %s complete", axis_string);732GCS_SEND_TEXT(MAV_SEVERITY_NOTICE,"AutoTune: %s Rate: P:%0.4f, I:%0.4f, D:%0.5f, FF:%0.4f", axis_string, rate_P, rate_I, rate_D, rate_ff);733GCS_SEND_TEXT(MAV_SEVERITY_NOTICE,"AutoTune: %s Angle P:%0.2f, Max Accel:%0.0f", axis_string, angle_P, rad_to_cd(max_accel_radss));734}735736void AC_AutoTune_Heli::dwell_test_init(float start_frq, float stop_frq, float amplitude, float filt_freq, FreqRespInput freq_resp_input, FreqRespCalcType calc_type, AC_AutoTune_FreqResp::ResponseType resp_type, AC_AutoTune_FreqResp::InputType waveform_input_type)737{738test_input_type = waveform_input_type;739test_freq_resp_input = freq_resp_input;740test_calc_type = calc_type;741test_start_freq = start_frq;742//target attitude magnitude743tgt_attitude = radians(amplitude);744745// initialize frequency response object746if (test_input_type == AC_AutoTune_FreqResp::InputType::SWEEP) {747step_timeout_ms = sweep_time_ms + 500;748reset_sweep_variables();749curr_test.gain = 0.0f;750curr_test.phase = 0.0f;751chirp_input.init(0.001f * sweep_time_ms, start_frq / M_2PI, stop_frq / M_2PI, 0.0f, 0.0001f * sweep_time_ms, 0.0f);752} else {753if (!is_zero(start_frq)) {754// time limit set by adding the pre calc cycles with the dwell cycles. 500 ms added to account for settling with buffer.755step_timeout_ms = (uint32_t) (2000 + ((float)num_dwell_cycles + pre_calc_cycles + 2.0f) * 1000.0f * M_2PI / start_frq);756}757chirp_input.init(0.001f * step_timeout_ms, start_frq / M_2PI, stop_frq / M_2PI, 0.0f, 0.0001f * step_timeout_ms, 0.0f);758}759760freqresp_tgt.init(test_input_type, resp_type, num_dwell_cycles);761freqresp_mtr.init(test_input_type, resp_type, num_dwell_cycles);762763dwell_start_time_ms = 0.0f;764settle_time = 200;765766rotation_rate_filt.set_cutoff_frequency(filt_freq);767command_filt.set_cutoff_frequency(filt_freq);768target_rate_filt.set_cutoff_frequency(filt_freq);769770rotation_rate_filt.reset(0);771command_filt.reset(0);772target_rate_filt.reset(0);773rotation_rate = 0.0f;774command_out = 0.0f;775filt_target_rate = 0.0f;776777// filter at lower frequency to remove steady state778filt_command_reading.set_cutoff_frequency(0.2f * filt_freq);779filt_gyro_reading.set_cutoff_frequency(0.05f * filt_freq);780filt_tgt_rate_reading.set_cutoff_frequency(0.05f * filt_freq);781filt_att_fdbk_from_velxy_cd.set_cutoff_frequency(0.2f * filt_freq);782783curr_test_mtr = {};784curr_test_tgt = {};785cycle_complete_tgt = false;786cycle_complete_mtr = false;787sweep_complete = false;788789}790791void AC_AutoTune_Heli::dwell_test_run(sweep_info &test_data)792{793float gyro_reading = 0.0f;794float command_reading = 0.0f;795float tgt_rate_reading = 0.0f;796const uint32_t now_ms = AP_HAL::millis();797float target_angle_cd = 0.0f;798float dwell_freq = test_start_freq;799800float cycle_time_ms = 0;801if (!is_zero(dwell_freq)) {802cycle_time_ms = 1000.0f * M_2PI / dwell_freq;803}804805// body frame calculation of velocity806Vector3f velocity_ned, velocity_bf;807if (ahrs_view->get_velocity_NED(velocity_ned)) {808velocity_bf.x = velocity_ned.x * ahrs_view->cos_yaw() + velocity_ned.y * ahrs_view->sin_yaw();809velocity_bf.y = -velocity_ned.x * ahrs_view->sin_yaw() + velocity_ned.y * ahrs_view->cos_yaw();810}811812if (settle_time == 0) {813dwell_freq = chirp_input.get_frequency_rads();814float tgt_att_limited = tgt_attitude;815if (is_positive(dwell_freq)) {816float tgt_att_temp = tgt_attitude;817if (is_positive(rate_max_degs)) {818float ang_limit_rate = radians(rate_max_degs) / dwell_freq;819tgt_att_temp = MIN(ang_limit_rate, tgt_attitude);820}821if (is_positive(accel_max_degss)) {822float ang_limit_accel_radss = radians(accel_max_degss) / sq(dwell_freq);823tgt_att_limited = MIN(ang_limit_accel_radss, tgt_att_temp);824} else {825tgt_att_limited = tgt_att_temp;826}827}828target_angle_cd = -chirp_input.update((now_ms - dwell_start_time_ms) * 0.001, degrees(tgt_att_limited) * 100.0f);829dwell_freq = chirp_input.get_frequency_rads();830const Vector2f att_fdbk {831-5730.0f * vel_hold_gain * velocity_bf.y,8325730.0f * vel_hold_gain * velocity_bf.x833};834filt_att_fdbk_from_velxy_cd.apply(att_fdbk, AP::scheduler().get_loop_period_s());835} else {836target_angle_cd = 0.0f;837trim_yaw_tgt_reading_cd = (float)attitude_control->get_att_target_euler_cd().z;838trim_yaw_heading_reading_cd = (float)ahrs_view->yaw_sensor;839dwell_start_time_ms = now_ms;840filt_att_fdbk_from_velxy_cd.reset(Vector2f(0.0f,0.0f));841settle_time--;842}843844const Vector2f trim_angle_cd {845constrain_float(filt_att_fdbk_from_velxy_cd.get().x, -2000.0f, 2000.0f),846constrain_float(filt_att_fdbk_from_velxy_cd.get().y, -2000.0f, 2000.0f)847};848849switch (axis) {850case AxisType::ROLL:851attitude_control->input_euler_angle_roll_pitch_euler_rate_yaw_rad(cd_to_rad(target_angle_cd + trim_angle_cd.x), cd_to_rad(trim_angle_cd.y), 0.0f);852command_reading = motors->get_roll();853if (test_calc_type == DRB) {854tgt_rate_reading = cd_to_rad(target_angle_cd);855gyro_reading = cd_to_rad((float)ahrs_view->roll_sensor + trim_angle_cd.x - target_angle_cd);856} else if (test_calc_type == RATE) {857tgt_rate_reading = attitude_control->rate_bf_targets().x;858gyro_reading = ahrs_view->get_gyro().x;859} else {860tgt_rate_reading = cd_to_rad((float)attitude_control->get_att_target_euler_cd().x);861gyro_reading = cd_to_rad((float)ahrs_view->roll_sensor);862}863break;864case AxisType::PITCH:865attitude_control->input_euler_angle_roll_pitch_euler_rate_yaw_rad(cd_to_rad(trim_angle_cd.x), cd_to_rad(target_angle_cd + trim_angle_cd.y), 0.0f);866command_reading = motors->get_pitch();867if (test_calc_type == DRB) {868tgt_rate_reading = cd_to_rad(target_angle_cd);869gyro_reading = cd_to_rad((float)ahrs_view->pitch_sensor + trim_angle_cd.y - target_angle_cd);870} else if (test_calc_type == RATE) {871tgt_rate_reading = attitude_control->rate_bf_targets().y;872gyro_reading = ahrs_view->get_gyro().y;873} else {874tgt_rate_reading = cd_to_rad((float)attitude_control->get_att_target_euler_cd().y);875gyro_reading = cd_to_rad((float)ahrs_view->pitch_sensor);876}877break;878case AxisType::YAW:879case AxisType::YAW_D:880attitude_control->input_euler_angle_roll_pitch_yaw_rad(cd_to_rad(trim_angle_cd.x), cd_to_rad(trim_angle_cd.y), cd_to_rad(wrap_180_cd(trim_yaw_tgt_reading_cd + target_angle_cd)), false);881command_reading = motors->get_yaw();882if (test_calc_type == DRB) {883tgt_rate_reading = cd_to_rad(target_angle_cd);884gyro_reading = cd_to_rad(wrap_180_cd((float)ahrs_view->yaw_sensor - trim_yaw_heading_reading_cd - target_angle_cd));885} else if (test_calc_type == RATE) {886tgt_rate_reading = attitude_control->rate_bf_targets().z;887gyro_reading = ahrs_view->get_gyro().z;888} else {889tgt_rate_reading = cd_to_rad(wrap_180_cd((float)attitude_control->get_att_target_euler_cd().z - trim_yaw_tgt_reading_cd));890gyro_reading = cd_to_rad((wrap_180_cd((float)ahrs_view->yaw_sensor - trim_yaw_heading_reading_cd)));891}892break;893}894895if (settle_time == 0) {896filt_command_reading.apply(command_reading, AP::scheduler().get_loop_period_s());897filt_gyro_reading.apply(gyro_reading, AP::scheduler().get_loop_period_s());898filt_tgt_rate_reading.apply(tgt_rate_reading, AP::scheduler().get_loop_period_s());899} else {900filt_command_reading.reset(command_reading);901filt_gyro_reading.reset(gyro_reading);902filt_tgt_rate_reading.reset(tgt_rate_reading);903}904905// looks at gain and phase of input rate to output rate906rotation_rate = rotation_rate_filt.apply((gyro_reading - filt_gyro_reading.get()),907AP::scheduler().get_loop_period_s());908filt_target_rate = target_rate_filt.apply((tgt_rate_reading - filt_tgt_rate_reading.get()),909AP::scheduler().get_loop_period_s());910command_out = command_filt.apply((command_reading - filt_command_reading.get()),911AP::scheduler().get_loop_period_s());912913float dwell_gain_mtr = 0.0f;914float dwell_phase_mtr = 0.0f;915float dwell_gain_tgt = 0.0f;916float dwell_phase_tgt = 0.0f;917// wait for dwell to start before determining gain and phase918if ((float)(now_ms - dwell_start_time_ms) > pre_calc_cycles * cycle_time_ms || (test_input_type == AC_AutoTune_FreqResp::InputType::SWEEP && settle_time == 0)) {919freqresp_mtr.update(command_out,command_out,rotation_rate, dwell_freq);920freqresp_tgt.update(command_out,filt_target_rate,rotation_rate, dwell_freq);921922if (freqresp_mtr.is_cycle_complete()) {923if (test_input_type == AC_AutoTune_FreqResp::InputType::SWEEP) {924if (is_zero(curr_test_mtr.freq) && freqresp_mtr.get_freq() < test_start_freq) {925// don't set data since captured frequency is below the start frequency926} else {927curr_test_mtr.freq = freqresp_mtr.get_freq();928curr_test_mtr.gain = freqresp_mtr.get_gain();929curr_test_mtr.phase = freqresp_mtr.get_phase();930}931// reset cycle_complete to allow indication of next cycle932freqresp_mtr.reset_cycle_complete();933#if HAL_LOGGING_ENABLED934// log sweep data935Log_AutoTuneSweep();936#endif937} else {938dwell_gain_mtr = freqresp_mtr.get_gain();939dwell_phase_mtr = freqresp_mtr.get_phase();940cycle_complete_mtr = true;941}942}943944if (freqresp_tgt.is_cycle_complete()) {945if (test_input_type == AC_AutoTune_FreqResp::InputType::SWEEP) {946if (is_zero(curr_test_tgt.freq) && freqresp_tgt.get_freq() < test_start_freq) {947// don't set data since captured frequency is below the start frequency948} else {949curr_test_tgt.freq = freqresp_tgt.get_freq();950curr_test_tgt.gain = freqresp_tgt.get_gain();951curr_test_tgt.phase = freqresp_tgt.get_phase();952if (test_calc_type == DRB) {test_accel_max_cdss = freqresp_tgt.get_accel_max();}953}954// reset cycle_complete to allow indication of next cycle955freqresp_tgt.reset_cycle_complete();956#if HAL_LOGGING_ENABLED957// log sweep data958Log_AutoTuneSweep();959#endif960} else {961dwell_gain_tgt = freqresp_tgt.get_gain();962dwell_phase_tgt = freqresp_tgt.get_phase();963if (test_calc_type == DRB) {test_accel_max_cdss = freqresp_tgt.get_accel_max();}964cycle_complete_tgt = true;965}966}967968if (test_freq_resp_input == TARGET) {969if (test_input_type == AC_AutoTune_FreqResp::InputType::SWEEP) {970curr_test = curr_test_tgt;971} else {972test_data.freq = test_start_freq;973test_data.gain = dwell_gain_tgt;974test_data.phase = dwell_phase_tgt;975}976} else {977if (test_input_type == AC_AutoTune_FreqResp::InputType::SWEEP) {978curr_test = curr_test_mtr;979} else {980test_data.freq = test_start_freq;981test_data.gain = dwell_gain_mtr;982test_data.phase = dwell_phase_mtr;983}984}985}986987// set sweep data if a frequency sweep is being conducted988if (test_input_type == AC_AutoTune_FreqResp::InputType::SWEEP && (float)(now_ms - dwell_start_time_ms) > 2.5f * cycle_time_ms) {989// track sweep phase to prevent capturing 180 deg and 270 deg data after phase has wrapped.990if (curr_test_tgt.phase > 180.0f && sweep_tgt.progress == 0) {991sweep_tgt.progress = 1;992} else if (curr_test_tgt.phase > 270.0f && sweep_tgt.progress == 1) {993sweep_tgt.progress = 2;994}995if (curr_test_tgt.phase <= 160.0f && curr_test_tgt.phase >= 150.0f && sweep_tgt.progress == 0) {996sweep_tgt.ph180 = curr_test_tgt;997}998if (curr_test_tgt.phase <= 250.0f && curr_test_tgt.phase >= 240.0f && sweep_tgt.progress == 1) {999sweep_tgt.ph270 = curr_test_tgt;1000}1001if (curr_test_tgt.gain > sweep_tgt.maxgain.gain) {1002sweep_tgt.maxgain = curr_test_tgt;1003}1004// Determine sweep info for motor input to response output1005if (curr_test_mtr.phase > 180.0f && sweep_mtr.progress == 0) {1006sweep_mtr.progress = 1;1007} else if (curr_test_mtr.phase > 270.0f && sweep_mtr.progress == 1) {1008sweep_mtr.progress = 2;1009}1010if (curr_test_mtr.phase <= 160.0f && curr_test_mtr.phase >= 150.0f && sweep_mtr.progress == 0) {1011sweep_mtr.ph180 = curr_test_mtr;1012}1013if (curr_test_mtr.phase <= 250.0f && curr_test_mtr.phase >= 240.0f && sweep_mtr.progress == 1) {1014sweep_mtr.ph270 = curr_test_mtr;1015}1016if (curr_test_mtr.gain > sweep_mtr.maxgain.gain) {1017sweep_mtr.maxgain = curr_test_mtr;1018}10191020if (now_ms - step_start_time_ms >= sweep_time_ms + 200) {1021// we have passed the maximum stop time1022sweep_complete = true;1023step = Step::UPDATE_GAINS;1024}1025} else {1026if (now_ms - step_start_time_ms >= step_timeout_ms || (freqresp_tgt.is_cycle_complete() && freqresp_mtr.is_cycle_complete())) {1027if (now_ms - step_start_time_ms >= step_timeout_ms) {1028GCS_SEND_TEXT(MAV_SEVERITY_INFO, "AutoTune: Step time limit exceeded");1029}1030cycle_complete_tgt = false;1031cycle_complete_tgt = false;1032// we have passed the maximum stop time1033step = Step::UPDATE_GAINS;1034}1035}1036}10371038// update gains for the rate p up tune type1039void AC_AutoTune_Heli::updating_rate_p_up_all(AxisType test_axis)1040{1041switch (test_axis) {1042case AxisType::ROLL:1043updating_rate_p_up(tune_roll_rp, curr_data, next_test_freq, max_rate_p);1044break;1045case AxisType::PITCH:1046updating_rate_p_up(tune_pitch_rp, curr_data, next_test_freq, max_rate_p);1047break;1048case AxisType::YAW:1049case AxisType::YAW_D:1050updating_rate_p_up(tune_yaw_rp, curr_data, next_test_freq, max_rate_p);1051break;1052}1053}10541055// update gains for the rate d up tune type1056void AC_AutoTune_Heli::updating_rate_d_up_all(AxisType test_axis)1057{1058switch (test_axis) {1059case AxisType::ROLL:1060updating_rate_d_up(tune_roll_rd, curr_data, next_test_freq, max_rate_d);1061break;1062case AxisType::PITCH:1063updating_rate_d_up(tune_pitch_rd, curr_data, next_test_freq, max_rate_d);1064break;1065case AxisType::YAW:1066case AxisType::YAW_D:1067updating_rate_d_up(tune_yaw_rd, curr_data, next_test_freq, max_rate_d);1068break;1069}1070}10711072// update gains for the rate ff up tune type1073void AC_AutoTune_Heli::updating_rate_ff_up_all(AxisType test_axis)1074{1075switch (test_axis) {1076case AxisType::ROLL:1077updating_rate_ff_up(tune_roll_rff, curr_data, next_test_freq);1078break;1079case AxisType::PITCH:1080updating_rate_ff_up(tune_pitch_rff, curr_data, next_test_freq);1081break;1082case AxisType::YAW:1083case AxisType::YAW_D:1084updating_rate_ff_up(tune_yaw_rff, curr_data, next_test_freq);1085// TODO make FF updating routine determine when to set rff gain to zero based on A/C response1086if (tune_yaw_rff <= AUTOTUNE_RFF_MIN && success_counter == AUTOTUNE_SUCCESS_COUNT) {1087tune_yaw_rff = 0.0f;1088}1089break;1090}1091}10921093// update gains for the angle p up tune type1094void AC_AutoTune_Heli::updating_angle_p_up_all(AxisType test_axis)1095{1096attitude_control->bf_feedforward(orig_bf_feedforward);10971098// sweep doesn't require gain update so return immediately after setting next test freq1099// determine next_test_freq for dwell testing1100if (sweep_complete && input_type == AC_AutoTune_FreqResp::InputType::SWEEP){1101// if a max gain frequency was found then set the start of the dwells to that freq otherwise start at min frequency1102if (!is_zero(sweep_tgt.maxgain.freq)) {1103next_test_freq = constrain_float(sweep_tgt.maxgain.freq, min_sweep_freq, max_sweep_freq);1104freq_max = next_test_freq;1105sp_prev_gain = sweep_tgt.maxgain.gain;1106phase_max = sweep_tgt.maxgain.phase;1107found_max_gain_freq = true;1108} else {1109next_test_freq = min_sweep_freq;1110}1111return;1112}11131114switch (test_axis) {1115case AxisType::ROLL:1116updating_angle_p_up(tune_roll_sp, curr_data, next_test_freq);1117break;1118case AxisType::PITCH:1119updating_angle_p_up(tune_pitch_sp, curr_data, next_test_freq);1120break;1121case AxisType::YAW:1122case AxisType::YAW_D:1123updating_angle_p_up(tune_yaw_sp, curr_data, next_test_freq);1124break;1125}1126}11271128// update gains for the max gain tune type1129void AC_AutoTune_Heli::updating_max_gains_all(AxisType test_axis)1130{1131// sweep doesn't require gain update so return immediately after setting next test freq1132// determine next_test_freq for dwell testing1133if (sweep_complete && input_type == AC_AutoTune_FreqResp::InputType::SWEEP) {1134// if a max gain frequency was found then set the start of the dwells to that freq otherwise start at min frequency1135if (!is_zero(sweep_mtr.ph180.freq)) {1136next_test_freq = constrain_float(sweep_mtr.ph180.freq, min_sweep_freq, max_sweep_freq);1137reset_maxgains_update_gain_variables();1138} else {1139next_test_freq = min_sweep_freq;1140}1141return;1142}11431144switch (test_axis) {1145case AxisType::ROLL:1146updating_max_gains(curr_data, next_test_freq, max_rate_p, max_rate_d, tune_roll_rp, tune_roll_rd);1147break;1148case AxisType::PITCH:1149updating_max_gains(curr_data, next_test_freq, max_rate_p, max_rate_d, tune_pitch_rp, tune_pitch_rd);1150break;1151case AxisType::YAW:1152case AxisType::YAW_D:1153updating_max_gains(curr_data, next_test_freq, max_rate_p, max_rate_d, tune_yaw_rp, tune_yaw_rd);1154// rate P and rate D can be non zero for yaw and need to be included in the max allowed gain1155if (!is_zero(max_rate_p.max_allowed) && success_counter == AUTOTUNE_SUCCESS_COUNT) {1156max_rate_p.max_allowed += tune_yaw_rp;1157}1158if (!is_zero(max_rate_d.max_allowed) && success_counter == AUTOTUNE_SUCCESS_COUNT) {1159max_rate_d.max_allowed += tune_yaw_rd;1160}1161break;1162}1163}11641165// set gains post tune for the tune type1166void AC_AutoTune_Heli::set_tuning_gains_with_backoff(AxisType test_axis)1167{1168switch (tune_type) {1169case TuneType::RATE_D_UP:1170switch (test_axis) {1171case AxisType::ROLL:1172tune_roll_rd = MAX(0.0f, tune_roll_rd * AUTOTUNE_RD_BACKOFF);1173break;1174case AxisType::PITCH:1175tune_pitch_rd = MAX(0.0f, tune_pitch_rd * AUTOTUNE_RD_BACKOFF);1176break;1177case AxisType::YAW:1178case AxisType::YAW_D:1179tune_yaw_rd = MAX(0.0f, tune_yaw_rd * AUTOTUNE_RD_BACKOFF);1180break;1181}1182break;1183case TuneType::RATE_P_UP:1184switch (test_axis) {1185case AxisType::ROLL:1186tune_roll_rp = MAX(0.0f, tune_roll_rp * AUTOTUNE_RP_BACKOFF);1187break;1188case AxisType::PITCH:1189tune_pitch_rp = MAX(0.0f, tune_pitch_rp * AUTOTUNE_RP_BACKOFF);1190break;1191case AxisType::YAW:1192case AxisType::YAW_D:1193tune_yaw_rp = MAX(AUTOTUNE_RP_MIN, tune_yaw_rp * AUTOTUNE_RP_BACKOFF);1194break;1195}1196break;1197case TuneType::ANGLE_P_UP:1198switch (test_axis) {1199case AxisType::ROLL:1200tune_roll_sp = MAX(AUTOTUNE_SP_MIN, tune_roll_sp * AUTOTUNE_SP_BACKOFF);1201break;1202case AxisType::PITCH:1203tune_pitch_sp = MAX(AUTOTUNE_SP_MIN, tune_pitch_sp * AUTOTUNE_SP_BACKOFF);1204break;1205case AxisType::YAW:1206case AxisType::YAW_D:1207tune_yaw_sp = MAX(AUTOTUNE_SP_MIN, tune_yaw_sp * AUTOTUNE_SP_BACKOFF);1208break;1209}1210break;1211case TuneType::RATE_FF_UP:1212break;1213default:1214break;1215}1216}12171218// updating_rate_ff_up - adjust FF to ensure the target is reached1219// FF is adjusted until rate requested is achieved1220void AC_AutoTune_Heli::updating_rate_ff_up(float &tune_ff, sweep_info &test_data, float &next_freq)1221{1222float tune_tgt = 0.95;1223float tune_tol = 0.025;1224next_freq = test_data.freq;12251226// handle axes where FF gain is initially zero1227if (test_data.gain < tune_tgt - tune_tol && !is_positive(tune_ff)) {1228tune_ff = 0.03f;1229return;1230}12311232if (test_data.gain < tune_tgt - 0.2 || test_data.gain > tune_tgt + 0.2) {1233tune_ff = tune_ff * constrain_float(tune_tgt / test_data.gain, 0.75, 1.25); //keep changes less than 25%1234} else if (test_data.gain < tune_tgt - 0.1 || test_data.gain > tune_tgt + 0.1) {1235if (test_data.gain < tune_tgt - 0.1) {1236tune_ff *= 1.05;1237} else {1238tune_ff *= 0.95;1239}1240} else if (test_data.gain < tune_tgt - tune_tol || test_data.gain > tune_tgt + tune_tol) {1241if (test_data.gain < tune_tgt - tune_tol) {1242tune_ff *= 1.02;1243} else {1244tune_ff *= 0.98;1245}1246} else if (test_data.gain >= tune_tgt - tune_tol && test_data.gain <= tune_tgt + tune_tol) {1247success_counter = AUTOTUNE_SUCCESS_COUNT;1248// reset next_freq for next test1249next_freq = 0.0f;1250tune_ff = constrain_float(tune_ff,0.0f,1.0f);1251}1252}12531254// updating_rate_p_up - uses maximum allowable gain determined from max_gain test to determine rate p gain that does not1255// exceed max response gain. A phase of 161 deg is used to conduct the tuning as this phase is where analytically1256// max gain to 6db gain margin is determined for a unity feedback controller.1257void AC_AutoTune_Heli::updating_rate_p_up(float &tune_p, sweep_info &test_data, float &next_freq, max_gain_data &max_gain_p)1258{1259float test_freq_incr = 0.25f * M_2PI;1260next_freq = test_data.freq;12611262sweep_info data_at_ph161;1263float sugg_freq;1264if (freq_search_for_phase(test_data, 161.0f, test_freq_incr, data_at_ph161, sugg_freq)) {1265if (data_at_ph161.gain < max_resp_gain && tune_p < 0.6f * max_gain_p.max_allowed) {1266tune_p += 0.05f * max_gain_p.max_allowed;1267next_freq = data_at_ph161.freq;1268} else {1269success_counter = AUTOTUNE_SUCCESS_COUNT;1270// reset next_freq for next test1271next_freq = 0.0f;1272tune_p -= 0.05f * max_gain_p.max_allowed;1273tune_p = constrain_float(tune_p,0.0f,0.6f * max_gain_p.max_allowed);1274}1275} else {1276next_freq = sugg_freq;1277}1278}12791280// updating_rate_d_up - uses maximum allowable gain determined from max_gain test to determine rate d gain where the response1281// gain is at a minimum. A phase of 161 deg is used to conduct the tuning as this phase is where analytically1282// max gain to 6db gain margin is determined for a unity feedback controller.1283void AC_AutoTune_Heli::updating_rate_d_up(float &tune_d, sweep_info &test_data, float &next_freq, max_gain_data &max_gain_d)1284{1285float test_freq_incr = 0.25f * M_2PI; // set for 1/4 hz increments1286next_freq = test_data.freq;12871288sweep_info data_at_ph161;1289float sugg_freq;1290if (freq_search_for_phase(test_data, 161.0f, test_freq_incr, data_at_ph161, sugg_freq)) {1291if ((data_at_ph161.gain < rd_prev_gain || is_zero(rd_prev_gain)) && tune_d < 0.6f * max_gain_d.max_allowed) {1292tune_d += 0.05f * max_gain_d.max_allowed;1293rd_prev_gain = data_at_ph161.gain;1294next_freq = data_at_ph161.freq;1295} else {1296success_counter = AUTOTUNE_SUCCESS_COUNT;1297// reset next freq and rd_prev_gain for next test1298next_freq = 0;1299rd_prev_gain = 0.0f;1300tune_d -= 0.05f * max_gain_d.max_allowed;1301tune_d = constrain_float(tune_d,0.0f,0.6f * max_gain_d.max_allowed);1302}1303} else {1304next_freq = sugg_freq;1305}1306}13071308// updating_angle_p_up - determines maximum angle p gain for pitch and roll. This is accomplished by determining the frequency1309// for the maximum response gain that is the disturbance rejection peak.1310void AC_AutoTune_Heli::updating_angle_p_up(float &tune_p, sweep_info &test_data, float &next_freq)1311{1312float test_freq_incr = 0.5f * M_2PI;1313float gain_incr = 0.5f;13141315if (is_zero(test_data.phase)) {1316// bad test point. increase slightly in hope of getting better result1317next_freq += 0.5f * test_freq_incr;1318return;1319}13201321if (!found_max_gain_freq) {1322if (test_data.gain > max_resp_gain && tune_p > AUTOTUNE_SP_MIN) {1323// exceeded max response gain already, reduce tuning gain to remain under max response gain1324tune_p -= gain_incr;1325// force counter to stay on frequency1326next_freq = test_data.freq;1327return;1328} else if (test_data.gain > max_resp_gain && tune_p <= AUTOTUNE_SP_MIN) {1329// exceeded max response gain at the minimum allowable tuning gain. terminate testing.1330tune_p = AUTOTUNE_SP_MIN;1331success_counter = AUTOTUNE_SUCCESS_COUNT;1332LOGGER_WRITE_EVENT(LogEvent::AUTOTUNE_REACHED_LIMIT);1333} else if (test_data.gain > sp_prev_gain) {1334freq_max = test_data.freq;1335phase_max = test_data.phase;1336sp_prev_gain = test_data.gain;1337next_freq = test_data.freq + test_freq_incr;1338return;1339// Gain is expected to continue decreasing past gain peak. declare max gain freq found and refine search.1340} else if (test_data.gain < 0.95f * sp_prev_gain) {1341found_max_gain_freq = true;1342next_freq = freq_max + 0.5 * test_freq_incr;1343return;1344} else {1345next_freq = test_data.freq + test_freq_incr;1346return;1347}1348}13491350// refine peak1351if (!found_peak) {1352// look at frequency above max gain freq found1353if (test_data.freq > freq_max && test_data.gain > sp_prev_gain) {1354// found max at frequency greater than initial max gain frequency1355found_peak = true;1356} else if (test_data.freq > freq_max && test_data.gain < sp_prev_gain) {1357// look at frequency below initial max gain frequency1358next_freq = test_data.freq - 0.5 * test_freq_incr;1359return;1360} else if (test_data.freq < freq_max && test_data.gain > sp_prev_gain) {1361// found max at frequency less than initial max gain frequency1362found_peak = true;1363} else {1364found_peak = true;1365test_data.freq = freq_max;1366test_data.gain = sp_prev_gain;1367}1368sp_prev_gain = test_data.gain;1369}13701371// start increasing gain1372if (found_max_gain_freq && found_peak) {1373if (test_data.gain < max_resp_gain && tune_p < AUTOTUNE_SP_MAX) {1374tune_p += gain_incr;1375next_freq = test_data.freq;1376if (tune_p >= AUTOTUNE_SP_MAX) {1377tune_p = AUTOTUNE_SP_MAX;1378success_counter = AUTOTUNE_SUCCESS_COUNT;1379LOGGER_WRITE_EVENT(LogEvent::AUTOTUNE_REACHED_LIMIT);1380}1381sp_prev_gain = test_data.gain;1382} else if (test_data.gain > 1.1f * max_resp_gain && tune_p > AUTOTUNE_SP_MIN) {1383tune_p -= gain_incr;1384} else {1385// adjust tuning gain so max response gain is not exceeded1386if (sp_prev_gain < max_resp_gain && test_data.gain > max_resp_gain) {1387float adj_factor = (max_resp_gain - test_data.gain) / (test_data.gain - sp_prev_gain);1388tune_p = tune_p + gain_incr * adj_factor;1389}1390success_counter = AUTOTUNE_SUCCESS_COUNT;1391}1392}1393if (success_counter == AUTOTUNE_SUCCESS_COUNT) {1394next_freq = 0.0f; //initializes next test that uses dwell test1395sweep_complete = false;1396reset_sweep_variables();1397}1398}13991400// updating_max_gains: use dwells at increasing frequency to determine gain at which instability will occur. This uses the frequency1401// response of motor class input to rate response to determine the max allowable gain for rate P gain. A phase of 161 deg is used to1402// determine analytically the max gain to 6db gain margin for a unity feedback controller. Since acceleration can be more noisy, the1403// response of the motor class input to rate response to determine the max allowable gain for rate D gain. A phase of 251 deg is used1404// to determine analytically the max gain to 6db gain margin for a unity feedback controller.1405void AC_AutoTune_Heli::updating_max_gains(sweep_info &test_data, float &next_freq, max_gain_data &max_gain_p, max_gain_data &max_gain_d, float &tune_p, float &tune_d)1406{1407float test_freq_incr = 0.5f * M_2PI;1408next_freq = test_data.freq;14091410sweep_info data_at_phase;1411float sugg_freq;1412if (!found_max_p) {1413if (freq_search_for_phase(test_data, 161.0f, test_freq_incr, data_at_phase, sugg_freq)) {1414max_gain_p.freq = data_at_phase.freq;1415max_gain_p.gain = data_at_phase.gain;1416max_gain_p.phase = data_at_phase.phase;1417max_gain_p.max_allowed = powf(10.0f,-1 * (log10f(max_gain_p.gain) * 20.0f + 2.42) / 20.0f);1418// limit max gain allowed to be no more than 2x the max p gain limit to keep initial gains bounded1419max_gain_p.max_allowed = constrain_float(max_gain_p.max_allowed, 0.0f, 2.0f * AUTOTUNE_RP_MAX);1420found_max_p = true;1421if (!is_zero(sweep_mtr.ph270.freq)) {1422next_freq = sweep_mtr.ph270.freq;1423} else {1424next_freq = data_at_phase.freq;1425}1426} else {1427next_freq = sugg_freq;1428}1429} else if (!found_max_d) {1430if (freq_search_for_phase(test_data, 251.0f, test_freq_incr, data_at_phase, sugg_freq)) {1431max_gain_d.freq = data_at_phase.freq;1432max_gain_d.gain = data_at_phase.gain;1433max_gain_d.phase = data_at_phase.phase;1434max_gain_d.max_allowed = powf(10.0f,-1 * (log10f(max_gain_d.freq * max_gain_d.gain) * 20.0f + 2.42) / 20.0f);1435// limit max gain allowed to be no more than 2x the max d gain limit to keep initial gains bounded1436max_gain_d.max_allowed = constrain_float(max_gain_d.max_allowed, 0.0f, 2.0f * AUTOTUNE_RD_MAX);1437found_max_d = true;1438} else {1439next_freq = sugg_freq;1440}1441}14421443if (found_max_p && found_max_d) {1444success_counter = AUTOTUNE_SUCCESS_COUNT;1445// reset variables for next test1446next_freq = 0.0f; //initializes next test that uses dwell test1447GCS_SEND_TEXT(MAV_SEVERITY_INFO, "AutoTune: Max rate P freq=%f gain=%f", (double)(max_rate_p.freq), (double)(max_rate_p.gain));1448GCS_SEND_TEXT(MAV_SEVERITY_INFO, "AutoTune: ph=%f rate_p=%f", (double)(max_rate_p.phase), (double)(max_rate_p.max_allowed));1449GCS_SEND_TEXT(MAV_SEVERITY_INFO, "AutoTune: Max Rate D freq=%f gain=%f", (double)(max_rate_d.freq), (double)(max_rate_d.gain));1450GCS_SEND_TEXT(MAV_SEVERITY_INFO, "AutoTune: ph=%f rate_d=%f", (double)(max_rate_d.phase), (double)(max_rate_d.max_allowed));1451}14521453}14541455float AC_AutoTune_Heli::target_angle_max_rp_cd() const1456{1457return AUTOTUNE_ANGLE_TARGET_MAX_RP_CD;1458}14591460float AC_AutoTune_Heli::target_angle_max_y_cd() const1461{1462return AUTOTUNE_ANGLE_TARGET_MAX_Y_CD;1463}14641465float AC_AutoTune_Heli::target_angle_min_rp_cd() const1466{1467return AUTOTUNE_ANGLE_TARGET_MIN_RP_CD;1468}14691470float AC_AutoTune_Heli::target_angle_min_y_cd() const1471{1472return AUTOTUNE_ANGLE_TARGET_MIN_Y_CD;1473}14741475float AC_AutoTune_Heli::angle_lim_max_rp_cd() const1476{1477return AUTOTUNE_ANGLE_MAX_RP_CD;1478}14791480float AC_AutoTune_Heli::angle_lim_neg_rpy_cd() const1481{1482return AUTOTUNE_ANGLE_NEG_RPY_CD;1483}14841485// freq_search_for_phase: general search strategy for specified phase. interpolation done once specified phase has been bounded.1486bool AC_AutoTune_Heli::freq_search_for_phase(sweep_info test, float desired_phase, float freq_incr, sweep_info &est_data, float &new_freq)1487{1488new_freq = test.freq;1489float phase_delta = 20.0f; // delta from desired phase below and above which full steps are taken1490if (is_zero(test.phase)) {1491// bad test point. increase slightly in hope of getting better result1492new_freq += 0.1f * freq_incr;1493return false;1494}14951496// test to see if desired phase is bounded with a 0.5 freq_incr delta in freq1497float freq_delta = fabsf(prev_test.freq - test.freq);1498if (test.phase > desired_phase && prev_test.phase < desired_phase && freq_delta < 0.75f * freq_incr && is_positive(prev_test.freq)) {1499est_data.freq = linear_interpolate(prev_test.freq,test.freq,desired_phase,prev_test.phase,test.phase);1500est_data.gain = linear_interpolate(prev_test.gain,test.gain,desired_phase,prev_test.phase,test.phase);1501est_data.phase = desired_phase;1502prev_test = {};1503return true;1504} else if (test.phase < desired_phase && prev_test.phase > desired_phase && freq_delta < 0.75f * freq_incr && is_positive(prev_test.freq)) {1505est_data.freq = linear_interpolate(test.freq,prev_test.freq,desired_phase,test.phase,prev_test.phase);1506est_data.gain = linear_interpolate(test.gain,prev_test.gain,desired_phase,test.phase,prev_test.phase);1507est_data.phase = desired_phase;1508prev_test = {};1509return true;1510}15111512if (test.phase < desired_phase - phase_delta) {1513new_freq += freq_incr;1514} else if (test.phase > desired_phase + phase_delta) {1515new_freq -= freq_incr;1516} else if (test.phase >= desired_phase - phase_delta && test.phase < desired_phase) {1517new_freq += 0.5f * freq_incr;1518} else if (test.phase <= desired_phase + phase_delta && test.phase >= desired_phase) {1519new_freq -= 0.5f * freq_incr;1520}1521prev_test = test;1522return false;1523}15241525#if HAL_LOGGING_ENABLED1526// log autotune summary data1527void AC_AutoTune_Heli::Log_AutoTune()1528{15291530switch (axis) {1531case AxisType::ROLL:1532Log_Write_AutoTune(axis, tune_type, curr_data.freq, curr_data.gain, curr_data.phase, tune_roll_rff, tune_roll_rp, tune_roll_rd, tune_roll_sp, test_accel_max_cdss);1533break;1534case AxisType::PITCH:1535Log_Write_AutoTune(axis, tune_type, curr_data.freq, curr_data.gain, curr_data.phase, tune_pitch_rff, tune_pitch_rp, tune_pitch_rd, tune_pitch_sp, test_accel_max_cdss);1536break;1537case AxisType::YAW:1538case AxisType::YAW_D:1539Log_Write_AutoTune(axis, tune_type, curr_data.freq, curr_data.gain, curr_data.phase, tune_yaw_rff, tune_yaw_rp, tune_yaw_rd, tune_yaw_sp, test_accel_max_cdss);1540break;1541}1542}15431544// log autotune time history results for command, angular rate, and attitude1545void AC_AutoTune_Heli::Log_AutoTuneDetails()1546{1547if (tune_type == TuneType::ANGLE_P_UP || tune_type == TuneType::TUNE_CHECK) {1548Log_Write_AutoTuneDetails(command_out, 0.0f, 0.0f, filt_target_rate, rotation_rate);1549} else {1550Log_Write_AutoTuneDetails(command_out, filt_target_rate, rotation_rate, 0.0f, 0.0f);1551}1552}15531554// log autotune frequency response data1555void AC_AutoTune_Heli::Log_AutoTuneSweep()1556{1557Log_Write_AutoTuneSweep(curr_test_mtr.freq, curr_test_mtr.gain, curr_test_mtr.phase,curr_test_tgt.freq, curr_test_tgt.gain, curr_test_tgt.phase);1558}15591560// @LoggerMessage: ATNH1561// @Description: Heli AutoTune1562// @Vehicles: Copter1563// @Field: TimeUS: Time since system startup1564// @Field: Axis: which axis is currently being tuned1565// @Field: TuneStep: step in autotune process1566// @Field: Freq: target dwell frequency1567// @Field: Gain: measured gain of dwell1568// @Field: Phase: measured phase of dwell1569// @Field: RFF: new rate gain FF term1570// @Field: RP: new rate gain P term1571// @Field: RD: new rate gain D term1572// @Field: SP: new angle P term1573// @Field: ACC: new max accel term15741575// Write an Autotune summary data packet1576void AC_AutoTune_Heli::Log_Write_AutoTune(AxisType _axis, TuneType tune_step, float dwell_freq, float meas_gain, float meas_phase, float new_gain_rff, float new_gain_rp, float new_gain_rd, float new_gain_sp, float max_accel_radss)1577{1578AP::logger().Write(1579"ATNH",1580"TimeUS,Axis,TuneStep,Freq,Gain,Phase,RFF,RP,RD,SP,ACC",1581"s--E-d----e",1582"F--000----0",1583"QBBffffffff",1584AP_HAL::micros64(),1585(uint8_t)axis,1586tune_step,1587dwell_freq,1588meas_gain,1589meas_phase,1590new_gain_rff,1591new_gain_rp,1592new_gain_rd,1593new_gain_sp,1594degrees(max_accel_radss));1595}15961597// Write an Autotune detailed data packet1598void AC_AutoTune_Heli::Log_Write_AutoTuneDetails(float motor_cmd, float tgt_rate_rads, float rate_rads, float tgt_ang_rad, float ang_rad)1599{1600// @LoggerMessage: ATDH1601// @Description: Heli AutoTune data packet1602// @Vehicles: Copter1603// @Field: TimeUS: Time since system startup1604// @Field: Cmd: current motor command1605// @Field: TRate: current target angular rate1606// @Field: Rate: current angular rate1607// @Field: TAng: current target angle1608// @Field: Ang: current angle1609AP::logger().WriteStreaming(1610"ATDH",1611"TimeUS,Cmd,TRate,Rate,TAng,Ang",1612"s-kkdd",1613"F00000",1614"Qfffff",1615AP_HAL::micros64(),1616motor_cmd,1617tgt_rate_rads*57.3,1618rate_rads*57.3f,1619tgt_ang_rad*57.3,1620ang_rad*57.3f);1621}16221623// Write an Autotune frequency response data packet1624void AC_AutoTune_Heli::Log_Write_AutoTuneSweep(float freq_mtr, float gain_mtr, float phase_mtr, float freq_tgt, float gain_tgt, float phase_tgt)1625{1626// @LoggerMessage: ATSH1627// @Description: Heli AutoTune Sweep packet1628// @Vehicles: Copter1629// @Field: TimeUS: Time since system startup1630// @Field: freq_m: current frequency for motor input to rate1631// @Field: gain_m: current response gain for motor input to rate1632// @Field: phase_m: current response phase for motor input to rate1633// @Field: freq_t: current frequency for target rate to rate1634// @Field: gain_t: current response gain for target rate to rate1635// @Field: phase_t: current response phase for target rate to rate1636AP::logger().WriteStreaming(1637"ATSH",1638"TimeUS,freq_m,gain_m,phase_m,freq_t,gain_t,phase_t",1639"sE-dE-d",1640"F000000",1641"Qffffff",1642AP_HAL::micros64(),1643freq_mtr,1644gain_mtr,1645phase_mtr,1646freq_tgt,1647gain_tgt,1648phase_tgt);1649}1650#endif // HAL_LOGGING_ENABLED16511652// reset the test variables for each vehicle1653void AC_AutoTune_Heli::reset_vehicle_test_variables()1654{1655// reset dwell test variables if sweep was interrupted in order to restart sweep1656if (!is_equal(start_freq, stop_freq)) {1657start_freq = 0.0f;1658stop_freq = 0.0f;1659next_test_freq = 0.0f;1660sweep_complete = false;1661}1662}16631664// reset the update gain variables for heli1665void AC_AutoTune_Heli::reset_update_gain_variables()1666{1667// reset max gain variables1668reset_maxgains_update_gain_variables();16691670// reset rd_up variables1671rd_prev_gain = 0.0f;16721673// reset sp_up variables1674phase_max = 0.0f;1675freq_max = 0.0f;1676sp_prev_gain = 0.0f;1677found_max_gain_freq = false;1678found_peak = false;16791680}16811682// reset the max_gains update gain variables1683void AC_AutoTune_Heli::reset_maxgains_update_gain_variables()1684{1685max_rate_p = {};1686max_rate_d = {};16871688found_max_p = false;1689found_max_d = false;16901691}16921693// reset the max_gains update gain variables1694void AC_AutoTune_Heli::reset_sweep_variables()1695{1696sweep_tgt.ph180 = {};1697sweep_tgt.ph270 = {};1698sweep_tgt.maxgain = {};1699sweep_tgt.progress = 0;17001701sweep_mtr.ph180 = {};1702sweep_mtr.ph270 = {};1703sweep_mtr.maxgain = {};17041705sweep_mtr.progress = 0;17061707}17081709// set the tuning test sequence1710void AC_AutoTune_Heli::set_tune_sequence()1711{1712uint8_t seq_cnt = 0;17131714if (seq_bitmask & AUTOTUNE_SEQ_BITMASK_VFF) {1715tune_seq[seq_cnt] = TuneType::RATE_FF_UP;1716seq_cnt++;1717}1718if (seq_bitmask & AUTOTUNE_SEQ_BITMASK_RATE_D) {1719tune_seq[seq_cnt] = TuneType::MAX_GAINS;1720seq_cnt++;1721tune_seq[seq_cnt] = TuneType::RATE_D_UP;1722seq_cnt++;1723tune_seq[seq_cnt] = TuneType::RATE_P_UP;1724seq_cnt++;1725}1726if (seq_bitmask & AUTOTUNE_SEQ_BITMASK_ANGLE_P) {1727tune_seq[seq_cnt] = TuneType::ANGLE_P_UP;1728seq_cnt++;1729}1730if (seq_bitmask & AUTOTUNE_SEQ_BITMASK_MAX_GAIN && !(seq_bitmask & AUTOTUNE_SEQ_BITMASK_RATE_D)) {1731tune_seq[seq_cnt] = TuneType::MAX_GAINS;1732seq_cnt++;1733}1734if (seq_bitmask & AUTOTUNE_SEQ_BITMASK_TUNE_CHECK) {1735tune_seq[seq_cnt] = TuneType::TUNE_CHECK;1736seq_cnt++;1737}1738tune_seq[seq_cnt] = TuneType::TUNE_COMPLETE;17391740}17411742// get_testing_step_timeout_ms accessor1743uint32_t AC_AutoTune_Heli::get_testing_step_timeout_ms() const1744{1745return AUTOTUNE_TESTING_STEP_TIMEOUT_MS;1746}17471748// exceeded_freq_range - ensures tuning remains inside frequency range1749bool AC_AutoTune_Heli::exceeded_freq_range(float frequency)1750{1751bool ret = false;1752if (frequency < min_sweep_freq || frequency > max_sweep_freq) {1753ret = true;1754}1755return ret;1756}17571758#endif // AC_AUTOTUNE_ENABLED175917601761