LCOV - code coverage report
Current view: top level - Filter - SlewLimiter.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 57 58 98.3 %
Date: 2026-09-09 11:02:08 Functions: 2 2 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             : /*
      16             :   The SlewLimiter filter provides an actuator slew rate limiter for
      17             :   PID controllers. It is used to reduce the P and D gains when the
      18             :   filter detects that the P+D components are pushing the actuator
      19             :   beyond the configured actuator rate limit. This can prevent
      20             :   oscillations that are caused by the output actuation rate going
      21             :   beyond the actuator maximum physical rate, which causes the
      22             :   actuation demand and achieved rate to get out of phase.
      23             : 
      24             :   this filter was originally written by Paul Riseborough for fixed
      25             :   wing use. It was adapted for wider use in AC_PID by Andrew Tridgell
      26             :  */
      27             : #include "SlewLimiter.h"
      28             : 
      29             : #define WINDOW_MS 300                   // time in msec required for a half cycle of the slowest oscillation frequency expected
      30             : #define MODIFIER_GAIN 1.5f              // ratio of modifier reduction to slew rate exceedance ratio
      31             : #define DERIVATIVE_CUTOFF_FREQ 25.0f
      32             : 
      33        9702 : SlewLimiter::SlewLimiter(const float &_slew_rate_max, const float &_slew_rate_tau) :
      34        9702 :     slew_rate_max(_slew_rate_max),
      35        9702 :     slew_rate_tau(_slew_rate_tau)
      36             : {
      37        9702 :     slew_filter.set_cutoff_frequency(DERIVATIVE_CUTOFF_FREQ);
      38        9702 :     slew_filter.reset(0.0);
      39        9702 : }
      40             : 
      41             : /*
      42             :   apply filter to sample, returning multiplier between 0 and 1 to keep
      43             :   output within slew rate
      44             :  */
      45   144423235 : float SlewLimiter::modifier(float sample, float dt)
      46             : {
      47   144423235 :     if (!is_positive(dt)) {
      48           0 :         return 1.0;
      49             :     }
      50             : 
      51             :     // Calculate a low pass filtered slew rate
      52   144423235 :     const float slew_rate = slew_filter.apply((sample - last_sample) / dt, dt);
      53   144423235 :     last_sample = sample;
      54             : 
      55   144423235 :     uint32_t now_ms = AP_HAL::millis();
      56             : 
      57             :     // Apply a filter to decay maximum seen slew rate once the value had left the window period
      58   144423234 :     const float decay_alpha = fminf(dt, slew_rate_tau) / slew_rate_tau;
      59             : 
      60             :     // Apply a filter to increases in slew rate only to reduce the effect of gusts and large controller setpoint changes
      61   144423234 :     const float attack_alpha = fminf(2.0f * decay_alpha, 1.0f);
      62             : 
      63             :     // Decay the peak positive and negative slew rate if they are outside the window
      64             :     // Never drop PID gains below 10% of configured value
      65   144423234 :     if (slew_rate > _max_pos_slew_rate) {
      66     2486854 :         _max_pos_slew_rate = slew_rate;
      67     2486854 :         _max_pos_slew_event_ms = now_ms;
      68   141936380 :     } else if (now_ms - _max_pos_slew_event_ms > WINDOW_MS) {
      69    79787545 :         _max_pos_slew_rate *= (1.0f - decay_alpha);
      70             :     }
      71             : 
      72   144423234 :     if (-slew_rate > _max_neg_slew_rate) {
      73     2429887 :         _max_neg_slew_rate = -slew_rate;
      74     2429887 :         _max_neg_slew_event_ms = now_ms;
      75   141993347 :     } else if (now_ms - _max_neg_slew_event_ms > WINDOW_MS) {
      76    81397840 :         _max_neg_slew_rate *= (1.0f - decay_alpha);
      77             :     }
      78             : 
      79   144423234 :     const float raw_slew_rate = 0.5f*(_max_pos_slew_rate + _max_neg_slew_rate);
      80   144423234 :     _output_slew_rate = (1.0f - attack_alpha) * _output_slew_rate + attack_alpha * raw_slew_rate;
      81   144423234 :     _output_slew_rate = fminf(_output_slew_rate, raw_slew_rate);
      82             : 
      83   144423234 :     if (slew_rate_max <= 0) {
      84   121127001 :         return 1.0;
      85             :     }
      86             : 
      87             :     // Constrain slew rate used for calculation
      88    23296233 :     const float limited_raw_slew_rate = 0.5f*(fminf(_max_pos_slew_rate, 10.0f * slew_rate_max) + fminf(_max_neg_slew_rate, 10.0f * slew_rate_max));
      89             : 
      90             :     // Store a series of positive slew rate exceedance events
      91    23296233 :     if (!_pos_event_stored && slew_rate > slew_rate_max) {
      92        5969 :         if (_pos_event_index >= SLEWLIMITER_N_EVENTS) {
      93        2809 :             _pos_event_index = 0;
      94             :         }
      95        5969 :         _pos_event_ms[_pos_event_index] = now_ms;
      96        5969 :         _pos_event_index++;
      97        5969 :         _pos_event_stored = true;
      98        5969 :         _neg_event_stored = false;
      99             :     }
     100             : 
     101             :     // Store a series of negative slew rate exceedance events
     102    23296233 :     if (!_neg_event_stored && -slew_rate > slew_rate_max) {
     103        6028 :         if (_neg_event_index >= SLEWLIMITER_N_EVENTS) {
     104        2829 :             _neg_event_index = 0;
     105             :         }
     106        6028 :         _neg_event_ms[_neg_event_index] = now_ms;
     107        6028 :         _neg_event_index++;
     108        6028 :         _neg_event_stored = true;
     109        6028 :         _pos_event_stored = false;
     110             :     }
     111             : 
     112             :     // Find the oldest event time
     113    23296233 :     uint32_t oldest_ms = now_ms;
     114    69888702 :     for (uint8_t index = 0; index < SLEWLIMITER_N_EVENTS; index++) {
     115    46592468 :         oldest_ms = MIN(oldest_ms, _pos_event_ms[index]);
     116    46592468 :         oldest_ms = MIN(oldest_ms, _neg_event_ms[index]);
     117             :     }
     118             : 
     119             :     // Apply a further reduction when the oldest exceedance event falls outside the window required for the
     120             :     // specified number of exceedance events. This prevents spikes due to control mode changed, etc causing
     121             :     // unwanted gain reduction and is only applied to the slew rate used for gain reduction
     122    23296234 :     float modifier_input = limited_raw_slew_rate;
     123    23296234 :     if (now_ms - oldest_ms > (SLEWLIMITER_N_EVENTS + 1) * WINDOW_MS) {
     124    23134673 :         const float oldest_time_from_window = 0.001f*(float)(now_ms - oldest_ms - (SLEWLIMITER_N_EVENTS + 1) * WINDOW_MS);
     125    23134673 :         modifier_input *= expf(-oldest_time_from_window / slew_rate_tau);
     126             :     }
     127             : 
     128    23296234 :     _modifier_slew_rate = (1.0f - attack_alpha) * _modifier_slew_rate + attack_alpha * modifier_input;
     129    23296234 :     _modifier_slew_rate = fminf(_modifier_slew_rate, modifier_input);
     130             : 
     131             :     // Calculate the gain adjustment
     132    23296234 :     float mod = 1.0f;
     133    23296234 :     if (_modifier_slew_rate > slew_rate_max) {
     134      183125 :         mod = slew_rate_max / (slew_rate_max + MODIFIER_GAIN * (_modifier_slew_rate - slew_rate_max));
     135             :     }
     136             : 
     137    23296234 :     return mod;
     138             : }

Generated by: LCOV version 1.14