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