LCOV - code coverage report
Current view: top level - APM_Control - AP_FW_Controller.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 148 156 94.9 %
Date: 2026-09-09 11:02:08 Functions: 16 16 100.0 %

          Line data    Source code
       1             : /*
       2             :    This program is free software: you can redistribute it and/or modify
       3             :    it under the terms of the GNU General Public License as published by
       4             :    the Free Software Foundation, either version 3 of the License, or
       5             :    (at your option) any later version.
       6             : 
       7             :    This program is distributed in the hope that it will be useful,
       8             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
       9             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      10             :    GNU General Public License for more details.
      11             : 
      12             :    You should have received a copy of the GNU General Public License
      13             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.
      14             :  */
      15             : //      Code by Jon Challinger
      16             : //  Modified by Paul Riseborough
      17             : //
      18             : 
      19             : 
      20             : #include "AP_FW_Controller.h"
      21             : #include <AP_AHRS/AP_AHRS.h>
      22             : #include <AP_Scheduler/AP_Scheduler.h>
      23             : #include <GCS_MAVLink/GCS.h>
      24             : #include <AP_Math/AP_Math.h>
      25             : 
      26        1384 : AP_FW_Controller::AP_FW_Controller(const AP_FixedWing &parms, const AC_PID::Defaults &defaults, AP_AutoTune::ATType _autotune_type)
      27        1384 :     : aparm(parms),
      28        1384 :       rate_pid(defaults),
      29        2768 :       autotune_type(_autotune_type)
      30             : {
      31        1384 :     rate_pid.set_slew_limit_scale(45);
      32        1384 : }
      33             : 
      34             : // Return true if input shaping should be used
      35    15559752 : bool AP_FW_Controller::should_apply_input_shaping() const
      36             : {
      37             :     // Must be using rate limits
      38    15559752 :     if (!should_apply_rate_limits()) {
      39      254025 :         return false;
      40             :     }
      41             : 
      42             :     // Accel limit must be set
      43    15305727 :     if (!is_positive(accel_limit.get())) {
      44      762064 :         return false;
      45             :     }
      46             : 
      47             :     // auto-tune must not be running
      48    14543663 :     if ((autotune != nullptr) && autotune->running) {
      49       39490 :         return false;
      50             :     }
      51             : 
      52    14504173 :     return true;
      53             : }
      54             : 
      55             : // Run angle controller
      56    10693292 : float AP_FW_Controller::run_angle_control(int32_t desired_angle_cd, float scaler, bool disable_integrator, bool ground_mode)
      57             : {
      58             :     // Ensure tau is valid
      59    10693292 :     if (gains.tau < 0.05f) {
      60           0 :         gains.tau.set(0.05f);
      61             :     }
      62    10693292 :     const float desired_angle_deg = wrap_180(desired_angle_cd * 0.01);
      63             : 
      64    10693292 :     if (!should_apply_input_shaping()) {
      65             :         // Calculate rate directly from angle error with no input shaping
      66      822909 :         angle_err_deg = wrap_180(desired_angle_deg - get_measured_angle_deg());
      67      822909 :         float desired_rate_degs = angle_err_deg / gains.tau;
      68             : 
      69             :         // Reset input shaping set points
      70      822909 :         reset_input_shaping_deg(desired_angle_deg, desired_rate_degs);
      71             : 
      72             :         // Add coordination offset
      73      822909 :         desired_rate_degs += get_rate_target_offset_degs();
      74             : 
      75             :         // Apply rate limits if enabled
      76      822909 :         if (should_apply_rate_limits()) {
      77      618145 :             desired_rate_degs = rate_limit_degs(desired_rate_degs);
      78             :         }
      79             : 
      80             :         // Run rate controller
      81      822909 :         return run_axis_rate_control(desired_rate_degs, scaler, disable_integrator, ground_mode);
      82             :     }
      83             : 
      84             :     // Apply input shaping to desired angle
      85     9870383 :     const float dt = AP::scheduler().get_loop_period_s();
      86             : 
      87     9870383 :     const float accel_max = accel_limit.get();
      88     9870383 :     const float jerk_limit = accel_max / MAX(gains.tau.get(), 0.1);
      89             : 
      90             :     // Ensure the shortest path is taken
      91     9870383 :     const float angle_error = wrap_180(desired_angle_deg - angle_target_deg);
      92             : 
      93             :     // Apply input shaping updating the accel target
      94    19740766 :     shape_pos_vel_accel(
      95             :         angle_error, 0.0, 0.0, // desired pos, vel and accel
      96     9870383 :         0.0, rate_target_degs, accel_target_degss, // current shaped target
      97     9870383 :         -get_negative_rate_limit_degs(), get_positive_rate_limit_degs(), // velocity limits
      98             :         -accel_max, accel_max, // accel limits
      99             :         jerk_limit, // jerk limit
     100             :         dt, true
     101             :     );
     102             : 
     103             :     // Integrate pos and vel from updated accel target
     104     9870383 :     angle_target_deg += rate_target_degs * dt + accel_target_degss * 0.5 * sq(dt);
     105     9870383 :     rate_target_degs += accel_target_degss * dt;
     106             : 
     107             :     // Make sure target remains in the range +-180
     108     9870383 :     angle_target_deg = wrap_180(angle_target_deg);
     109             : 
     110             :     // Calculate angle error
     111     9870383 :     angle_err_deg = wrap_180(angle_target_deg - get_measured_angle_deg());
     112             : 
     113             :     // Use 1 / tau if angle gain is not set
     114     9870383 :     float angle_gain = 1.0 / gains.tau.get();
     115     9870383 :     if (is_positive(angle_p.get())) {
     116           0 :         angle_gain = angle_p.get();
     117             :     }
     118             : 
     119             :     // Apply gain using sqrt controller
     120     9870383 :     float desired_rate_degs = sqrt_controller(angle_err_deg, angle_gain, accel_max * 0.5, dt);
     121             : 
     122             :     // Add feed forward rate demand and offset then constrain to rate limit
     123     9870383 :     desired_rate_degs = rate_limit_degs(desired_rate_degs + rate_target_degs + get_rate_target_offset_degs());
     124             : 
     125             :     // Run rate controller
     126     9870383 :     return run_axis_rate_control(desired_rate_degs, scaler, disable_integrator, ground_mode);
     127             : }
     128             : 
     129             : /*
     130             :   AC_PID based rate controller
     131             : */
     132    15125334 : float AP_FW_Controller::run_rate_control(float desired_rate_degs, float scaler, bool disable_integrator, bool ground_mode)
     133             : {
     134             : #if CONFIG_HAL_BOARD == HAL_BOARD_SITL
     135             :     // Check that the controller is called once per loop and no more
     136    15125334 :     const uint32_t ticks = AP::scheduler().ticks32();
     137    15125334 :     if (last_run_ticks != 0) {
     138    15124480 :         if (last_run_ticks == ticks) {
     139           0 :             AP_HAL::panic("FW rate control must be run only once per loop");
     140             :         }
     141    15124480 :         if ((last_run_ticks + 1) != ticks) {
     142           0 :             AP_HAL::panic("FW rate control must be run or reset every loop");
     143             :         }
     144             :     }
     145    15125334 :     last_run_ticks = ticks;
     146             : #endif // CONFIG_HAL_BOARD == HAL_BOARD_SITL
     147             : 
     148    15125334 :     const float dt = AP::scheduler().get_loop_period_s();
     149             : 
     150    15125334 :     const float eas2tas = AP::ahrs().get_EAS2TAS();
     151    15125334 :     bool limit_I = fabsf(_last_out) >= 45;
     152    15125334 :     const float rate_rads = get_measured_rate_rads();
     153    15125334 :     const float old_I = rate_pid.get_i();
     154             : 
     155    15125334 :     const bool underspeed = is_underspeed();
     156    15125334 :     if (underspeed) {
     157     7001082 :         limit_I = true;
     158             :     }
     159             : 
     160             :     // the PID elements are scaled by sq(scaler). To use an
     161             :     // unmodified AC_PID object we scale the inputs (target and measurement)
     162             :     //
     163             :     // note that we run AC_PID in radians so that the normal scaling
     164             :     // range for IMAX in AC_PID applies (usually an IMAX value less than 1.0)
     165    15125334 :     rate_pid.update_all(radians(desired_rate_degs) * scaler * scaler, rate_rads * scaler * scaler, dt, limit_I);
     166             : 
     167    15125334 :     if (underspeed) {
     168             :         // when underspeed we lock the integrator
     169     7001082 :         rate_pid.set_integrator(old_I);
     170             :     }
     171             : 
     172             :     // FF and DFF should be scaled by scaler/eas2tas, but since we have scaled
     173             :     // the AC_PID target above by scaler*scaler we need to instead
     174             :     // divide by scaler*eas2tas to get the right scaling
     175    15125334 :     const float ff = degrees(ff_scale * rate_pid.get_ff_component() / (scaler * eas2tas));
     176    15125334 :     const float dff = degrees(ff_scale * rate_pid.get_dff_component() / (scaler * eas2tas));
     177    15125334 :     ff_scale = 1.0;
     178             : 
     179    15125334 :     if (disable_integrator) {
     180         978 :         rate_pid.reset_I();
     181             :     }
     182             : 
     183             :     // convert AC_PID info object to same scale as old controller
     184    15125334 :     _pid_info = rate_pid.get_pid_info();
     185    15125334 :     auto &pinfo = _pid_info;
     186             : 
     187    15125334 :     const float deg_scale = degrees(1);
     188    15125334 :     pinfo.FF = ff;
     189    15125334 :     pinfo.P *= deg_scale;
     190    15125334 :     pinfo.I *= deg_scale;
     191    15125334 :     pinfo.D *= deg_scale;
     192    15125334 :     pinfo.DFF = dff;
     193             : 
     194             :     // fix the logged target and actual values to not have the scalers applied
     195    15125334 :     pinfo.target = desired_rate_degs;
     196    15125334 :     pinfo.actual = degrees(rate_rads);
     197             : 
     198             :     // sum components
     199    15125334 :     float out = pinfo.FF + pinfo.P + pinfo.I + pinfo.D + pinfo.DFF;
     200    15125334 :     if (ground_mode) {
     201             :         // when on ground suppress D and half P term to prevent oscillations
     202     2600584 :         out -= pinfo.D + 0.5*pinfo.P;
     203             :     }
     204             : 
     205             :     // remember the last output to trigger the I limit
     206    15125334 :     _last_out = out;
     207             : 
     208    15125334 :     if (autotune != nullptr && autotune->running && get_airspeed() > aparm.airspeed_min) {
     209             :         // let autotune have a go at the values
     210       37088 :         autotune->update(pinfo, scaler, angle_err_deg);
     211             :     }
     212             : 
     213             :     // output is scaled to notional centidegrees of deflection
     214    15125334 :     return constrain_float(out * 100, -4500, 4500);
     215             : }
     216             : 
     217             : /*
     218             :  Function returns an equivalent control surface deflection in centi-degrees in the range from -4500 to 4500
     219             : */
     220     4432042 : float AP_FW_Controller::run_rate_control(float desired_rate_degs, float scaler)
     221             : {
     222             :     // Zero angle error in pure rate control
     223     4432042 :     angle_err_deg = 0.0;
     224             : 
     225     4432042 :     if (!should_apply_input_shaping()) {
     226             :         // Reset input shaping set points
     227      225410 :         reset_input_shaping_deg(get_measured_angle_deg(), desired_rate_degs);
     228             : 
     229             :         // run rate control with no input shaping
     230      225410 :         return run_rate_control(desired_rate_degs, scaler, false, false);
     231             :     }
     232             : 
     233             :     // Apply input shaping to desired rate
     234     4206632 :     const float dt = AP::scheduler().get_loop_period_s();
     235             : 
     236             :     // Reset the input shaping target angle
     237     4206632 :     angle_target_deg = get_measured_angle_deg();
     238             : 
     239     4206632 :     const float accel_max = accel_limit.get();
     240     4206632 :     const float jerk_limit = accel_max / MAX(gains.tau.get(), 0.1);
     241             : 
     242             :     // Apply input shaping updating the accel target
     243     8413264 :     shape_pos_vel_accel(
     244             :         0.0, desired_rate_degs, 0.0, // desired pos, vel and accel
     245     4206632 :         0.0, rate_target_degs, accel_target_degss, // current shaped target
     246     4206632 :         -get_negative_rate_limit_degs(), get_positive_rate_limit_degs(), // velocity limits
     247             :         -accel_max, accel_max, // accel limits
     248             :         jerk_limit, // jerk limit
     249             :         dt, true
     250             :     );
     251             : 
     252     4206632 :     rate_target_degs += accel_target_degss * dt;
     253             : 
     254             :     // Run rate controller
     255     4206632 :     return run_rate_control(rate_target_degs, scaler, false, false);
     256             : }
     257             : 
     258             : // Reset I term
     259     4025984 : void AP_FW_Controller::reset_I()
     260             : {
     261     4025984 :     rate_pid.reset_I();
     262     4025984 :     _last_out = 0.0;
     263     4025984 : }
     264             : 
     265             : /*
     266             :     reduce the integrator, used when we have a low scale factor in a quadplane hover
     267             : */
     268     4526724 : void AP_FW_Controller::decay_I()
     269             : {
     270             :     // this reduces integrator by 95% over 2s
     271     4526724 :     _pid_info.I *= 0.995f;
     272     4526724 :     rate_pid.set_integrator(rate_pid.get_i() * 0.995);
     273     4526724 : }
     274             : 
     275             : /*
     276             :   restore autotune gains
     277             :  */
     278        3848 : void AP_FW_Controller::autotune_restore(void)
     279             : {
     280        3848 :     if (autotune != nullptr) {
     281          64 :         autotune->stop();
     282             :     }
     283        3848 : }
     284             : 
     285             : /*
     286             :   start an autotune
     287             :  */
     288           8 : void AP_FW_Controller::autotune_start(void)
     289             : {
     290           8 :     if (autotune == nullptr) {
     291           6 :         autotune = NEW_NOTHROW AP_AutoTune(gains, autotune_type, aparm, rate_pid);
     292           6 :         if (autotune == nullptr) {
     293           0 :             if (!failed_autotune_alloc) {
     294           0 :                 GCS_SEND_TEXT(MAV_SEVERITY_ERROR, "AutoTune: failed %s allocation", AP_AutoTune::axis_string(autotune_type));
     295             :             }
     296           0 :             failed_autotune_alloc = true;
     297             :         }
     298             :     }
     299           8 :     if (autotune != nullptr) {
     300           8 :         autotune->start();
     301             :     }
     302           8 : }
     303             : 
     304             : // Return the airspeed in m/s
     305    20412851 : float AP_FW_Controller::get_airspeed() const
     306             : {
     307             :     float aspeed;
     308    20412851 :     if (!AP::ahrs().airspeed_EAS(aspeed)) {
     309             :         // If no airspeed available use average of min and max
     310      101024 :         aspeed = 0.5f*(float(aparm.airspeed_min) + float(aparm.airspeed_max));
     311             :     }
     312    20412851 :     return aspeed;
     313             : }
     314             : 
     315             : // Reset controller
     316     1628664 : void AP_FW_Controller::reset()
     317             : {
     318             : #if CONFIG_HAL_BOARD == HAL_BOARD_SITL
     319             :     // Reset tick tracking
     320     1628664 :     last_run_ticks = 0;
     321             : #endif // CONFIG_HAL_BOARD == HAL_BOARD_SITL
     322             : 
     323             :     // Reset PID
     324     1628664 :     rate_pid.reset_I();
     325     1628664 :     rate_pid.reset_filter();
     326     1628664 :     _last_out = 0.0;
     327             : 
     328             :     // Reset input shaping
     329     1628664 :     reset_input_shaping_deg(get_measured_angle_deg(), degrees(get_measured_rate_rads()));
     330     1628664 : }
     331             : 
     332             : // Apply positive and negative rate limits to passed in value
     333    13165511 : float AP_FW_Controller::rate_limit_degs(float rate_degs) const
     334             : {
     335    13165511 :     const float pos_rate_limit = get_positive_rate_limit_degs();
     336    13165511 :     if (is_positive(pos_rate_limit)) {
     337     4073984 :         rate_degs = MIN(rate_degs, pos_rate_limit);
     338             :     }
     339             : 
     340    13165511 :     const float neg_rate_limit = get_negative_rate_limit_degs();
     341    13165511 :     if (is_positive(neg_rate_limit)) {
     342     4073984 :         rate_degs = MAX(rate_degs, -neg_rate_limit);
     343             :     }
     344             : 
     345    13165511 :     return rate_degs;
     346             : }
     347             : 
     348             : // Reset input shaping applying rate limits
     349     2676983 : void AP_FW_Controller::reset_input_shaping_deg(const float angle_deg, const float rate_degs)
     350             : {
     351             :     // No angle limits at the controller level, reset to the passed in angle
     352     2676983 :     angle_target_deg = angle_deg;
     353             : 
     354             :     // Reset to passed in rate and apply rate limits
     355     2676983 :     rate_target_degs = rate_limit_degs(rate_degs);
     356             : 
     357             :     // Reset accel to zero
     358     2676983 :     accel_target_degss = 0.0;
     359     2676983 : }
     360             : 
     361             : // Get input shaping angle, rate and accel for logging
     362     2033082 : void AP_FW_Controller::get_input_shaping(float &angle_deg, float &rate_degs, float &accel_degss) const
     363             : {
     364     2033082 :     angle_deg = angle_target_deg;
     365     2033082 :     rate_degs = rate_target_degs;
     366     2033082 :     accel_degss = accel_target_degss;
     367     2033082 : }
     368             : 
     369             : // Reset the attitude target to such that a change in attitude due to an ahrs change is smooth
     370         848 : void AP_FW_Controller::ahrs_reset()
     371             : {
     372             :     // Update the target angle such that the angle error remains the same before and after the change in measured angle from the ahrs
     373         848 :     angle_target_deg = wrap_180(get_measured_angle_deg() + angle_err_deg);
     374         848 : }
     375             : 
     376             : // Get angle P gain
     377      434418 : float AP_FW_Controller::get_angle_p() const
     378             : {
     379      434418 :     if (should_apply_input_shaping() && is_positive(angle_p.get())) {
     380             :         // Angle gain is in use and configured
     381           0 :         return angle_p.get();
     382             :     }
     383             : 
     384             :     // angle gain = 1 / tau
     385      434418 :     return 1.0 / gains.tau.get();
     386             : }

Generated by: LCOV version 1.14