Simple data smoothing with Single Pole filter technique

I just ran across this technique and think it's really cool and simple. It's apparently a common DSP toolkit item, but it took me years to find out about it, so let me share this with the un-enlightened.

Common problem: ADC values fluctuate, need to smooth out data for better results.

Solution: A simple technique known to academics as a single pole recursive filter.

Technically we make our current value dependent on the previous value. In a full recursive equation this goes on across many samples with different coefficients for adding each previous value back into the equation, and each coefficient carefully choosen so that the output value is not astable. Do some Google searches if you want to learn more about all that stuff.
Here's were I got this: http://www.analog.com/static/imported-files/tech_docs/dsp_book_Ch19.pdf

Luckily for us, there is a nice simple equation with just 2 parameters and the previous value when interpretting the current value.
Mathematically it looks something like this:
y[n] = A0 * x[n] + B1 * y[n-1];
Using A=0.15 and B=.85 turns this into a beautifully simple low pass filter for our data. Different values can be used, but the rule is that A+B=1, or to put it differently B=1-A

Here's a sample sketch for this

#define SINGLE_POLE_FILTER_A0 0.15
#define SINGLE_POLE_FILTER_B1 0.85
#define SINGLE_POLE_C 0.75

float val=-1; // our filtered, read value
byte a_pin=8; //analog pin to read

float singlePoleFilter(const int adc, int prev_val)
{
  int sign;
  if (prev_val==-1)
    return adc;
  else
  {

    if (adc>prev_val)
      sign=1;
    else if (adc==prev_val)
      sign=0;
    else
      sign=-1;
    return SINGLE_POLE_C*sign+ SINGLE_POLE_FILTER_A0*adc + SINGLE_POLE_FILTER_B1*prev_val;
  }
}

void setup() {
  Serial.begin(9600);
//  val = 256; // uncomment to watch function converge from here
}

void loop() {

//  compact call:
//  val = singlePoleFilter(analogRead(a_pin),val);

// illustrative call:
  int adc = analogRead(a_pin);
 // adc = 512; //uncomment to watch convergence to a fixed value
  val = singlePoleFilter(adc,val);
  Serial.print(val);
  Serial.print(' ');
  Serial.print(adc);
  Serial.print(' ');
  Serial.println(val-adc);
  delay(500);
  
}

I initialized "val" in setup so that you can see how many samples it takes for the readings to converge. You can have some fun tweaking the numbers. A=.25 and B=.75 gives a much faster convergence but is a little noisier. After spending way too much time watching numbers scroll across my screen, I noticed that the filtered values didn't exactly converge. It would converge to a point and then stop, and it tended to get stuck on the low side. After a bunch of trial and error I found that .75 added helps converge from below, but increased the offset from above. Flipping the sign around depending on which way it is converging seemed to produce the best output. In the test case from 256 converging to 512 it reaches 511.90, and it nails 512 when converging from above.

So anyway there's a nice little trick to help when reading analog values. Enjoy!

its quite intuitive, when u (by coincidence) found (last+current)/2 already... :slight_smile:
it is mentioned here in the playground:
http://arduino.cc/playground/Main/RunningAverage
but the sign trick is not mentioned there...

can it be done with faster integer arithmetics?
return 12sign+ 2adc + 14ULL*prev_val/16;
the result would be a value, that is 4 bit (2^4=16) longer than adc...
this might be an increase of accuracy by some variation of "oversampling"...