Control Surface  1.1.0
MIDI Control Surface library for Arduino
FilteredAnalog.hpp
Go to the documentation of this file.
1 #pragma once
2 
4 AH_DIAGNOSTIC_WERROR() // Enable errors on warnings
5 
6 #include <AH/Filters/EMA.hpp>
11 #include <AH/Math/MinMaxFix.hpp>
13 
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)>
55  public:
56  /**
57  * @brief Construct a new FilteredAnalog object.
58  *
59  * @param analogPin
60  * The analog pin to read from.
61  */
62  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  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  void invert() {
95  constexpr AnalogType maxval = (1UL << (ADC_BITS + Upsample)) - 1;
96  map([](AnalogType val) -> AnalogType { return maxval - val; });
97  }
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  bool update() {
109  AnalogType input = getRawValue(); // read the raw analog input value
110  input = filter.filter(input); // apply a low-pass EMA filter
111  if (mapFn) // If a mapping function is specified,
112  input = mapFn(input); // apply it
113  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  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  AnalogType getRawValue() const {
142  return increaseBitDepth<ADC_BITS + Upsample, ADC_BITS, AnalogType,
143  AnalogType>(ExtIO::analogRead(analogPin));
144  }
145 
146  static void setupADC() {
147 #if HAS_ANALOG_READ_RESOLUTION
148  analogReadResolution(ADC_BITS);
149 #endif
150  }
151 
152  private:
154 
155  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 
169  Hysteresis<ADC_BITS + Upsample - Precision, AnalogType, AnalogType>
171 };
172 
174 
AH::FilteredAnalog::filter
EMA< FilterShiftFactor, FilterType > filter
Definition: FilteredAnalog.hpp:160
Warnings.hpp
Hysteresis.hpp
AH::FilteredAnalog::map
void map(MappingFunction fn)
Specify a mapping function that is applied to the raw analog value before filtering.
Definition: FilteredAnalog.hpp:85
AH::increaseBitDepth
T_out increaseBitDepth(T_in in)
Increase the bit depth of the given value from Bits_in bits wide to Bits_out bits wide,...
Definition: IncreaseBitDepth.hpp:68
AH::FilteredAnalog::invert
void invert()
Invert the analog value.
Definition: FilteredAnalog.hpp:94
AH::analog_t
uint16_t analog_t
The type returned from analogRead and similar functions.
Definition: Hardware-Types.hpp:15
SettingsWrapper.hpp
ExtendedInputOutput.hpp
AH::min
constexpr auto min(const T &a, const U &b) -> decltype(b< a ? b :a)
Return the smaller of two numbers/objects.
Definition: MinMaxFix.hpp:15
IncreaseBitDepth.hpp
AH::FilteredAnalog::setupADC
static void setupADC()
Definition: FilteredAnalog.hpp:146
AH::FilteredAnalog< ContinuousCCSender ::precision()>::MappingFunction
analog_t(*)(analog_t) MappingFunction
A function pointer to a mapping function to map analog values.
Definition: FilteredAnalog.hpp:66
AH_DIAGNOSTIC_POP
#define AH_DIAGNOSTIC_POP()
Definition: Warnings.hpp:17
AH::FilteredAnalog
A class that reads and filters an analog input.
Definition: FilteredAnalog.hpp:54
AH::FilteredAnalog::update
bool update()
Read the analog input value, apply the mapping function, and update the average.
Definition: FilteredAnalog.hpp:108
EMA< FilterShiftFactor, FilterType >
Hysteresis
A class for applying hysteresis to a given input.
Definition: Hysteresis.hpp:36
AH::ADC_BITS
constexpr uint8_t ADC_BITS
The bit depth to use for the ADC (Analog to Digital Converter).
Definition: AH/Settings/Settings.hpp:53
AH::FilteredAnalog::getRawValue
AnalogType getRawValue() const
Read the raw value of the analog input any filtering or mapping applied, but with its bit depth incre...
Definition: FilteredAnalog.hpp:141
AH::ExtIO::analogRead
analog_t analogRead(pin_t pin)
An ExtIO version of the Arduino function.
Definition: ExtendedInputOutput.cpp:86
AH::pin_t
uint16_t pin_t
The type for Arduino pins (and ExtendedIOElement pins).
Definition: Hardware-Types.hpp:17
AH::FilteredAnalog::FilteredAnalog
FilteredAnalog(pin_t analogPin)
Construct a new FilteredAnalog object.
Definition: FilteredAnalog.hpp:62
AH::FilteredAnalog::getValue
AnalogType getValue() const
Get the filtered value of the analog input with the mapping function applied.
Definition: FilteredAnalog.hpp:124
AH::FilteredAnalog::analogPin
const pin_t analogPin
Definition: FilteredAnalog.hpp:153
AH::FilteredAnalog::hysteresis
Hysteresis< ADC_BITS+Upsample - Precision, AnalogType, AnalogType > hysteresis
Definition: FilteredAnalog.hpp:170
AH_DIAGNOSTIC_WERROR
#define AH_DIAGNOSTIC_WERROR()
Definition: Warnings.hpp:16
AH::FilteredAnalog::getFloatValue
float getFloatValue() const
Get the filtered value of the analog input with the mapping function applied as a floating point numb...
Definition: FilteredAnalog.hpp:133
BEGIN_AH_NAMESPACE
#define BEGIN_AH_NAMESPACE
Definition: AH/Settings/NamespaceSettings.hpp:9
AH::ANALOG_FILTER_TYPE
uint16_t ANALOG_FILTER_TYPE
The unsigned integer type to use for analog inputs during filtering.
Definition: AH/Settings/Settings.hpp:71
END_AH_NAMESPACE
#define END_AH_NAMESPACE
Definition: AH/Settings/NamespaceSettings.hpp:10
MinMaxFix.hpp
AH::ANALOG_FILTER_SHIFT_FACTOR
constexpr uint8_t ANALOG_FILTER_SHIFT_FACTOR
The factor for the analog filter: Difference equation: where .
Definition: AH/Settings/Settings.hpp:64
Hardware-Types.hpp