Math

Hi all,

How could I program the Arduino to take the average of the last ten values of a volatile variable and set and LED to high when the average decreases significantly from the last one?

use a circular buffer, or you can try to use my little library like this:

#include "CircularBuffer.h"

#define READINGS 10

CircularBuffer myBuffer(READINGS);  // create a global array of pointers to CircularBuffer objects
byte interruptPin = 3;
unsigned long lastUpdateTime = 0;
float runningAverage;
volatile bool gotValue = false;

void setup()
{
  Serial.begin(9600);
  attachInterrupt(digitalPinToInterrupt(interruptPin), myISR, CHANGE);
}

void loop()
{
  if (millis() - lastUpdateTime > 1000UL)
  {
    Serial.print("\tAverage: ");
    Serial.println(myBuffer.mean());
    Serial.print("\tAverage of ");
    Serial.print(myBuffer.numElements());
    Serial.println(F(" readings"));
    Serial.print("\tMax Value of ");
    Serial.println(myBuffer.maxVal());
    Serial.print("\tMin Value of ");
    Serial.println(myBuffer.minVal());
    lastUpdateTime += 1000UL;
  }
  if(gotValue)
  {
    int myValue = millis();  // <<<<<<<<<<<<< Get your value instead!!!
    if(myValue > runningAverage * 1.90f)  // Value is less than average minus 10%
    {
      doAlarm();
    }
    myBuffer.addElement(myValue);
    runningAverage = myBuffer.mean();
    gotValue = false;
  }
}

void myISR()
{
  gotValue = true;
}
void doAlarm()
{
  Serial.println(F("ALARM"));
}

header:

#ifndef CircularBuffer_h
#define CircularBuffer_h

#include <Arduino.h>

class CircularBuffer {
  public:
    CircularBuffer(size_t size);
    void addElement(float newElement);
    float mean();
    float maxVal();
    float minVal();
    float sigma();
    size_t numElements();
    float arrayElement(int location);

  private:
    float* array;
    float* next;
    size_t size;
    int index = 0;
    int num_elements = 0;
};

#endif

implementation file:

#include "CircularBuffer.h"
#include <Arduino.h>

CircularBuffer::CircularBuffer(size_t arraySize)
{
  size = arraySize;
  array = new float[arraySize];
  next = &array[0];
}

void CircularBuffer::addElement(float newElement)
{
  *next = newElement;
  index++;
  if (index >= size)
  {
    index = 0;
  }
  next = &array[index];
  if (++num_elements > size)
  {
    num_elements = size;
  }
}

float CircularBuffer::arrayElement(int location)
{
  return array[location];
}


float CircularBuffer::mean()
{
  float average = 0;
  for (int i = 0; i < num_elements; i++)
  {
    average += array[i];
  }
  return average / num_elements;
}

size_t CircularBuffer::numElements()
{
  return num_elements;
}

float CircularBuffer::maxVal()
{
  float maximum;
  for (int i = 0; i < num_elements; i++)
  {
    if (i == 0)
    {
      maximum = array[0];
    }
    else if (array[i] > maximum)
    {
      maximum =  array[i];
    }
  }
  return maximum;
}
float CircularBuffer::minVal()
{
  float minimum;
  for (int i = 0; i < num_elements; i++)
  {
    if (i == 0)
    {
      minimum = array[0];
    }
    else if (array[i] < minimum)
    {
      minimum =  array[i];
    }
  }
  return minimum;
}
float CircularBuffer::sigma()
{
  float average = mean();
  float sigma = 0;
  for(int i = 0; i < min(size, num_elements) ; i++)
  {
    sigma += ((array[i] - average) * (array[i] - average));
  }
  return sqrt(sigma / min(size, num_elements));
}

NOT TESTED!!!

You can also use a rolling average. With that, you don’t have to store all the values.

You should change the thread title to something more descriptive than “math”. Something like “Average of ten values” would get more attention.

How do I use a rolling average?

chessy:
How do I use a rolling average?

You simply post another question on the forum.

BulldogLowell:
You simply post another question on the forum.

Irony? :neutral_face:

lg, couka

chessy:
Hi all,

How could I program the Arduino to take the average of the last ten values of a volatile variable and set and LED to high when the average decreases significantly from the last one?

If you have control over the ISR (interrupt service routine) that updates the variable, which in turn is the reason it is volatile, then this is easy. If not, your code has to periodically read out the variable for changes.

In both cases, using a circular buffer seems like the right choice.

What are you trying to do? Implement something statistically important, or just smooth out some bumpiness?

rollingAverage = rollingAverage9/10 + newestValue1/10;

will smooth the data approximately the same amount as taking the average of the last n samples, with a lot less overhead.

Trying to smooth out bumpiness could help, but I am trying to warn the user of a term which differs significantly from previous values.

chessy:
Trying to smooth out bumpiness could help, but I am trying to warn the user of a term which differs significantly from previous values.

That term wouldn't be the "constantly changing variable" that you reference in this thread would it? Because that would be yet another cross post.

But it can be done. With:

if (abs(newestValue - rollingAverage) > someThreshold)
{ do something }

chessy:
Trying to smooth out bumpiness could help, but I am trying to warn the user of a term which differs significantly from previous values.

Take two rolling averages with different weightings.

float a1;
float a2;

loop() {
val = read_the_val();
a1 = a1*.99 + val*.01;
a2 = a2*.75 + val*.25;
}

A sudden change will show up as a difference between the two values. For better stability, take samples at known intervals.