I'm trying to average/smooth values from an analogue input pin. Heres the code...
// Read in a value from an analogue input pin.
int smoothEle = readElevatorPot();
// Create a 'rolling average' of the last 8 values....
_eleAvg = ((_eleAvg * 7) / 8) + (smoothEle / 8);
Serial.print("smoothEle=<");Serial.print(smoothEle);Serial.print(">, _eleAvg=<");Serial.print(_eleAvg);Serial.println(">");
The function readElevatorPot() just returns a number between 0 and 1023 from an analogue input pin. (this bit works ok)
But I was expecting the line _eleAvg = ((_eleAvg * 7) / 8) + (smoothEle / 8); to create a sort of rolling average of the last 8 values from the analogue input pin. As smoothEle varies, the value of _eleAvg also varies, but why is it consistently between 5 and 10 lower than smoothEle? Even after it's been given time to stabilize?
It is not an average of the last 8 inputs. It is an average of every input since the program started.
As KeithRB has said, there's a problem with integer truncation too. It works a bit better if you use this:
Not exactly el_supremo. It is pretty heavily weighted for the last 8 values. After all, the effect of the initial 0 is gone after 45 steps. A full average of all the steps is still 900.
After all, the effect of the initial 0 is gone after 45 steps.
That is only because of the finite precision of a 16-bit word and the integer division. If this were done in 64-bit floating point, the initial values would still be having an impact long after the code started.
Depending upon the application and the sampling rate, 45 steps might be unacceptable.
el_supremo:
It is not an average of the last 8 inputs. It is an average of every input since the program started.
As KeithRB has said, there's a problem with integer truncation too. It works a bit better if you use this:
_eleAvg = (_eleAvg * 7 + smoothEle)/8;
Pete
There's a more robust way to implement a digital filter, you need to be more careful about
where the errors can accumulate. Its not an average its a IIR digital filter and you'd
normally do it thus:
The reason to do it this way is so that the DC gain is exactly 1.0 whether or not
the coefficients are exactly representable (you might be using fixed point rather
than floating point in digital signal processing for greater speed). You also
only need one coefficient, rather than a pair of them that might get out of step.
In fixed point you might do something like
unsigned int filtered ;
...
unsigned int input = analogRead (pin) << 6 ; // scale to 16 bits, 6 guard bits
filtered += (input - filtered) >> 3 ;
But note that when the difference of (input - filtered) is in the range 1..7 the shifted
version won't work any more in steady state conditions - it can be improved by
doing more sensible rounding:
Now the difference can be in the range -4..3 before the value jams up.
The solution to this problem is to inject a noise value into the system, with
just enough amplitude to allow the value to converge. In all sampled systems
injecting the right amount of noise increases accuracy, odd though it sounds!
But here the 6 guard bits are probably enough to give good enough results.
Thanks for all your help guys. For my purposes, making sure the initial value of _eleAvg is correctly set at the beginning seems to give the desired results.
There's a more robust way to implement a digital filter, you need to be more careful about
where the errors can accumulate. Its not an average its a IIR digital filter and you'd
normally do it thus:
Sorry to Jump in here, but MarkT, can you explain what hapens to 'Filtered' when you add the + in front of it, and WHY " *0.125"?
I used " filtered += (input - filtered) * 0.125 ;" to smooth out some analog inputs in a circuit thats in my car, and WOW does it work well!
I just need to understand the formula!!!
Think of it like this - the value of filtered is sluggish to respond
to changes since only a fraction of the error term is allowed to
correct it on each cycle - it takes a dozen or more cycles to shift
significantly close to the input value when there's a step change.
As a consequence noise is mainly cancelled out when its rapidly
fluctuating. Long term changes in the input do get through.
IS there a way to speed it up slightly?
And where does the 0.125 factor come from? Obviously this has been derived...
Can I sacrifice some accuracy but get a quicker responding output?
Can I sacrifice some accuracy but get a quicker responding output?
Sure, but that conflicts with the post title. You need to back up and explain what you are trying to accomplish.
Hi Paul.
Essentially, this is a pretty basic Multi Sensor system. 4 Analog Inputs being displayed on a LCD inside a car.
I was battling with noise / just unstable measurements because of the harsh environment. However, this 1-pole digital IIR filter works like a charm!!!!
But, i find that the readings displayed on the LCD change a little bit to slowly - not instant enough.
On startup, when certain reading are zero, within one second these readings should jump to around a value of 7. This takes sometime ti get to because of the system obviously trying to correct the error.
IF, i could manipulate this filter and make it half for twice the speed, that would probably suffice.
MarkT explained what all the terms mean. The 0.125 is the fraction of the new reading to add to the existing average. Increase that fraction to get the average to change faster. Decrease it to get the average to change slower.