1
\$\begingroup\$

I'm hung up on how to smooth analog sensor data in a specific way. Math/code is not my expertise so thanks in advance.

My sketch needs to listen an AnalogPin and smooth/remove any outliers as the values change, though smoothly settle on the final AnalogPin value.

Ex. if the sensor is a Mic in an empty room and over 30-60 seconds the room fills with people, I'd need a MicValue that slowly grows to the new volume but omits any initial foot steps or major peaks.

Ex. if the AnalogRead pin is a potentiometer, rapidly rotating the knob for 5 seconds wouldn't have much effect, but where the knob ends up the value would slowly settle upon.

Any help to define what I am looking for, or help with code would be greatly appreciated. Thanks!

\$\endgroup\$

4 Answers 4

5
\$\begingroup\$

A simple smoothed estimator can be obtained simply by a linear blending of the newly sampled value and the existing smoothed value:

smoothed_value := (1 - alpha) * smoothed_value + alpha * new_value

For instance if alpha is 0.20 (20%), then we have:

smoothed_value = 0.8 * smoothed_value + 0.2 * new_value

In other words, take 80% of the existing value, and 20% of the new sample.

By doing some algebraic manipulation, we can show that it can also be calculated this way:

smoothed_value := smoothed_value + alpha * (new_value - smoothed_value)

In other words, update the smoothed value by adding to it just the alpha fraction of the step between it and the new value.

If you make everything integer, and use a power of two for alpha or a multiple of a power of two, you can do this very efficiently on even unsophisticated processors that don't support multiplication or division. For instance, suppose we want to make alpha 3/16. In C, we can do the multiply by 3 and divide by 16 with shifts and adds:

delta = new_value - smoothed_value
smoothed_value += ((delta << 1) + delta) >> 4;

This type of smoothing exhibits exponential decay. It is similar to processes in nature such as, oh, the way an RC circuit charges or discharges to track changes in the applied voltage.

The alpha parameter is in the range [0, 1]. If it is zero, the new values are ignored; there is no convergence. If it is 1, then smoothed_value isn't smoothed at all; it follows new_value instantly. Low values close to zero provide slow convergence.

This algorithm, or something very similar, is used in the TCP protocol to establish a basis for the retransmission time-out based on measurements of the connection round-trip time.

\$\endgroup\$
0
2
\$\begingroup\$

Call this at some, rather slow, interval:

int ReadValue() {
  static int value = 0;
  int inputValue = ReadAnalogPin();

  if (inputValue > value) value++;
  if (inputValue < value) value--;

  return value;
}
\$\endgroup\$
0
\$\begingroup\$

When sampling noisy systems, it is normal practice to take multiple samples and average them out. You could do something similar here to avoid minor peaks in signal from significantly effecting your measurement.

On top of that, you could keep a history of measurement 'milestones' with, say, a 1 second period. If this measurement history had a finite size (10 measurements, perhaps) then you could push in your latest measurement milestone each second, and then take an average of your measurement history at the same time. In this way, when the average volume in the room grows consistently louder, the averaged value of your measurement history will grow consistently larger as well. The opposite would be true if the room got consistently quieter.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ What you describe is often implemented as a rolling average filter. It can be handy to make the number of values in the filter history array be an even power of two so that the calculation of the average value is simply a binary shift operation. There is a clever scheme that can be used with a rolling average filter where you keep track of the sum of the history elements. When a new value arrives you subtract the oldest value from the sum value then add the new value to the sum and place the new value into the history array replacing the oldest value. \$\endgroup\$ Commented Dec 29, 2014 at 14:01
  • \$\begingroup\$ Keeping the sum as described above eliminates the overhead of having to add all 8, 16 or 32 elements of the history array together every time you need the new running average value. \$\endgroup\$ Commented Dec 29, 2014 at 14:03
0
\$\begingroup\$

Very helpful, thanks all. Here's a test for anyone should they need it. Rolling the pot will slowly affect an LED's brightness:

int sensorPin = A0;  //pot dial to test
int led = 9;

int brightness = 0;
int smooth_return = 0;
int smoothed = 0;
int new_value;

void setup() {
  pinMode(led, OUTPUT);  
  pinMode(sensorPin, INPUT);
  Serial.begin(9600);
}

void loop() {
  smoothed = Smooth(); 
  brightness = map(smoothed, 0, 1023, 0, 255); 
  analogWrite(led, brightness);    

  Serial.print("new smoothed: ");
  Serial.print(smoothed);
  Serial.print(" vs raw data: ");
  Serial.print(new_value);
  Serial.println();                              
} 

int Smooth(){
  new_value = analogRead(A0);

  smooth_return = 0.95 * smooth_return + 0.05 * new_value;
  return smooth_return;
} 
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.