Line data Source code
1 : #pragma once
2 :
3 : #include <AH/Filters/EMA.hpp>
4 : #include <AH/Filters/Hysteresis.hpp>
5 : #include <AH/Hardware/ExtendedInputOutput/ExtendedInputOutput.hpp>
6 : #include <AH/Hardware/Hardware-Types.hpp>
7 : #include <AH/Math/IncreaseBitDepth.hpp>
8 : #include <AH/Math/MinMaxFix.hpp>
9 : #include <AH/STL/type_traits> // std::enable_if, std::is_constructible
10 : #include <AH/STL/utility> // std::forward
11 : #include <AH/Settings/SettingsWrapper.hpp>
12 :
13 : BEGIN_AH_NAMESPACE
14 :
15 : /**
16 : * @brief Helper to determine how many of the remaining bits of the filter
17 : * data types can be used to achieve higher precision.
18 : */
19 : template <uint8_t FilterShiftFactor, class FilterType, class AnalogType>
20 : struct MaximumFilteredAnalogIncRes {
21 : constexpr static uint8_t value =
22 : min(sizeof(FilterType) * CHAR_BIT - ADC_BITS - FilterShiftFactor,
23 : sizeof(AnalogType) * CHAR_BIT - ADC_BITS);
24 : };
25 :
26 : /**
27 : * @brief FilteredAnalog base class with generic MappingFunction.
28 : *
29 : * @see FilteredAnalog
30 : */
31 : template <class MappingFunction, uint8_t Precision = 10,
32 : uint8_t FilterShiftFactor = ANALOG_FILTER_SHIFT_FACTOR,
33 : class FilterType = ANALOG_FILTER_TYPE, class AnalogType = analog_t,
34 : uint8_t IncRes = MaximumFilteredAnalogIncRes<
35 : FilterShiftFactor, FilterType, AnalogType>::value>
36 : class GenericFilteredAnalog {
37 : public:
38 : /**
39 : * @brief Construct a new GenericFilteredAnalog object.
40 : *
41 : * @param analogPin
42 : * The analog pin to read from.
43 : * @param mapFn
44 : * The mapping function
45 : * @param initial
46 : * The initial value of the filter.
47 : */
48 9 : GenericFilteredAnalog(pin_t analogPin, MappingFunction mapFn,
49 : AnalogType initial = 0)
50 9 : : analogPin(analogPin), mapFn(std::forward<MappingFunction>(mapFn)),
51 9 : filter(increaseBitDepth<ADC_BITS + IncRes, Precision, AnalogType,
52 9 : AnalogType>(initial)) {}
53 :
54 : /**
55 : * @brief Reset the filter to the given value.
56 : *
57 : * @param value
58 : * The value to reset the filter state to.
59 : *
60 : * @todo Should the filter be initialized to the first value that is read
61 : * instead of to zero? This would require adding a `begin` method.
62 : */
63 1 : void reset(AnalogType value = 0) {
64 : AnalogType widevalue = increaseBitDepth<ADC_BITS + IncRes, Precision,
65 1 : AnalogType, AnalogType>(value);
66 1 : filter.reset(widevalue);
67 1 : hysteresis.setValue(widevalue);
68 1 : }
69 :
70 : /**
71 : * @brief Reset the filtered value to the value that's currently being
72 : * measured at the analog input.
73 : *
74 : * This is useful to avoid transient effects upon initialization.
75 : */
76 1 : void resetToCurrentValue() {
77 1 : AnalogType widevalue = getRawValue();
78 1 : filter.reset(widevalue);
79 1 : hysteresis.setValue(widevalue);
80 1 : }
81 :
82 : /**
83 : * @brief Specify a mapping function/functor that is applied to the analog
84 : * value after filtering and before applying hysteresis.
85 : *
86 : * @param fn
87 : * This functor should have a call operator that takes the filtered
88 : * value (of ADC_BITS + IncRes bits wide) as a parameter,
89 : * and returns a value of ADC_BITS + IncRes bits wide.
90 : *
91 : * @note Applying the mapping function before filtering could result in
92 : * the noise being amplified to such an extent that filtering it
93 : * afterwards would be ineffective.
94 : * Applying it after hysteresis would result in a lower resolution.
95 : * That's why the mapping function is applied after filtering and
96 : * before hysteresis.
97 : */
98 3 : void map(MappingFunction fn) { mapFn = std::forward<MappingFunction>(fn); }
99 :
100 : /**
101 : * @brief Get a reference to the mapping function.
102 : */
103 : MappingFunction &getMappingFunction() { return mapFn; }
104 : /**
105 : * @brief Get a reference to the mapping function.
106 : */
107 : const MappingFunction &getMappingFunction() const { return mapFn; }
108 :
109 : /**
110 : * @brief Read the analog input value, apply the mapping function, and
111 : * update the average.
112 : *
113 : * @retval true
114 : * The value changed since last time it was updated.
115 : * @retval false
116 : * The value is still the same.
117 : */
118 24 : bool update() {
119 24 : AnalogType input = getRawValue(); // read the raw analog input value
120 24 : input = filter.filter(input); // apply a low-pass EMA filter
121 24 : input = mapFnHelper(input); // apply the mapping function
122 24 : return hysteresis.update(input); // apply hysteresis, and return true
123 : // if the value changed since last time
124 : }
125 :
126 : /**
127 : * @brief Get the filtered value of the analog input (with the mapping
128 : * function applied).
129 : *
130 : * @note This function just returns the value from the last call to
131 : * @ref update, it doesn't read the analog input again.
132 : *
133 : * @return The filtered value of the analog input, as a number
134 : * of `Precision` bits wide.
135 : */
136 47 : AnalogType getValue() const { return hysteresis.getValue(); }
137 :
138 : /**
139 : * @brief Get the filtered value of the analog input with the mapping
140 : * function applied as a floating point number from 0.0 to 1.0.
141 : *
142 : * @return The filtered value of the analog input, as a number
143 : * from 0.0 to 1.0.
144 : */
145 21 : float getFloatValue() const {
146 21 : return getValue() * (1.0f / (ldexpf(1.0f, Precision) - 1.0f));
147 : }
148 :
149 : /**
150 : * @brief Read the raw value of the analog input without any filtering or
151 : * mapping applied, but with its bit depth increased by @c IncRes.
152 : */
153 25 : AnalogType getRawValue() const {
154 25 : AnalogType value = ExtIO::analogRead(analogPin);
155 : #ifdef ESP8266
156 : if (value > 1023)
157 : value = 1023;
158 : #endif
159 25 : return increaseBitDepth<ADC_BITS + IncRes, ADC_BITS, AnalogType>(value);
160 : }
161 :
162 : /**
163 : * @brief Get the maximum value that can be returned from @ref getRawValue.
164 : */
165 : constexpr static AnalogType getMaxRawValue() {
166 : return (1ul << (ADC_BITS + IncRes)) - 1ul;
167 : }
168 :
169 : /**
170 : * @brief Select the configured ADC resolution. By default, it is set to
171 : * the maximum resolution supported by the hardware.
172 : *
173 : * @see @ref ADC_BITS
174 : * @see @ref ADCConfig.hpp
175 : */
176 7 : static void setupADC() {
177 : #if HAS_ANALOG_READ_RESOLUTION
178 7 : analogReadResolution(ADC_BITS);
179 : #endif
180 7 : }
181 :
182 : private:
183 : /// Helper function that applies the mapping function if it's enabled.
184 : /// This function is only enabled if MappingFunction is explicitly
185 : /// convertible to bool.
186 : template <typename M = MappingFunction>
187 : typename std::enable_if<std::is_constructible<bool, M>::value,
188 : AnalogType>::type
189 23 : mapFnHelper(AnalogType input) {
190 23 : return bool(mapFn) ? mapFn(input) : input;
191 : }
192 :
193 : /// Helper function that applies the mapping function without checking if
194 : /// it's enabled.
195 : /// This function is only enabled if MappingFunction is not convertible to
196 : /// bool.
197 : template <typename M = MappingFunction>
198 : typename std::enable_if<!std::is_constructible<bool, M>::value,
199 : AnalogType>::type
200 1 : mapFnHelper(AnalogType input) {
201 1 : return mapFn(input);
202 : }
203 :
204 : private:
205 : pin_t analogPin;
206 : MappingFunction mapFn;
207 :
208 : using EMA_t = EMA<FilterShiftFactor, AnalogType, FilterType>;
209 :
210 : static_assert(
211 : ADC_BITS + IncRes + FilterShiftFactor <= sizeof(FilterType) * CHAR_BIT,
212 : "Error: FilterType is not wide enough to hold the maximum value");
213 : static_assert(
214 : ADC_BITS + IncRes <= sizeof(AnalogType) * CHAR_BIT,
215 : "Error: AnalogType is not wide enough to hold the maximum value");
216 : static_assert(
217 : Precision <= ADC_BITS + IncRes,
218 : "Error: Precision is larger than the increased ADC precision");
219 : static_assert(EMA_t::supports_range(AnalogType(0), getMaxRawValue()),
220 : "Error: EMA filter type doesn't support full ADC range");
221 :
222 : EMA_t filter;
223 : Hysteresis<ADC_BITS + IncRes - Precision, AnalogType, AnalogType>
224 : hysteresis;
225 : };
226 :
227 : /**
228 : * @brief A class that reads and filters an analog input.
229 : *
230 : * A map function can be applied to the analog value (e.g. to compensate for
231 : * logarithmic taper potentiometers or to calibrate the range). The analog input
232 : * value is filtered using an exponential moving average filter. The default
233 : * settings for this filter can be changed in Settings.hpp.
234 : * After filtering, hysteresis is applied to prevent flipping back and forth
235 : * between two values when the input is not changing.
236 : *
237 : * @tparam Precision
238 : * The number of bits of precision the output should have.
239 : * @tparam FilterShiftFactor
240 : * The number of bits used for the EMA filter.
241 : * The pole location is
242 : * @f$ 1 - \left(\frac{1}{2}\right)^{\text{FilterShiftFactor}} @f$.
243 : * A lower shift factor means less filtering (@f$0@f$ is no filtering),
244 : * and a higher shift factor means more filtering (and more latency).
245 : * @tparam FilterType
246 : * The type to use for the intermediate types of the filter.
247 : * Should be at least
248 : * @f$ \text{ADC_BITS} + \text{IncRes} +
249 : * \text{FilterShiftFactor} @f$ bits wide.
250 : * @tparam AnalogType
251 : * The type to use for the analog values.
252 : * Should be at least @f$ \text{ADC_BITS} + \text{IncRes} @f$
253 : * bits wide.
254 : * @tparam IncRes
255 : * The number of bits to increase the resolution of the analog reading
256 : * by.
257 : *
258 : * @ingroup AH_HardwareUtils
259 : */
260 : template <uint8_t Precision = 10,
261 : uint8_t FilterShiftFactor = ANALOG_FILTER_SHIFT_FACTOR,
262 : class FilterType = ANALOG_FILTER_TYPE, class AnalogType = analog_t,
263 : uint8_t IncRes = MaximumFilteredAnalogIncRes<
264 : FilterShiftFactor, FilterType, AnalogType>::value>
265 : class FilteredAnalog
266 : : public GenericFilteredAnalog<AnalogType (*)(AnalogType), Precision,
267 : FilterShiftFactor, FilterType, AnalogType,
268 : IncRes> {
269 : public:
270 : /**
271 : * @brief Construct a new FilteredAnalog object.
272 : *
273 : * @param analogPin
274 : * The analog pin to read from.
275 : * @param initial
276 : * The initial value of the filter.
277 : */
278 5 : FilteredAnalog(pin_t analogPin, AnalogType initial = 0)
279 : : GenericFilteredAnalog<AnalogType (*)(AnalogType), Precision,
280 : FilterShiftFactor, FilterType, AnalogType,
281 5 : IncRes>(analogPin, nullptr, initial) {}
282 :
283 : /**
284 : * @brief Construct a new FilteredAnalog object.
285 : *
286 : * **This constructor should not be used.**
287 : * It is just a way to easily create arrays of FilteredAnalog objects, and
288 : * initializing them later. Trying to update a default-constructed or
289 : * uninitialized FilteredAnalog object will result in a fatal runtime error.
290 : */
291 : FilteredAnalog() : FilteredAnalog(NO_PIN) {}
292 :
293 : /// A function pointer to a mapping function to map analog values.
294 : /// @see map()
295 : using MappingFunction = AnalogType (*)(AnalogType);
296 :
297 : /**
298 : * @brief Invert the analog value. For example, if the precision is 10
299 : * bits, when the analog input measures 1023, the output will be 0,
300 : * and when the analog input measures 0, the output will be 1023.
301 : *
302 : * @note This overrides the mapping function set by the `map` method.
303 : */
304 1 : void invert() {
305 1 : constexpr AnalogType maxval = FilteredAnalog::getMaxRawValue();
306 8 : this->map([](AnalogType val) -> AnalogType { return maxval - val; });
307 1 : }
308 : };
309 :
310 : END_AH_NAMESPACE
|