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