LCOV - code coverage report
Current view: top level - src/AH/Hardware - FilteredAnalog.hpp (source / functions) Hit Total Coverage
Test: 90a1b9beff85a60dc6ebcea034a947a845e56960 Lines: 17 19 89.5 %
Date: 2019-11-30 15:53:32 Functions: 18 19 94.7 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.14-5-g4ff2ed6