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()