Arduino Playground is read-only starting December 31st, 2018. For more info please look at this Forum Post

A runningAverage Class for Arduino.

Last Modified: January 15, 2016, at 05:19 AM
By:
Platforms: All

remarks & comments

latest version on github 0.2.04

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) {
  #define LM_SIZE 10
  static int LM[LM_SIZE];      // 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 = index % LM_SIZE;
  if (count < LM_SIZE) 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()

    // new in 0.2.04 version
    float getElement(uint8_t idx);
    uint8_t getSize() { return _size; }
    uint8_t getCount() { return _cnt; }

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
  • 2014-07-03: added memory protection code - if internal array cannot be allocated size becomes 0. This is to solve issue described here - http://forum.arduino.cc/index.php?topic=50473.msg1790086#msg1790086 -

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: https://arduino.cc/playground/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.04"

#include "Arduino.h"

class RunningAverage
{
public:
    RunningAverage(void);
    RunningAverage(int);
    ~RunningAverage();

    void clear();
    void addValue(float);
    void fillValue(float, int);

    float getAverage();

    float getElement(uint8_t idx);
    uint8_t getSize() { return _size; }
    uint8_t getCount() { return _cnt; }

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

#endif
// END OF FILE

RunningAverage.cpp

//
//    FILE: RunningAverage.cpp
//  AUTHOR: Rob Tillaart
// VERSION: 0.2.04
// 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)
//          https://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
// 0.2.03 - 2013-11-31 getElement
// 0.2.04 - 2014-07-03 added memory protection
//
// Released to the public domain
//

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

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

RunningAverage::~RunningAverage()
{
    if (_ar != NULL) 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)
{
    if (_ar == NULL) return;
    _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 NAN;
    return _sum / _cnt;
}

// returns the value of an element if exist, 0 otherwise
float RunningAverage::getElement(uint8_t idx)
{
    if (idx >=_cnt ) return NAN;
    return _ar[idx];
}

// 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