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