RunningAverage Class on playground (updated)

Or use an array of objects. Note the allocation is somewhat different

(note not tested, highly experimental)

//
//    FILE: runningAverageTest.ino
//  AUTHOR: Rob Tillaart
//    DATE: 2014-05-07
//
// PUPROSE: show working of runningAverage
//

#include "RunningAverage.h"

RunningAverage *RA[6];  // 6 pointers to RA objects
int pin[6] = { 
  A0, A1, A2, A3, A4, A5 };
int size[6] = { 15, 13, 11, 9, 7, 5 };

int samples = 0;

void setup(void) 
{
  Serial.begin(115200);
  Serial.println("Demo RunningAverage lib");
  Serial.print("Version: ");
  Serial.println(RUNNINGAVERAGE_LIB_VERSION);

  for (int i=0;i<6; i++) 
  {
    RA[i] = new RunningAverage( size[i] );
    RA[i]->clear();
  }
}

void loop(void) 
{
  for (int i=0; i<6; i++)
  {
    RA[i]->addValue(analogRead(pin[i]));
  }

  for (int i=0; i<6; i++)
  {
    Serial.print(RA[i]->getAverage(), 3);
    Serial.print(",");
  }
  Serial.println();

  samples++;
  if (samples == 300)
  {
    samples = 0;
    for (int i=0;i<6; i++) 
    {
      RA[i]->clear();
    }
  }
  delay(100);
}

I am having overflow errors in visual micro debugger in ms visual studio.

I have 15 instances of RunningAverage sized from 24 to 60.

I am getting random ovf errors on calls to getAverage().

S = savg.getAverage() generates an ovf error at random times.

Chrismolloy:
I am having overflow errors in visual micro debugger in ms visual studio.

I have 15 instances of RunningAverage sized from 24 to 60.

I am getting random ovf errors on calls to getAverage().

S = savg.getAverage() generates an ovf error at random times.

here is the code

/*
 This code creates a schedule for events every second, minute & hour 
 */

#include <Metro.h> //Include Metro library 

// Create metro objects and set intervals to one second, minute & hour
Metro Second = Metro(1000); //one second
Metro Minute = Metro(2000); //one minute
Metro Hour = Metro(4000); //one hour

#include "RunningAverage.h"

//Create running average objects
RunningAverage Sensor1Seconds(60);
RunningAverage Sensor1Minutes(60);
RunningAverage Sensor1Hours(24);
RunningAverage Sensor2Seconds(60);
RunningAverage Sensor2Minutes(60);
RunningAverage Sensor2Hours(24);
RunningAverage Sensor3Seconds(60);
RunningAverage Sensor3Minutes(60);
RunningAverage Sensor3Hours(24);
RunningAverage Sensor4Seconds(60);
RunningAverage Sensor4Minutes(60);
RunningAverage Sensor4Hours(24);
RunningAverage Sensor5Seconds(60);
RunningAverage Sensor5Minutes(60);
RunningAverage Sensor5Hours(24);


//variables to track time elapsed
int seconds = 0;
int minutes = 0;
int hours = 0;
int led = 13;

void blink()
{
	digitalWrite(led,1);
	int m = millis();
	while(millis() < m+500);
	//for(int n=1;n<100000;n=++n);
	digitalWrite(led,0);
}

void setup()
{
    pinMode(led,OUTPUT);
	blink();
	Sensor1Seconds.clear();
	Sensor1Minutes.clear();
	Sensor1Hours.clear();
	Sensor2Seconds.clear();
	Sensor2Minutes.clear();
	Sensor2Hours.clear();
	Sensor3Seconds.clear();
	Sensor3Minutes.clear();
	Sensor3Hours.clear();
	Sensor4Seconds.clear();
	Sensor4Minutes.clear();
	Sensor4Hours.clear();
	Sensor5Seconds.clear();
	Sensor5Minutes.clear();
	Sensor5Hours.clear();
}

float s1=0;
float s2=0;
float s3=0;
float s4=0;
float s5=0;
float m1=0;
float m2=0;
float m3=0;
float m4=0;
float m5=0;
float h1=0;
float h2=0;
float h3=0;
float h4=0;
float h5=0;
float rn;

void SensorSecondUpdate() {
		rn = random(0, 1000);
        Sensor1Seconds.addValue(rn);
		s1 = Sensor1Seconds.getAverage();
		rn = random(0, 1000);
        Sensor2Seconds.addValue(rn);
		s2 = Sensor2Seconds.getAverage();
		rn = random(0, 1000);
        Sensor3Seconds.addValue(rn);
		s3 = Sensor3Seconds.getAverage();
		rn = random(0, 1000);
        Sensor4Seconds.addValue(rn);
		s4 = Sensor4Seconds.getAverage();
		rn = random(0, 1000);
        Sensor5Seconds.addValue(rn);
		s5 = Sensor5Seconds.getAverage();

}

void SensorMinuteUpdate() {
		float x = Sensor1Seconds.getAverage();
		Sensor1Minutes.addValue(x);
		m1 = Sensor1Minutes.getAverage();
		Sensor1Seconds.clear();
		x = Sensor2Seconds.getAverage();
		Sensor2Minutes.addValue(x);
		m2 = Sensor2Minutes.getAverage();
		Sensor2Seconds.clear();
		x=Sensor3Seconds.getAverage();
		Sensor3Minutes.addValue(x);
		m3 = Sensor3Minutes.getAverage();
		Sensor3Seconds.clear();
		x = Sensor4Seconds.getAverage();
		Sensor4Minutes.addValue(x);
		m4 = Sensor4Minutes.getAverage();
		Sensor4Seconds.clear();
		x=Sensor5Seconds.getAverage();
		Sensor5Minutes.addValue(x);
		m5 = Sensor5Minutes.getAverage();
		Sensor5Seconds.clear();
}

void SensorHourUpdate() {
		float x=Sensor1Minutes.getAverage();
		Sensor1Hours.addValue(x);
		h1 = Sensor1Hours.getAverage();
		x=Sensor2Minutes.getAverage();
		Sensor2Hours.addValue(x);
		h2 = Sensor2Hours.getAverage();
		x=Sensor3Minutes.getAverage();
		Sensor3Hours.addValue(x);
		h3 = Sensor3Hours.getAverage();
		x=Sensor4Minutes.getAverage();
		Sensor4Hours.addValue(x);
		h4 = Sensor4Hours.getAverage();
		x=Sensor5Minutes.getAverage();
		Sensor5Hours.addValue(x);
		h5 = Sensor5Hours.getAverage();
}


void loop()
{

  if (Second.check() == 1) { // check if its been a second
		seconds = ++seconds;
		SensorSecondUpdate();
		blink();
	//put stuff here to do every second
  }

    if (Minute.check() == 1) { // check if its been a minute
		minutes = ++minutes;
		SensorMinuteUpdate();
		//put stuff here to do every minute
  }
	 
	if (Hour.check() == 1) { // check if its been an hour
		hours = ++hours;
		SensorHourUpdate();

		//put stuff here to do every hour
  }

}

moderatore: added code tags - # button above smileys

you have 10 RA objects of size 60 and 5 RA objects of size 24, that is a lot of memory (at least for an UNO)

Every object has 10 bytes local vars and 4 bytes per element.

15 objects = 150 bytes
10 x 60 x 4 = 2400 bytes
5 x 24 x 4 = 480 bytes

roughly 3000+ bytes of SRAM

What board are you using?
If it is an UNO, it does not have that much memory, ==> Get a MEGA.

Learning point for me: The RA class has no error handling or state to see its "health".
Analysis:
The class does not check if the allocation of the needed "arrays" succeed, but the code behaves as if it does so.
That means that only the first RA object are healthy and the others have internal pointers pointing to (probably) NULL.

To make it more robust the interface of the Class could change to have a method

bool begin(uint8_t size) ;

that returns true is size elements could be allocated.
The allocation should be removed from the constructor.
// this is a breaking interface change

Another alternative is to have a method
uint8_t internalSize();
That returns the size of the internal size. This returns zero if allocation had failed and the internal size otherwise.
// this would not break the existing interface and would increase footprint not much.

Updated the library to version 0.2.04 ( 0.2.03 was skipped - dev only version)

To solve the problem of Chrismolloy above I added code to check if the internal array could be allocated. The previous 0.2.02 version was really opportunistic :wink:
If the array cannot be allocated the size of the array is set to 0, the new method getSize() can be used to check if the allocation worked. Having an internal size of zero the Class cannot accept new values and the average will be NAN as it cannot be calculated.

The size of the lib increased about 20 bytes, which is imho acceptable for this "safety net".

As always remarks and comments are welcome.

Rob,

I've been using your library, in conjunction with TinyGPS++ library, in a GPS routine to average the GPS position and help reduce the position wander, and had some interesting results. Whilst the longitude seems to function correctly e.g. 1.01834380, the latitude seems to stop or stall and not continue example: 51.34103012.

Note that I'm going to 8 decimal points for the LAT/ LON position. I wondered if there is a limitation in the RA library that limits the length or size of the number?

Also, would it be capable of taking both + and - numbers?

Comments or help very much appreciated!

Regards.

The RA lib uses floats which are on an AVR based Arduino 32bits IEEE754 floats. This means a 23 bit mantissa which equals about 7 significant digits.

If you are using an ARM based Arduino, which supports 64 bit double you could patch the library by replacing all float types with double. Should be a good change anyway. On my todo list.

updated Running Average Library to version 0.2.05 on GITHUB

changed float to double
==> support for double on ARM of the Arduino DUE ...

Thanks Rob.

BTW I'm using a MEGA with GPS engines running at 4Hz...

updated Running Average Library to version 0.2.06 on GITHUB

  • minor edits

skip version 0.2.07 (contained typos)

updated Running Average Library to version 0.2.08 on GITHUB

since 0.2.06

  • added getMin() and getMax() - thanks to Eric M.
  • refactored

Note getMin() and getMax() gives the minimum/maximum since last call to clear(),
==> so it is not the min/max of the internal buffer.

Do you have any plans on getting your libraries added to the 1.6.2+ library manager? I imagine it would take quite a bit of work because it seems at first brush that each library should be it's own repository (instead of how you have several libraries in one repository).

I think AdaFruit has published a script to help mass publishing of libraries.

No I do not have such plans in near future as my time is quite limited.

And I am using 1.5.8 which works quite well for me.

robtillaart:
No I do not have such plans in near future as my time is quite limited.

And I am using 1.5.8 which works quite well for me.

OK. Fair answer.

One thing to be wary of when using this library is floating point imprecision.

I left a unit running for a week sampling 10 times a second and was very confused when I came back as to why my average (over the last 50 samples) was very different to the actual current input.

Looking into the code, the RunningAverage::addValue function subtracts a Double and then adds a Double. Adding and subtracting of Doubles (and Floats) can lead to a huge degree of imprecision.

I'm not sure what the best solution is here, calculating the sum total every time getAverage() is called is the obvious one, but that leads to somewhat more function overhead.

You are completely right. Thanks for this observation.

10 samples/second = 864.000 /day = 6 million per week.

The number of samples is approaching the accuracy of the (IEEE754) float (7 digits) when one reaches 1 million samples (1 day in your case) . This means if the average sample has 3 digits, one will loose 2 digits every time.

The "trick" of remove one double and add one double is an efficient way to calculate the sum of the array of samples. And yes when using this trick the error will add up in sum. In theory after 100 samples the last two digits are suspected to be incorrect. Need to investigate the effect.

Solution is to (sort and) add all elements of the array every time, giving a constant error. Consequence is a slower execution. The sorting will improve the sum when the dynamic range of the numbers is large.

A practical solution for the library might be to do a proper sum e.g. every 100 or 1000 times a value is added (new parameter?). The lower this number the more accurate the sum. The price is a performance penalty once every 100 / 1000 times. There are several variations possible.
A good value for the number will also depend on the size of the numbers added. Ideally one would like to have a sort of error counter that makes an estimate of the max error and when a threshold is reached do a recalculation of sum.

The easiest solution is to redo the function Average() and rename the current one to fastAverage();

in short food for thought.

thanks

quick patch:

// returns the average of the data-set added sofar
double RunningAverage::getAverage() const
{
    if (_cnt == 0) return NAN;
    double sum = 0;
    for (uint8_t i = 0; i < _cnt; i++)
    {
        sum += _ar[i];
    }
    return sum / _cnt;
}

double RunningAverage::getFastAverage() const
{
    if (_cnt == 0) return NAN;
    return _sum / _cnt;
}

the .h file must add a signature for

double getFastAverage() const;

updated Running Average Library to version 0.2.11 on GITHUB
Arduino/libraries/RunningAverage at master · RobTillaart/Arduino · GitHub -

The following changes were made since 0.2.08
(request aaronblanchard)

  • getAverage() renamed to getFastAverage() as it is fast but less accurate.
  • reimplemented getAverage() to be accurate, but a bit slower.
    getAverage() sums all elements in the internal buffer and average them. This solves the remark from aaronblanchard a few posts ago that the fastAverage() drifts from the real value. An example sketch is included to show this drift.

(request by mail)

  • added GetMinInBuffer() to make difference with getMin()

  • added GetMaxInBuffer() to make difference with getMin()
    Note getMin() and getMax() gives the minimum/maximum since last call to clear(). An example sketch shows the differences between the methods.

  • refactored

snippet from drift test (included as example)

COUNT       AVG	        FASTAVG     DIFF        MAXDIFF sofar
30750000	0.5153000	0.5152977	0.0000023	0.0000522
30760000	0.5241000	0.5240974	0.0000026	0.0000522

The sample sketch adds values between 0.0 and 0.999 in a loop. The maximum difference after 30 million++ additions is relative still low. There will be input streams which are more sensitive for the fastAverage algorithm. So when max accuracy is a requirement please use getAverage().

as always remarks and bug reports are welcome,
Rob

robtillaart:
updated Running Average Library to version 0.2.11 on GITHUB
Arduino/libraries/RunningAverage at master · RobTillaart/Arduino · GitHub -

Maybe a stupid question but where is the download button for this library? I must be missing something. :-[

No stupid question, the [download zip] button is two levels up (right side)

or use

https://github.com/RobTillaart/Arduino/archive/master.zip