A runningAverage Class for Arduino.

Last Modified: November 30, 2013, at 05:40 AM
By: robtillaart
Platforms: All

remarks & comments

Intro

One of the main applications for the Arduino board is reading and logging of sensor data. For instance one monitors pressure every second of the day. As high sample rates often generates "spikes" in the graphs one also wants to have an average of the measurements. As the measurements are not static in time what we often need is a running average. This is the average of a certain period and very valuable when doing trend analysis.

Simplest form of a running average can be done by code that builds upon the "previous" running average:

float alpha = 0.7; // factor to tune
value = alpha * measurement + (1-alpha) * value;

If one doesn't want to use floating point math - as this takes up memory and decreases speed - one can do the same completely in the integer domain. The division by 256 in the sample code is a shift-right 8, which is faster than say division by e.g. 100. This is true for every power of 2 as divider and one only must take care the sum of the weigths equals the power of 2. And of course one should take care there is no intermediate overflow (consider using unsigned long)

#define POWER 256
int alpha = 178;
value = (alpha * measurement + (POWER - alpha) * value )/ POWER;

If you need a more accurate running average, in concreto from the last 10 measurements, you need an array (or linked list) to hold them. This array acts as a circular buffer and with every new measurement the oldest one is removed. The running average is calculated as the sum of all elements divided by the number of elements in the array. The code for the running average will be something like this:

long runningAverage(int M)
{
  static int LM[10];      // LastMeasurements
  static byte index = 0;
  static long sum = 0;
  static byte count = 0;

  // keep sum updated to improve speed.
  sum -= LM[index];
  LM[index] = M;
  sum += LM[index];
  index = index % LMSIZE;
  if (count < LMSIZE) count++;

  return sum / count;
}

Drawback of this code is that the array to hold all values can become quite large. If you have one measurement per second and you want a running average per minute you need an array of 60; an average per hour would need an array of 3600!. That couldn't be done this way on an Arduino as it only has 2K of RAM. However by building a 2 stage average it can be approached quite well (disclaimer: not for all measurements). In psuedo code:

every second:   rapm = runningAverageMinute(measurement);
every minute:   raph = runningAverageHour(rapm);

As a new internal static array is needed for every runningAverage function, this screams to be implemented as a class.

RunningAverage library

The runningAverage library makes a class of the function above so it can be used multiple times in an sketch. It decouples the add() and the avg() function to be a bit more flexible e.g. one can call the average multiple times without adding a thing. Please note that every instance of the class adds its own array to hold measurements, and that this adds up to the memory usage. The interface of the class is kept as small as possible.

Note: with version 0.2 the names of the methods are all made more descriptive.

  RunningAverage(int);		// constructor; int=size of internal array;
  ~RunningAverage();		// destructor;  
  void clear();			// reset all counters
  void addValue(float);		// add a value (and remove an old one)
  float getAverage();		// get the running average
  void fillValue(float, int);	// fill with n x value

// backwards compatibility
// clr() clear()
// add(x) addValue(x)
// avg() getAverage()

Usage

A small sketch shows how it can be used. A random generator is used to mimic a sensor.

//
//    FILE: runningAverageTest.pde
//  AUTHOR: Rob Tillaart
//    DATE: 2012-12-30
//
// PUPROSE: show working of runningAverage
//

#include "RunningAverage.h"

RunningAverage myRA(10);
int samples = 0;

void setup(void) 
{
  Serial.begin(115200);
  Serial.println("Demo RunningAverage lib");
  Serial.print("Version: ");
  Serial.println(RUNNINGAVERAGE_LIB_VERSION);
  myRA.clear(); // explicitly start clean
}

void loop(void) 
{
  long rn = random(0, 1000);
  myRA.addValue(rn * 0.001);
  samples++;
  Serial.print("Running Average: ");
  Serial.println(myRA.getAverage(), 3);

  if (samples == 300)
  {
    samples = 0;
    myRA.clear();
  }
  delay(100);
}

In setup() the myRA is cleared so we can start adding new data.

In loop() first a random number is generated and converted to a float to be added to myRA. Then the runningAverage is printed to the serial port. One could also display it on some LCD or send over ethernet etc. When 300 items are added myRA is cleared to start over again.

Notes

To use the library, make a folder in your SKETCHBOOKPATH\libaries with the name RunningAverage and put the .h and .cpp there. Optionally make a examples subdirectory to place the sample app.

History

  • 2011-01-30: initial version
  • 2011-02-28: fixed missing destructor in .h file
  • 2011-02-28: removed default constructor;
  • 2012-??-??: trimValue()Yuval Naveh added trimValue (found on web)
  • 2012-11-21: refactored
  • 2012-12-30: added fillValue() + refactored for publishing

Todo

  • Test extensively ...
  • Template class

Enjoy tinkering,
rob.tillaart@removethisgmail.com

RunningAverage.h

 (:source lang=c:)
#ifndef RunningAverage_h
#define RunningAverage_h
// 
//    FILE: RunningAverage.h
//  AUTHOR: Rob dot Tillaart at gmail dot com
// PURPOSE: RunningAverage library for Arduino
//     URL: http://playground.arduino.cc/Main/RunningAverage
// HISTORY: See RunningAverage.cpp
//
// Released to the public domain
//

// backwards compatibility
// clr() clear()
// add(x) addValue(x)
// avg() getAverage()

#define RUNNINGAVERAGE_LIB_VERSION "0.2.02"

class RunningAverage 
{
	public:
	RunningAverage(void);
	RunningAverage(int);
	~RunningAverage();
	void clear();
	void addValue(float);
	float getAverage();
	void fillValue(float, int);

protected:
	int _size;
	int _cnt;
	int _idx;
	float _sum;
	float * _ar;
};

#endif
// END OF FILE

RunningAverage.cpp

// 
//    FILE: RunningAverage.cpp
//  AUTHOR: Rob Tillaart
// VERSION: 0.2.02
// PURPOSE: RunningAverage library for Arduino
//
// The library stores the last N individual values in a circular buffer, 
// to calculate the running average. 
//
// HISTORY: 
// 0.1.00 - 2011-01-30 initial version
// 0.1.01 - 2011-02-28 fixed missing destructor in .h
// 0.2.00 - 2012-??-?? Yuval Naveh added trimValue (found on web)
//          http://stromputer.googlecode.com/svn-history/r74/trunk/Arduino/Libraries/RunningAverage/RunningAverage.cpp
// 0.2.01 - 2012-11-21 refactored
// 0.2.02 - 2012-12-30 refactored trimValue -> fillValue
//
// Released to the public domain
//

#include "RunningAverage.h"
#include <stdlib.h>

RunningAverage::RunningAverage(int n)
{
	_size = n;
	_ar = (float*) malloc(_size * sizeof(float));
	clear();
}

RunningAverage::~RunningAverage()
{
	free(_ar);
}

// resets all counters
void RunningAverage::clear() 
{ 
	_cnt = 0;
	_idx = 0;
	_sum = 0.0;
	for (int i = 0; i< _size; i++) _ar[i] = 0.0;  // needed to keep addValue simple
}

// adds a new value to the data-set
void RunningAverage::addValue(float f)
{
	_sum -= _ar[_idx];
	_ar[_idx] = f;
	_sum += _ar[_idx];
	_idx++;
	if (_idx == _size) _idx = 0;  // faster than %
	if (_cnt < _size) _cnt++;
}

// returns the average of the data-set added sofar
float RunningAverage::getAverage()
{
	if (_cnt == 0) return 0; // NaN ?  math.h
	return _sum / _cnt;
}

// fill the average with a value
// the param number determines how often value is added (weight)
// number should preferably be between 1 and size
void RunningAverage::fillValue(float value, int number)
{
	clear();
	for (int i = 0; i < number; i++) 
	{
		addValue(value);
	}
}
// END OF FILE

Share