Measurements from the real world often contain noise. Loosely speaking, noise is just the part of the signal you didn’t want. Maybe it comes from electrical noise: the random variations you see when calling analogRead on a sensor that should be stable. Noise also arises from real effects on the sensor. Vibration from the engine adds noise if you’re measuring the acceleration of a go-kart, for example. Filtering is a method to remove some of the unwanted signal to leave a smoother result.

Filters

We’ll use MegunoLink to compare three different filters:

Averaging Running average Expotential filter

MegunoLink’s Time Plot Visualizer will be used to show both the raw, unfiltered, data and the output from the filter. Comparing the raw and filtered on a single plot lets us easily see the effect of the filter. The filters will smooth the data but they can also introduce a lag.

Something to Filter

To generate some ‘noisy’ data for filtering a thermistor was connected to analog-input 0 on an Arduino Uno. A thermistor is a resistor whose resistance changes with temperature. As temperature increases, resistance goes down; as temperature decreases, resistance goes up. The thermistor was connected in a voltage divider configuration with one leg connected to ground and the other to the analog-input. A 100kΩ resistance was connected between the 3.3V output of the Arduino and the analog-input to provide current for the thermistor.

To measure temperature, the analog value was read. Then the raw analog value was converted to temperature, in degrees Celsius, using the following Arduino code:

float MeasureTemperature() { int nRawThermistor = analogRead(0); /* Constants to help conver the raw analogue measurement into * temperature in degrees Celcius */ const float ThermistorResistance = 10000; // Thermistor resistance at some nominal temperature const float NominalTemperature = 25; // The nominal temperature where resistance is known. const float BCoefficient = 3950; // The beta coefficient of the thermistor (from data-sheet) const float Vsupply = 3.3; // The supply voltage for the voltage divider. const float Vref = 1.1; // Analogue reference voltage. const float Rtop = 100e3; // Bias resistance for the voltage divider. // Calculate the output voltage of the voltage divider; it depends on temperature. float Vout = (float)nRawThermistor * Vref/1023.0; // Calculate the thermistor resistance. float Temp = Vout/Vsupply; float Rtherm = Rtop*Temp/(1-Temp); // Convert thermistor resistance into temperature using the Steinhart equation float Temperature; Temperature = Rtherm / ThermistorResistance; Temperature = log(Temperature); Temperature /= BCoefficient; Temperature += 1.0 / (NominalTemperature + 273.15); Temperature = 1.0 / Temperature; Temperature -= 273.15; // convert to C return Temperature; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 float MeasureTemperature ( ) { int nRawThermistor = analogRead ( 0 ) ; /* Constants to help conver the raw analogue measurement into * temperature in degrees Celcius */ const float ThermistorResistance = 10000 ; // Thermistor resistance at some nominal temperature const float NominalTemperature = 25 ; // The nominal temperature where resistance is known. const float BCoefficient = 3950 ; // The beta coefficient of the thermistor (from data-sheet) const float Vsupply = 3.3 ; // The supply voltage for the voltage divider. const float Vref = 1.1 ; // Analogue reference voltage. const float Rtop = 100e3 ; // Bias resistance for the voltage divider. // Calculate the output voltage of the voltage divider; it depends on temperature. float Vout = ( float ) nRawThermistor * Vref / 1023.0 ; // Calculate the thermistor resistance. float Temp = Vout / Vsupply ; float Rtherm = Rtop* Temp / ( 1 - Temp ) ; // Convert thermistor resistance into temperature using the Steinhart equation float Temperature ; Temperature = Rtherm / ThermistorResistance ; Temperature = log ( Temperature ) ; Temperature /= BCoefficient ; Temperature += 1.0 / ( NominalTemperature + 273.15 ) ; Temperature = 1.0 / Temperature ; Temperature -= 273.15 ; // convert to C return Temperature ; }

After applying the various filters, the data was sent to MegunoLink for plotting:

: : TimePlot Plot; Plot.SendData("Raw Temperature", RawTemperature); Plot.SendData("Average Temperature", AverageTemperature); : : 1 2 3 4 5 6 7 : : TimePlot Plot ; Plot . SendData ( "Raw Temperature" , RawTemperature ) ; Plot . SendData ( "Average Temperature" , AverageTemperature ) ; : :

Filtering Algorithms

The three filtering algorithms we are going to look at are:

Averaging Running average Exponential filter

The screenshot from MegunoLink below shows these algorithms together. A small rise in temperature was created by touching the thermistor to see how the different filters would respond. The grey line is the raw temperature measurement. It is pretty good with not much noise. But there is still room for improvement.

Averaging

One of the easiest ways to filter noisy data is by averaging.

Averaging works by adding together a number of measurements, the dividing the total by the number of measurements you added together. The more measurements you include in the average the more noise gets removed. There is a diminishing return though: the average of 101 measurements isn’t going to be much less noisy than the average of 100 measurements.

Here’s the code to calculate an average temperature measurement:

float AverageTemperature = 0; int MeasurementsToAverage = 16; for(int i = 0; i < MeasurementsToAverage; ++i) { AverageTemperature += MeasureTemperature(); delay(1); } AverageTemperature /= MeasurementsToAverage; 1 2 3 4 5 6 7 8 float AverageTemperature = 0 ; int MeasurementsToAverage = 16 ; for ( int i = 0 ; i < MeasurementsToAverage ; ++ i ) { AverageTemperature += MeasureTemperature ( ) ; delay ( 1 ) ; } AverageTemperature /= MeasurementsToAverage ;

Notice the delay between each measurement. That delay is to give the analog-input time to stabilize between each measurement. Without it, your average will tend to be lower than the true measurement.

The picture below shows just the raw data and the average filter. Averaging, even only 16 measurements, does a pretty good job of smoothing the small random noise in the raw measurements. Even better, it tracks the change in temperature (when I touched the sensor) very closely.

The average filter might be the best one for this application, when the original measurement is not very noisy.

Running Average

One downside of the average filter is the amount of time needed to make a measurement. The measurement time can be important in low-power applications. The Arduino uses much more power when it is awake and running your program than it does when it asleep in standby.

An alternative to making all the measurements at once then averaging them is to make one measurement at a time and add it to a running average. The calculation of the average is the same: sum up all the measurements and divide by how many you made.

But we have to store the measurement history. Here’s a snippet of code to do the running average:

const int RunningAverageCount = 16; float RunningAverageBuffer[RunningAverageCount]; int NextRunningAverage; void loop() { float RawTemperature = MeasureTemperature(); RunningAverageBuffer[NextRunningAverage++] = RawTemperature; if (NextRunningAverage >= RunningAverageCount) { NextRunningAverage = 0; } float RunningAverageTemperature = 0; for(int i=0; i< RunningAverageCount; ++i) { RunningAverageTemperature += RunningAverageBuffer[i]; } RunningAverageTemperature /= RunningAverageCount; delay(100); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const int RunningAverageCount = 16 ; float RunningAverageBuffer [ RunningAverageCount ] ; int NextRunningAverage ; void loop ( ) { float RawTemperature = MeasureTemperature ( ) ; RunningAverageBuffer [ NextRunningAverage ++ ] = RawTemperature ; if ( NextRunningAverage > = RunningAverageCount ) { NextRunningAverage = 0 ; } float RunningAverageTemperature = 0 ; for ( int i = 0 ; i < RunningAverageCount ; ++ i ) { RunningAverageTemperature += RunningAverageBuffer [ i ] ; } RunningAverageTemperature /= RunningAverageCount ; delay ( 100 ) ; }

This gives a much slower response to changes because of the 100 ms delay between making each measurement. In other words, it only takes 16 ms for the average filter to get 16 new measurements but it takes 1.6 seconds (16 x 100 ms) for the running average to get 16 new measurements. So it responds to changes more slowly. And you can see that in the picture below. The grey line is the same raw temperature measurement as before. The running average is shown in blue.

If the delay at the end of the loop was reduced from 100 ms to 1 ms, the response of the running average would be the same as the simple average. However, the longer delay between measurements is time when the Arduino could be asleep, saving battery power. Or busy doing something else.

The running average seems like a good alternative to a simple average to give a smoother output and let the Arduino work on other things. But it has one big down side: memory use.

Because you have to keep track of the history to calculate a running average, filtering many measurements quickly becomes impractical. The Arduino Uno only has 2k of RAM to store this history and you will quickly run out. If you needed to keep the history for some other reason, it could be a good choice. But I wouldn’t use a running average filter on an Arduino very often because of the amount of memory it uses.

Exponential Filter

The last filter is a recursive filter. A recursive filter is just one that calculates a new, smoothed value (y n ) by using the last smoothed value (y n – 1 ) and a new measurement (x n ):

y n = w × x n + (1 – w) × y n – 1

The amount of smoothing is controlled by a weighting parameter (w). The weight is a value between 0% and 100%. When the weight is high (say 90%), the filter doesn’t smooth the measurements very much but responds quickly to changes. If the weight is low (say 10%), the filter smooths the measurements a lot but doesn’t respond very quickly to changes.

This is my favorite filter because:

it doesn’t need much memory (just enough to store the last measurement)

you can control how much filtering is applied with a single parameter (the weight)

it works well in battery powered applications because you don’t need to make many measurements at once

Its so useful, we added the exponential filter to our Arduino library. And that makes it easy to use as well! Here’s what you need:

#include "Filter.h" // the <float> makes a filter for float numbers // 20 is the weight (20 => 20%) // 0 is the initial value of the filter ExponentialFilter<float> FilteredTemperature(20, 0); loop { float RawTemperature = MeasureTemperature(); FilteredTemperature.Filter(RawTemperature); float SmoothTemperature = FilteredTemperature.Current(); } 1 2 3 4 5 6 7 8 9 10 11 12 13 #include "Filter.h" // the <float> makes a filter for float numbers // 20 is the weight (20 => 20%) // 0 is the initial value of the filter ExponentialFilter < float > FilteredTemperature ( 20 , 0 ) ; loop { float RawTemperature = MeasureTemperature ( ) ; FilteredTemperature . Filter ( RawTemperature ) ; float SmoothTemperature = FilteredTemperature . Current ( ) ; }

With a weight of 20, we get a fairly strong filter but it is still more responsive than the running average:

Conclusion

So there we have it. 3 methods to filter noisy measurements you make with an Arduino:

Averaging: easy to implement and understand.

Running average: can use a lot of memory; usually not a good choice for an Arduino sketch.

Exponential filter: easy to change the amount of filtering using a weight; doesn’t need much memory; easy to implement with our Arduino filter library.

To test these filters, plot your raw data and the filtered measurements with MegunoLink to see how the filter responds when the data changes. Pick the one that gives the smoothest result without distorting the original data. Which one worked best for you? Post a comment below and let us know how you get on.

Check out our documentation for help getting started with MegunoLink plotting.