LCOV - code coverage report
Current view: top level - src/AH/Filters - EMA.hpp (source / functions) Hit Total Coverage
Test: ffed98f648fe78e7aa7bdd228474317d40dadbec Lines: 18 18 100.0 %
Date: 2022-05-28 15:22:59 Functions: 16 16 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* ✔ */
       2             : 
       3             : #pragma once
       4             : 
       5             : #include <AH/Settings/Warnings.hpp>
       6             : AH_DIAGNOSTIC_WERROR() // Enable errors on warnings
       7             : 
       8             : #include <stdint.h>
       9             : #include <AH/STL/limits>
      10             : #include <AH/STL/type_traits>
      11             : 
      12             : /**
      13             :  * @brief   Exponential moving average filter.
      14             :  * 
      15             :  * Fast integer EMA implementation where the weight factor is a power of two.
      16             :  * 
      17             :  * Difference equation: @f$ y[n] = \alpha·x[n]+(1-\alpha)·y[n-1] @f$
      18             :  * where @f$ \alpha = \left(\frac{1}{2}\right)^{K} @f$, @f$ x @f$ is the
      19             :  * input sequence, and @f$ y @f$ is the output sequence.
      20             :  *
      21             :  * [An in-depth explanation of the EMA filter](https://tttapa.github.io/Pages/Mathematics/Systems-and-Control-Theory/Digital-filters/Exponential%20Moving%20Average/)
      22             :  *
      23             :  * @tparam  K
      24             :  *          The amount of bits to shift by. This determines the location
      25             :  *          of the pole in the EMA transfer function, and therefore the
      26             :  *          cut-off frequency.  
      27             :  *          The higher this number, the more filtering takes place.  
      28             :  *          The pole location is @f$ 1 - 2^{-K} @f$.
      29             :  * @tparam  input_t
      30             :  *          The integer type to use for the input and output of the filter. 
      31             :  *          Can be signed or unsigned.
      32             :  * @tparam  state_t
      33             :  *          The unsigned integer type to use for the internal state of the
      34             :  *          filter. A fixed-point representation with @f$ K @f$ fractional
      35             :  *          bits is used, so this type should be at least @f$ M + K @f$ bits
      36             :  *          wide, where @f$ M @f$ is the maximum number of bits of the input.
      37             :  *
      38             :  * Some examples of different combinations of template parameters:
      39             :  * 
      40             :  * 1. Filtering the result of `analogRead`: analogRead returns an integer
      41             :  *    between 0 and 1023, which can be represented using 10 bits, so 
      42             :  *    @f$ M = 10 @f$. If `input_t` and `output_t` are both `uint16_t`,
      43             :  *    the maximum shift factor `K` is @f$ 16 - M = 6 @f$. If `state_t`
      44             :  *    is increased to `uint32_t`, the maximum shift factor `K` is 
      45             :  *    @f$ 32 - M = 22 @f$.
      46             :  * 2. Filtering a signed integer between -32768 and 32767: this can be 
      47             :  *    represented using a 16-bit signed integer, so `input_t` is `int16_t`,
      48             :  *    and @f$ M = 16 @f$. (2¹⁵ = 32768)
      49             :  *    Let's say the shift factor `K` is 1, then the minimum width of 
      50             :  *    `state_t` should be @f$ M + K = 17 @f$ bits, so `uint32_t` would be 
      51             :  *    a sensible choice.
      52             :  * 
      53             :  * @ingroup    AH_Filters
      54             :  */
      55             : template <uint8_t K,
      56             :           class input_t = uint_fast16_t,
      57             :           class state_t = typename std::make_unsigned<input_t>::type>
      58             : class EMA {
      59             :   public:
      60             :     /// Constructor: initialize filter to zero or optional given value.
      61          25 :     EMA(input_t initial = input_t(0))
      62          25 :       : state(zero + (state_t(initial) << K) - initial) {}
      63             : 
      64             :     /**
      65             :      * @brief   Reset the filter to the given value.
      66             :      * 
      67             :      * @param   value 
      68             :      *          The value to reset the filter state to.
      69             :      */
      70           9 :     void reset(input_t value = input_t(0)) {
      71           9 :         state = zero + (state_t(value) << K) - value;
      72           9 :     }
      73             : 
      74             :     /**
      75             :      * @brief   Filter the input: Given @f$ x[n] @f$, calculate @f$ y[n] @f$.
      76             :      *
      77             :      * @param   input
      78             :      *          The new raw input value.
      79             :      * @return  The new filtered output value.
      80             :      */
      81        2063 :     input_t filter(input_t input) {
      82        2063 :       state         += state_t(input);
      83        2063 :       state_t output = (state + half) >> K;
      84        2063 :       output        -= zero >> K;
      85        2063 :       state         -= output;
      86        2063 :       return input_t(output);
      87             :     }
      88             : 
      89             :     /// @copydoc    EMA::filter(input_t)
      90        2021 :     input_t operator()(input_t input) {
      91        2021 :         return filter(input);
      92             :     }
      93             : 
      94             :     constexpr static state_t 
      95             :       max_state  = std::numeric_limits<state_t>::max(),
      96             :       half_state = max_state / 2 + 1,
      97             :       zero       = std::is_unsigned<input_t>::value ? state_t(0) : half_state,
      98             :       half       = K > 0 ? state_t(1) << (K - 1) : state_t(0);
      99             :   
     100             :     static_assert(std::is_unsigned<state_t>::value, 
     101             :                   "state type should be unsigned");
     102             : 
     103             :     static_assert(max_state >= std::numeric_limits<input_t>::max(),
     104             :                   "state type cannot be narrower than input type");               
     105             : 
     106             :     /// Verify the input range to make sure it's compatible with the shift
     107             :     /// factor and the width of the state type.
     108             :     ///
     109             :     /// Examples:
     110             :     /// ~~~cpp
     111             :     /// EMA<5, int_fast16_t, uint_fast16_t> filter;
     112             :     /// static_assert(filter.supports_range(-1024, 1023),
     113             :     ///               "use a wider state or input type, or a smaller shift factor");
     114             :     /// ~~~
     115             :     /// ~~~cpp
     116             :     /// EMA<5, uint_fast16_t, uint_fast16_t> filter;
     117             :     /// static_assert(filter.supports_range(0u, 2047u),
     118             :     ///               "use a wider state or input type, or a smaller shift factor");
     119             :     /// ~~~
     120             :     template <class T>
     121             :     constexpr static bool supports_range(T min, T max) {
     122             :       using sstate_t = typename std::make_signed<state_t>::type;
     123             :       return min <= max &&
     124             :              min >= std::numeric_limits<input_t>::min() &&
     125             :              max <= std::numeric_limits<input_t>::max() &&
     126             :              (std::is_unsigned<input_t>::value
     127             :                ? state_t(max) <= (max_state >> K)
     128             :                : min >= -sstate_t(max_state >> (K + 1)) - 1 &&  
     129             :                  max <= sstate_t(max_state >> (K + 1)));
     130             :     }
     131             : 
     132             :   private:
     133             :     state_t state;
     134             : };
     135             : 
     136             : // -------------------------------------------------------------------------- //
     137             : 
     138             : /**
     139             :  * @brief   A class for single-pole infinite impulse response filters
     140             :  *          or exponential moving average filters.
     141             :  * 
     142             :  * This version uses floating point maths.
     143             :  * 
     144             :  * Difference equation: @f$ y[n] = \alpha·x[n]+(1-\alpha)·y[n-1] @f$
     145             :  * @f$ x @f$ is the input sequence, and @f$ y @f$ is the output sequence.
     146             :  *
     147             :  * [An in-depth explanation of the EMA filter]
     148             :  * (https://tttapa.github.io/Pages/Mathematics/Systems-and-Control-Theory/Digital-filters/Exponential%20Moving%20Average/)
     149             :  * 
     150             :  * @ingroup    AH_Filters
     151             :  */
     152             : class EMA_f {
     153             :   public:
     154             :     /**
     155             :      * @brief   Create an exponential moving average filter with a pole at the
     156             :      *          given location.
     157             :      * 
     158             :      * @param   pole
     159             :      *          The pole of the filter (@f$1-\alpha@f$).  
     160             :      *          Should be a value in the range 
     161             :      *          @f$ \left[0,1\right) @f$.  
     162             :      *          Zero means no filtering, and closer to one means more filtering.
     163             :      */
     164           1 :     EMA_f(float pole) : alpha(1 - pole) {}
     165             : 
     166             :     /**
     167             :      * @brief   Filter the input: Given @f$ x[n] @f$, calculate @f$ y[n] @f$.
     168             :      *
     169             :      * @param   value
     170             :      *          The new raw input value.
     171             :      * @return  The new filtered output value.
     172             :      */
     173          12 :     float filter(float value) {
     174          12 :         filtered += (value - filtered) * alpha;
     175          12 :         return filtered;
     176             :     }
     177             : 
     178             :     /// @copydoc    filter(float)
     179          12 :     float operator()(float value) { return filter(value); }
     180             : 
     181             :   private:
     182             :     float alpha;
     183             :     float filtered = 0;
     184             : };
     185             : 
     186             : AH_DIAGNOSTIC_POP()

Generated by: LCOV version 1.15