LCOV - code coverage report
Current view: top level - src/Hardware - FilteredAnalog.hpp (source / functions) Hit Total Coverage
Test: 19d2efc7037c2e176feca44750a12594c76f466f Lines: 17 19 89.5 %
Date: 2019-11-24 14:50:27 Functions: 18 19 94.7 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : #pragma once
       2             : 
       3             : #include <Def/Def.hpp>
       4             : #include <Hardware/ExtendedInputOutput/ExtendedInputOutput.hpp>
       5             : #include <Helpers/EMA.hpp>
       6             : #include <Helpers/Hysteresis.hpp>
       7             : #include <Helpers/IncreaseBitDepth.hpp>
       8             : #include <Helpers/MinMaxFix.hpp>
       9             : #include <Settings/SettingsWrapper.hpp>
      10             : 
      11             : BEGIN_CS_NAMESPACE
      12             : 
      13             : /**
      14             :  * A class that reads and filters an analog input.
      15             :  *
      16             :  * A map function can be applied to the analog value (e.g. to compensate for
      17             :  * logarithmic taper potentiometers or to calibrate the range). The analog input
      18             :  * value is filtered using an exponential moving average filter. The default
      19             :  * settings for this filter can be changed in Settings.hpp.  
      20             :  * After filtering, hysteresis is applied to prevent flipping back and forth 
      21             :  * between two values when the input is not changing.
      22             :  * 
      23             :  * @tparam  Precision
      24             :  *          The number of bits of precision the output should have.
      25             :  * @tparam  FilterShiftFactor
      26             :  *          The number of bits used for the EMA filter.
      27             :  *          The pole location is
      28             :  *          @f$ 1 - \left(\frac{1}{2}\right)^{\mathrm{FilterShiftFactor}} @f$.  
      29             :  *          A lower shift factor means less filtering (@f$0@f$ is no filtering),
      30             :  *          and a higher shift factor means more filtering (and more latency).
      31             :  * @tparam  FilterType
      32             :  *          The type to use for the intermediate types of the filter.  
      33             :  *          Should be at least 
      34             :  *          @f$ 10 + \mathrm{Upsample} + \mathrm{FilterShiftFactor} @f$
      35             :  *          bits (@f$10@f$ is the number of bits of the ADC).
      36             :  * @tparam  Upsample
      37             :  *          The number of bits to upsample the analog reading by.
      38             :  * 
      39             :  * @ingroup HardwareUtils
      40             :  */
      41             : template <uint8_t Precision = 10,
      42             :           uint8_t FilterShiftFactor = ANALOG_FILTER_SHIFT_FACTOR,
      43             :           class FilterType = ANALOG_FILTER_TYPE,
      44             :           uint8_t Upsample =
      45             :               min(sizeof(FilterType) * CHAR_BIT - ADC_BITS - FilterShiftFactor,
      46             :                   sizeof(analog_t) * CHAR_BIT - ADC_BITS)>
      47             : class FilteredAnalog {
      48             :   public:
      49             :     /**
      50             :      * @brief   Construct a new FilteredAnalog object.
      51             :      *
      52             :      * @param   analogPin
      53             :      *          The analog pin to read from.
      54             :      */
      55          12 :     FilteredAnalog(pin_t analogPin) : analogPin(analogPin) {}
      56             : 
      57             :     /**
      58             :      * @brief   Specify a mapping function that is applied to the raw
      59             :      *          analog value before filtering.
      60             :      *
      61             :      * @param   fn
      62             :      *          A function pointer to the mapping function. This function
      63             :      *          should take the filtered value (of ADC_BITS + Upsample bits 
      64             :      *          wide) as a parameter, and should return a value of ADC_BITS + 
      65             :      *          Upsample bits wide.
      66             :      * 
      67             :      * @note    Applying the mapping function before filtering could result in
      68             :      *          the noise being amplified to such an extent that filtering it
      69             :      *          afterwards would be ineffective.  
      70             :      *          Applying it after hysteresis would result in a lower resolution.  
      71             :      *          That's why the mapping function is applied after filtering and
      72             :      *          before hysteresis.
      73             :      */
      74           4 :     void map(MappingFunction fn) { mapFn = fn; }
      75             : 
      76             :     /**
      77             :      * @brief   Invert the analog value. For example, if the precision is 10 
      78             :      *          bits, when the analog input measures 1023, the output will be 0,
      79             :      *          and when the analog input measures 0, the output will be 1023.
      80             :      * 
      81             :      * @note    This overrides the mapping function set by the `map` method.
      82             :      */
      83           2 :     void invert() {
      84           2 :         constexpr analog_t maxval = (1 << (ADC_BITS + Upsample)) - 1;
      85          16 :         map([](analog_t val) -> analog_t { return maxval - val; });
      86           2 :     }
      87             : 
      88             :     /**
      89             :      * @brief   Read the analog input value, apply the mapping function, and
      90             :      *          update the average.
      91             :      *
      92             :      * @retval  true
      93             :      *          The value changed since last time it was updated.
      94             :      * @retval  false
      95             :      *          The value is still the same.
      96             :      */
      97          18 :     bool update() {
      98          18 :         analog_t input = getRawValue();  // read the raw analog input value
      99          18 :         input = filter.filter(input);    // apply a low-pass EMA filter
     100          18 :         if (mapFn)                       // If a mapping function is specified,
     101          12 :             input = mapFn(input);        // apply it
     102          18 :         return hysteresis.update(input); // apply hysteresis, and return true if
     103             :         // the value changed since last time
     104             :     }
     105             : 
     106             :     /**
     107             :      * @brief   Get the filtered value of the analog input with the mapping 
     108             :      *          function applied.
     109             :      *
     110             :      * @return  The filtered value of the analog input, as a number
     111             :      *          of `Precision` bits wide.
     112             :      */
     113          18 :     analog_t getValue() const { return hysteresis.getValue(); }
     114             : 
     115             :     /**
     116             :      * @brief   Get the filtered value of the analog input with the mapping 
     117             :      *          function applied as a floating point number from 0.0 to 1.0.
     118             :      * 
     119             :      * @return  The filtered value of the analog input, as a number
     120             :      *          from 0.0 to 1.0.
     121             :      */
     122             :     float getFloatValue() const {
     123             :         return getValue() * (1.0f / (ldexpf(1.0f, Precision) - 1.0f));
     124             :     }
     125             : 
     126             :     /**
     127             :      * @brief   Read the raw value of the analog input any filtering or mapping
     128             :      *          applied, but with its bit depth increased by @c Upsample.
     129             :      */
     130          18 :     analog_t getRawValue() const {
     131          18 :         return increaseBitDepth<ADC_BITS + Upsample, ADC_BITS, analog_t,
     132          18 :                                 analog_t>(ExtIO::analogRead(analogPin));
     133             :     }
     134             : 
     135           0 :     static void setupADC() {
     136             : #if HAS_ANALOG_READ_RESOLUTION
     137             :         analogReadResolution(ADC_BITS);
     138             : #endif
     139           0 :     }
     140             : 
     141             :   private:
     142             :     const pin_t analogPin;
     143             : 
     144          12 :     MappingFunction mapFn = nullptr;
     145             : 
     146             :     static_assert(
     147             :         ADC_BITS + Upsample + FilterShiftFactor <=
     148             :             sizeof(FilterType) * CHAR_BIT,
     149             :         "Error: FilterType is not wide enough to hold the maximum value");
     150             :     static_assert(
     151             :         ADC_BITS + Upsample <= sizeof(analog_t) * CHAR_BIT,
     152             :         "Error: analog_t is not wide enough to hold the maximum value");
     153             :     static_assert(
     154             :         Precision <= ADC_BITS + Upsample,
     155             :         "Error: Precision is larger than the upsampled ADC precision");
     156             : 
     157             :     EMA<FilterShiftFactor, FilterType> filter;
     158             :     Hysteresis<ADC_BITS + Upsample - Precision, analog_t, analog_t> hysteresis;
     159             : };
     160             : 
     161             : END_CS_NAMESPACE

Generated by: LCOV version 1.14-5-g4ff2ed6