RSeries astromech firmware
AnalogMonitor.h
Go to the documentation of this file.
1 /*
2  * AnalogMonitor.h
3  * Arduino library for eliminating noise in analogRead inputs without decreasing responsiveness
4  *
5  * Copyright (c) 2016 Damien Clarke
6  *
7  * Permission is hereby granted, free of charge, to any person obtaining a copy
8  * of this software and associated documentation files (the "Software"), to deal
9  * in the Software without restriction, including without limitation the rights
10  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11  * copies of the Software, and to permit persons to whom the Software is
12  * furnished to do so, subject to the following conditions:
13  *
14  * The above copyright notice and this permission notice shall be included in all
15  * copies or substantial portions of the Software.
16  *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23  * SOFTWARE.
24  */
25 
26 #ifndef AnalogMonitor_h
27 #define AnalogMonitor_h
28 
29 #include "core/AnimatedEvent.h"
30 
52 {
53 public:
59  {
60  }
61 
74  AnalogMonitor(byte pin, bool sleepEnable = true, float snapMultiplier = 0.01)
75  {
76  begin(pin, sleepEnable, snapMultiplier);
77  }
78 
88  void begin(byte pin, bool sleepEnable = true, float snapMultiplier = 0.01)
89  {
90  pinMode(pin, INPUT); // ensure button pin is an input
91  digitalWrite(pin, LOW); // ensure pullup is off on button pin
92 
93  fPin = pin;
94  fSleepEnable = sleepEnable;
95  setSnapMultiplier(snapMultiplier);
96  }
97 
101  inline int getValue()
102  {
103  return fResponsiveValue;
104  }
105 
109  inline int getRawValue()
110  {
111  return fRawValue;
112  }
113 
117  inline bool hasChanged()
118  {
119  return fResponsiveValueHasChanged;
120  }
121 
125  inline bool isSleeping()
126  {
127  return fSleeping;
128  }
129 
130  virtual void animate()
131  {
132  if (fPin == 0xFF)
133  return;
134  fRawValue = analogRead(fPin);
135  fPrevResponsiveValue = fResponsiveValue;
136  fResponsiveValue = getResponsiveValue(fRawValue);
137  fResponsiveValueHasChanged = (fResponsiveValue != fPrevResponsiveValue);
138  }
139 
140  void setSnapMultiplier(float newMultiplier)
141  {
142  fSnapMultiplier =
143  (newMultiplier > 1.0) ? 1.0 :
144  (newMultiplier < 0.0) ? 0.0 : newMultiplier;
145  }
146 
147  inline void enableSleep()
148  {
149  fSleepEnable = true;
150  }
151 
152  inline void disableSleep()
153  {
154  fSleepEnable = false;
155  }
156 
157  inline void enableEdgeSnap()
158  {
159  fEdgeSnapEnable = true;
160  }
161 
165  inline void disableEdgeSnap()
166  {
167  fEdgeSnapEnable = false;
168  }
169 
170  inline void setActivityThreshold(float newThreshold)
171  {
172  fActivityThreshold = newThreshold;
173  }
174 
178  inline void setAnalogResolution(int resolution)
179  {
180  // if your ADC is something other than 10bit (1024), set that here
181  fAnalogResolution = resolution;
182  }
183 
184 private:
185  byte fPin = 0xFF;
186 #if defined(ESP32)
187  // Default 12 bit resolution
188  int fAnalogResolution = 4096;
189 #else
190  // Default 10 bit resolution
191  int fAnalogResolution = 1024;
192 #endif
193  float fSnapMultiplier;
194  bool fSleepEnable;
195  float fActivityThreshold = 4.0;
196  bool fEdgeSnapEnable = true;
197 
198  float fSmoothValue;
199  unsigned long fLastActivityMS;
200  float fErrorEMA = 0.0;
201  bool fSleeping = false;
202 
203  int fRawValue;
204  int fResponsiveValue;
205  int fPrevResponsiveValue;
206  bool fResponsiveValueHasChanged;
207 
208  int getResponsiveValue(int newValue)
209  {
210  // if sleep and edge snap are enabled and the new value is very close to an edge, drag it a little closer to the edges
211  // This'll make it easier to pull the output values right to the extremes without sleeping,
212  // and it'll make movements right near the edge appear larger, making it easier to wake up
213  if (fSleepEnable && fEdgeSnapEnable)
214  {
215  if (newValue < fActivityThreshold)
216  {
217  newValue = (newValue * 2) - fActivityThreshold;
218  }
219  else if (newValue > fAnalogResolution - fActivityThreshold)
220  {
221  newValue = (newValue * 2) - fAnalogResolution + fActivityThreshold;
222  }
223  }
224 
225  // get difference between new input value and current smooth value
226  unsigned int diff = abs(newValue - fSmoothValue);
227 
228  // measure the difference between the new value and current value
229  // and use another exponential moving average to work out what
230  // the current margin of error is
231  fErrorEMA += ((newValue - fSmoothValue) - fErrorEMA) * 0.4;
232 
233  // if sleep has been enabled, sleep when the amount of error is below the activity threshold
234  if (fSleepEnable)
235  {
236  // recalculate sleeping status
237  fSleeping = abs(fErrorEMA) < fActivityThreshold;
238  }
239 
240  // if we're allowed to sleep, and we're sleeping
241  // then don't update responsiveValue this loop
242  // just output the existing responsiveValue
243  if (fSleepEnable && fSleeping)
244  {
245  return (int)fSmoothValue;
246  }
247  // use a 'snap curve' function, where we pass in the diff (x) and get back a number from 0-1.
248  // We want small values of x to result in an output close to zero, so when the smooth value is close to the input value
249  // it'll smooth out noise aggressively by responding slowly to sudden changes.
250  // We want a small increase in x to result in a much higher output value, so medium and large movements are snappy and responsive,
251  // and aren't made sluggish by unnecessarily filtering out noise. A hyperbola (f(x) = 1/x) curve is used.
252  // First x has an offset of 1 applied, so x = 0 now results in a value of 1 from the hyperbola function.
253  // High values of x tend toward 0, but we want an output that begins at 0 and tends toward 1, so 1-y flips this up the right way.
254  // Finally the result is multiplied by 2 and capped at a maximum of one, which means that at a certain point all larger movements are maximally snappy
255 
256  // then multiply the input by SNAP_MULTIPLER so input values fit the snap curve better.
257  float snap = snapCurve(diff * fSnapMultiplier);
258 
259  // when sleep is enabled, the emphasis is stopping on a responsiveValue quickly, and it's less about easing into position.
260  // If sleep is enabled, add a small amount to snap so it'll tend to snap into a more accurate position before sleeping starts.
261  if (fSleepEnable)
262  {
263  snap *= 0.5 + 0.5;
264  }
265 
266  // calculate the exponential moving average based on the snap
267  fSmoothValue += (newValue - fSmoothValue) * snap;
268 
269  // ensure output is in bounds
270  if (fSmoothValue < 0.0)
271  {
272  fSmoothValue = 0.0;
273  }
274  else if (fSmoothValue > fAnalogResolution - 1)
275  {
276  fSmoothValue = fAnalogResolution - 1;
277  }
278 
279  // expected output is an integer
280  return (int)fSmoothValue;
281  }
282 
283  static inline float snapCurve(float x)
284  {
285  float y = 1.0 / (x + 1.0);
286  y = (1.0 - y) * 2.0;
287  return (y > 1.0) ? 1.0: y;
288  }
289 };
290 
291 #endif
AnalogMonitor::begin
void begin(byte pin, bool sleepEnable=true, float snapMultiplier=0.01)
Start reading data from analog port.
Definition: AnalogMonitor.h:88
AnalogMonitor::enableSleep
void enableSleep()
Definition: AnalogMonitor.h:147
AnimatedEvent
Base class for all animated devices. AnimatedEvent::animate() is called for each device once through ...
Definition: AnimatedEvent.h:18
AnimatedEvent.h
AnalogMonitor::setActivityThreshold
void setActivityThreshold(float newThreshold)
Definition: AnalogMonitor.h:170
AnalogMonitor::hasChanged
bool hasChanged()
Definition: AnalogMonitor.h:117
AnalogMonitor::getValue
int getValue()
Definition: AnalogMonitor.h:101
AnalogMonitor::AnalogMonitor
AnalogMonitor(byte pin, bool sleepEnable=true, float snapMultiplier=0.01)
Constructor with explicit call to begin()
Definition: AnalogMonitor.h:74
AnalogMonitor::disableEdgeSnap
void disableEdgeSnap()
Edge snap ensures that values at the edges of the spectrum (0 and 1023) can be easily reached when sl...
Definition: AnalogMonitor.h:165
AnalogMonitor::getRawValue
int getRawValue()
Definition: AnalogMonitor.h:109
AnalogMonitor::disableSleep
void disableSleep()
Definition: AnalogMonitor.h:152
AnalogMonitor::isSleeping
bool isSleeping()
Definition: AnalogMonitor.h:125
AnalogMonitor::setSnapMultiplier
void setSnapMultiplier(float newMultiplier)
Definition: AnalogMonitor.h:140
AnalogMonitor::AnalogMonitor
AnalogMonitor()
Default Constructor.
Definition: AnalogMonitor.h:58
AnalogMonitor::enableEdgeSnap
void enableEdgeSnap()
Definition: AnalogMonitor.h:157
AnalogMonitor::setAnalogResolution
void setAnalogResolution(int resolution)
The amount of movement that must take place to register as activity and start moving the output value...
Definition: AnalogMonitor.h:178
AnalogMonitor
Used for eliminating noise in analogRead inputs without decreasing responsiveness....
Definition: AnalogMonitor.h:51
AnalogMonitor::animate
virtual void animate()
Subclasses must implement this function to run through a single frame of animation/activity.
Definition: AnalogMonitor.h:130