LCOV - code coverage report
Current view: top level - AC_PID - AC_PID_2D.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 64 90 71.1 %
Date: 2026-09-09 11:02:08 Functions: 8 15 53.3 %

          Line data    Source code
       1             : /// @file       AC_PID_2D.cpp
       2             : /// @brief      2D PID controller with vector support, input filtering, integrator clamping, and EEPROM-backed gain storage.
       3             : 
       4             : #include <AP_Math/AP_Math.h>
       5             : #include "AC_PID_2D.h"
       6             : 
       7             : #define AC_PID_2D_FILT_D_HZ_MIN      0.005f   // minimum input filter frequency
       8             : 
       9             : const AP_Param::GroupInfo AC_PID_2D::var_info[] = {
      10             :     // @Param: P
      11             :     // @DisplayName: PID Proportional Gain
      12             :     // @Description: P Gain which produces an output value that is proportional to the current error value
      13             :     AP_GROUPINFO_FLAGS_DEFAULT_POINTER("P",    0, AC_PID_2D, _kp, default_kp),
      14             : 
      15             :     // @Param: I
      16             :     // @DisplayName: PID Integral Gain
      17             :     // @Description: I Gain which produces an output that is proportional to both the magnitude and the duration of the error
      18             :     AP_GROUPINFO_FLAGS_DEFAULT_POINTER("I",    1, AC_PID_2D, _ki, default_ki),
      19             : 
      20             :     // @Param: IMAX
      21             :     // @DisplayName: PID Integral Maximum
      22             :     // @Description: The maximum/minimum value that the I term can output
      23             :     AP_GROUPINFO_FLAGS_DEFAULT_POINTER("IMAX", 2, AC_PID_2D, _kimax, default_kimax),
      24             : 
      25             :     // @Param: FLTE
      26             :     // @DisplayName: PID Error filter frequency in Hz
      27             :     // @Description: Low-pass filter frequency applied to the error (Hz)
      28             :     // @Units: Hz
      29             :     AP_GROUPINFO_FLAGS_DEFAULT_POINTER("FLTE", 3, AC_PID_2D, _filt_E_hz, default_filt_E_hz),
      30             : 
      31             :     // @Param: D
      32             :     // @DisplayName: PID Derivative Gain
      33             :     // @Description: D Gain which produces an output that is proportional to the rate of change of the error
      34             :     AP_GROUPINFO_FLAGS_DEFAULT_POINTER("D",    4, AC_PID_2D, _kd, default_kd),
      35             : 
      36             :     // @Param: FLTD
      37             :     // @DisplayName: D term filter frequency in Hz
      38             :     // @Description: Low-pass filter frequency applied to the derivative (Hz)
      39             :     // @Units: Hzs
      40             :     AP_GROUPINFO_FLAGS_DEFAULT_POINTER("FLTD", 5, AC_PID_2D, _filt_D_hz, default_filt_D_hz),
      41             : 
      42             :     // @Param: FF
      43             :     // @DisplayName: PID Feed Forward Gain
      44             :     // @Description: FF Gain which produces an output that is proportional to the magnitude of the target
      45             :     AP_GROUPINFO_FLAGS_DEFAULT_POINTER("FF",    6, AC_PID_2D, _kff, default_kff),
      46             : 
      47             :     AP_GROUPEND
      48             : };
      49             : 
      50             : // Constructor
      51        1570 : AC_PID_2D::AC_PID_2D(float initial_kP, float initial_kI, float initial_kD, float initial_kFF, float initial_imax, float initial_filt_E_hz, float initial_filt_D_hz) :
      52        1570 :     default_kp(initial_kP),
      53        1570 :     default_ki(initial_kI),
      54        1570 :     default_kd(initial_kD),
      55        1570 :     default_kff(initial_kFF),
      56        1570 :     default_kimax(initial_imax),
      57        1570 :     default_filt_E_hz(initial_filt_E_hz),
      58        1570 :     default_filt_D_hz(initial_filt_D_hz)
      59             : {
      60             :     // load parameter values from eeprom
      61        1570 :     AP_Param::setup_object_defaults(this, var_info);
      62             : 
      63             :     // reset input filter to first value received
      64        1570 :     _reset_filter = true;
      65        1570 : }
      66             : 
      67             : // Computes the 2D PID output from target and measurement vectors.
      68             : // Applies filtering to error and derivative terms.
      69             : // Integrator is updated only if it does not grow in the direction of the specified limit vector.
      70    19442525 : Vector2f AC_PID_2D::update_all(const Vector2f &target, const Vector2f &measurement, float dt, const Vector2f &limit)
      71             : {
      72             :     // Return zero if any component is NaN or infinite
      73    58327575 :     if (target.is_nan() || target.is_inf() ||
      74    58327575 :         measurement.is_nan() || measurement.is_inf()) {
      75           0 :         return Vector2f{};
      76             :     }
      77             : 
      78    19442525 :     _target = target;
      79             : 
      80             :     // reset input filter to value received
      81    19442525 :     if (_reset_filter) {
      82             :         // Reset filters to match the current inputs
      83     2317957 :         _reset_filter = false;
      84     2317957 :         _error = _target - measurement;
      85             :         // Reset the derivative to avoid transients
      86     2317957 :         _derivative.zero();
      87             :     } else {
      88    17124568 :         Vector2f error_last{_error};
      89             :         // Apply first-order low-pass filter to error
      90    17124568 :         _error += ((_target - measurement) - _error) * get_filt_E_alpha(dt);
      91             : 
      92             :         // Compute and low-pass filter the derivative
      93    17124568 :         if (is_positive(dt)) {
      94    17113242 :             const Vector2f derivative{(_error - error_last) / dt};
      95    17113242 :             _derivative += (derivative - _derivative) * get_filt_D_alpha(dt);
      96             :         }
      97             :     }
      98             : 
      99             :     // update I term
     100    19442525 :     update_i(dt, limit);
     101             :  
     102    19442525 :     _pid_info_x.target = _target.x;
     103    19442525 :     _pid_info_x.actual = measurement.x;
     104    19442525 :     _pid_info_x.error = _error.x;
     105    19442525 :     _pid_info_x.P = _error.x * _kp;
     106    19442525 :     _pid_info_x.I = _integrator.x;
     107    19442525 :     _pid_info_x.D = _derivative.x * _kd;
     108    19442525 :     _pid_info_x.FF = _target.x * _kff;
     109             : 
     110    19442525 :     _pid_info_y.target = _target.y;
     111    19442525 :     _pid_info_y.actual = measurement.y;
     112    19442525 :     _pid_info_y.error = _error.y;
     113    19442525 :     _pid_info_y.P = _error.y * _kp;
     114    19442525 :     _pid_info_y.I = _integrator.y;
     115    19442525 :     _pid_info_y.D = _derivative.y * _kd;
     116    19442525 :     _pid_info_y.FF = _target.y * _kff;
     117             : 
     118             :     // Return total control output: P + I + D + FF terms
     119    19442525 :     return _error * _kp + _integrator + _derivative * _kd + _target * _kff;
     120             : }
     121             : 
     122           0 : Vector2f AC_PID_2D::update_all(const Vector3f &target, const Vector3f &measurement, float dt, const Vector3f &limit)
     123             : {
     124           0 :     return update_all(Vector2f{target.x, target.y}, Vector2f{measurement.x, measurement.y}, dt, Vector2f{limit.x, limit.y});
     125             : }
     126             : 
     127             : // Updates the 2D integrator using the filtered error.
     128             : // The integrator is only allowed to grow if it does not push further in the direction of the limit vector.
     129    19442525 : void AC_PID_2D::update_i(float dt, const Vector2f &limit)
     130             : {
     131    19442525 :     _pid_info_x.limit = false;
     132    19442525 :     _pid_info_y.limit = false;
     133             : 
     134    19442525 :     Vector2f delta_integrator = (_error * _ki) * dt;
     135    19442525 :     float integrator_length = _integrator.length();
     136    19442525 :     _integrator += delta_integrator;
     137             :     // Compute integrator delta and apply anti-windup by limiting growth in the direction of the limit vector
     138    19442525 :     if (is_positive(delta_integrator * limit) && _integrator.limit_length(integrator_length)) {
     139      363303 :         _pid_info_x.limit = true;
     140      363303 :         _pid_info_y.limit = true;
     141             :     }
     142             : 
     143             :     // Clamp integrator to maximum length (IMAX)
     144    19442525 :     _integrator.limit_length(_kimax);
     145    19442525 : }
     146             : 
     147           0 : Vector2f AC_PID_2D::get_p() const
     148             : {
     149           0 :     return _error * _kp;
     150             : }
     151             : 
     152      178458 : const Vector2f& AC_PID_2D::get_i() const
     153             : {
     154      178458 :     return _integrator;
     155             : }
     156             : 
     157           0 : Vector2f AC_PID_2D::get_d() const
     158             : {
     159           0 :     return _derivative * _kd;
     160             : }
     161             : 
     162             : // Update FF terms in PID logs and return feedforward vector
     163           0 : Vector2f AC_PID_2D::get_ff()
     164             : {
     165           0 :     _pid_info_x.FF = _target.x * _kff;
     166           0 :     _pid_info_y.FF = _target.y * _kff;
     167           0 :     return _target * _kff;
     168             : }
     169             : 
     170          80 : void AC_PID_2D::reset_I()
     171             : {
     172          80 :     _integrator.zero(); 
     173          80 : }
     174             : 
     175             : // Saves controller configuration from EEPROM, including gains and filter frequencies. (not used)
     176           0 : void AC_PID_2D::save_gains()
     177             : {
     178           0 :     _kp.save();
     179           0 :     _ki.save();
     180           0 :     _kd.save();
     181           0 :     _kff.save();
     182           0 :     _kimax.save();
     183           0 :     _filt_E_hz.save();
     184           0 :     _filt_D_hz.save();
     185           0 : }
     186             : 
     187             : // Returns alpha value for the error low-pass filter (based on filter frequency and dt)
     188    17124568 : float AC_PID_2D::get_filt_E_alpha(float dt) const
     189             : {
     190    17124568 :     return calc_lowpass_alpha_dt(dt, _filt_E_hz);
     191             : }
     192             : 
     193             : // Returns alpha value for the derivative low-pass filter (based on filter frequency and dt)
     194    17113242 : float AC_PID_2D::get_filt_D_alpha(float dt) const
     195             : {
     196    17113242 :     return calc_lowpass_alpha_dt(dt, _filt_D_hz);
     197             : }
     198             : 
     199             : // Compute error from target and measurement, then set integrator
     200           0 : void AC_PID_2D::set_integrator(const Vector2f& target, const Vector2f& measurement, const Vector2f& i)
     201             : {
     202           0 :     set_integrator(target - measurement, i);
     203           0 : }
     204             : 
     205             : // Convert from desired total output and error to integrator value
     206           0 : void AC_PID_2D::set_integrator(const Vector2f& error, const Vector2f& i)
     207             : {
     208           0 :     set_integrator(i - error * _kp);
     209           0 : }
     210             : 
     211             : // Set integrator directly and clamp to IMAX
     212     3887333 : void AC_PID_2D::set_integrator(const Vector2f& i)
     213             : {
     214     3887333 :     _integrator = i;
     215     3887333 :     _integrator.limit_length(_kimax);
     216     3887333 : }
     217             : 

Generated by: LCOV version 1.14