Mean value of the last 10 measurements

Hello,

I doing a mean value of the last 10 measurements.
But I'm not sure about the variable type/conversion ? Because it a mix between long uint8_t, unint16_t
Questions are in the code

const uint8_t N_LUM_EXT_SAMPLES = 10;  //number of samples for average
unsigned long lumExtSample[N_LUM_EXT_SAMPLES + 1]; //[N_LUM_EXT_SAMPLES + 1] or [N_LUM_EXT_SAMPLES] ????
uint8_t lumExtIndex = 0;


void setup()
{
  for (uint8_t i = 0; i < N_LUM_EXT_SAMPLES; i++)
  {
    lumExtSample[i] = 0;  //init table to 0
  }
}

void loop()
{
  lumExtSample[lumExtIndex] = lumExt;  //put new value in table. lumExt is unit16_t and is comming from a sensor (from 0 to 35000).
  lumExtIndex++;
  if ( lumExtIndex >= N_LUM_EXT_SAMPLES ) //end of table ? 
  {
    lumExtIndex = 0;
  }

  for ( uint8_t i = 0 ; i < N_LUM_EXT_SAMPLES ; i++ ) //calculate mean value
  {
    lumExtAvg += lumExtSample[i];  //sum all elements
  }

  lumExtAvg /= N_LUM_EXT_SAMPLES;  //sum / n, or lumExtAvg /= uint16_t (N_LUM_EXT_SAMPLES);  ???

  Serial.print(" lumExtAvg: "); Serial.print(lumExtAvg);
}

Thanks

if you want N_LUM_EXT_SAMPLES samples, the array is unsigned long lumExtSample[N_LUM_EXT_SAMPLES]; the index will go from 0 to N_LUM_EXT_SAMPLES-1

Yes this is correct, if after incrementing the value you are at the sizeif ( lumExtIndex >= N_LUM_EXT_SAMPLES )it means the table is full. So you might want to calculate the average within this if condition, not after

if lumExt is an unit16_t, then so should be the array that keeps the history.

the summation f 10 values might exceed what unit16_t can represent, so your lumExtAvg needs to be larger, for example an unit32_t will do

because those are integers, when you'll do the division by N_LUM_EXT_SAMPLES you'll get an integer (eg 133 /10 = 13 not 13.3). if you are fine with this rounding down then you can keep things that way. If you want do perform the math in floating point, you'll need to promote (cast) the numbers into float or double during the division

//calculate mean value
uint32_t lumSum = 0;
for ( uint8_t i = 0 ; i < N_LUM_EXT_SAMPLES ; i++ )
    lumSum += lumExtSample[i];  //sum all elements  
Serial.print(" average : "); 
Serial.print( ((double) lumSum) / N_LUM_EXT_SAMPLES, 3); // print with 3 digits after decimal point

Thanks a lot J-M-L
I would like 10 samples only.

For the calculation, it's not necessary to make a format conversion ? We can do lumExtAvg /= N_LUM_EXT_SAMPLES) and not lumExtAvg /= uint16_t (N_LUM_EXT_SAMPLES);

Now am I right ?

const uint8_t N_LUM_EXT_SAMPLES = 10;  //number of sample for average

unsigned long lumExtAvg;
unsigned long lumExtSample[N_LUM_EXT_SAMPLES]; 
uint8_t lumExtIndex = 0;



void setup()
{
  for (uint8_t i = 0; i < N_LUM_EXT_SAMPLES; i++)
  {
    lumExtSample[i] = 0;  //init table to 0
  }
}

void loop()
{
  lumExtSample[lumExtIndex] = lumExt;  //put new value in table. lumExt is unit16_t and is comming from a sensor (from 0 to 35000).
  lumExtIndex++;


  for ( uint8_t i = 0 ; i < N_LUM_EXT_SAMPLES ; i++ ) //calculate mean value
  {
    lumExtAvg += lumExtSample[i];  //sum all elements
  }
  lumExtAvg /= N_LUM_EXT_SAMPLES;  
  

  if ( lumExtIndex >= N_LUM_EXT_SAMPLES )
  {
    lumExtIndex = 0;
  }

  Serial.print(" lumExtAvg: "); Serial.print(lumExtAvg);
}

Thanks

as lumExtAvg is already a uint32_t when you perform an operation like + or - or / or * with a smaller unsigned type, the smaller one is transformed into the larger type. So this is fine lumExtAvg /= N_LUM_EXT_SAMPLES;as long as lumExtAvg is an uint32_t

(if it's an uint16_t you risk overflow when you perform the sum)

Should you be calculating the average before you have ten readings?

This is my approach, avoid having to iterate over the buffer on every sample, just maintain
a running total the smart way:

#define N 10
byte index = 0;
uint16_t history[N];
uint32_t total = 0 ;

void add_sample (uint16_t sample)
{
  uint16_t previous = history[index] ;  // get oldest from buffer
  history[index] = sample ;  // insert newest
  index ++ ;    // move pointer circularly
  if (index >= N)
    index = 0 ;

  total -= previous ;  // update the cached sum of N latest samples to reflect
  total += sample ;    // the outgoing and incoming values
}

float latest_mean()
{
  return float (total) / N ;  // current average
}


void loop()  // keep loop() clean and simple!
{
  uint16_t sample = read_sensor() ;
  add_sample (sample) ;
  Serial.print (" lumExtAvg: ") ; Serial.print (latest_mean()) ;
}

Note the separation out into separate functions, so loop() isnt cluttered, and so that
you don't need to touch floats unless actually want to see the mean.

This technique of using a circular buffer is powerful, well worth adding to your
programming toolkit.

Perfect!!! Thanks to all for your advices and helps !!!

You can also perform smoothing with a digital filter. It behaves slightly differently but it performs very similarly if smoothing is all you are doing. The advantage is that you don't have to store a number of measurements and putz around with circular buffers to do it.

unsigned int smooth(unsigned int newVal) {
  static unsigned int oldVal = 0;
  unsigned long sum;
    //  optimize sum = (oldVal * 3 + newVal) / 4;
    sum = ( (oldVal << 1) + oldVal + newVal) >> 2;
  }
  oldVal = sum;
  return oldVal;
}
...
  // example use
  smoothedValue = smooth( analogRead(A0) );

...

One thing I didn't see mentioned: You forgot to set 'lumExtAvg' to zero before you added the 10 samples to it. You were using the previous average as an 11th sample. After dividing 11 samples by 10 your results would be about 10% too high.

johnwasser:
One thing I didn't see mentioned: You forgot to set 'lumExtAvg' to zero before you added the 10 samples to it. You were using the previous average as an 11th sample. After dividing 11 samples by 10 your results would be about 10% too high.

You are right johnwasser! thanks!

aarg:
// optimize sum = (oldVal * 3 + newVal) / 4;
sum = ( (oldVal << 1) + oldVal + newVal) >> 2;

Nice this digital filter. I think it was what I wanted to do.

I just saw that it is very smooth.
Maybe it could be more "nervous like that ? *:

sum = ( (oldVal *2)  + newVal) / 3;

Got a library for that..

#include <runningAvg.h> // The #include for the code.

runningAvg smoother(10); // How to create one. This does 10 data items.

float average = smoother.addData(newDataValue); // Use it by adding data values to it. Add value, get average.

If you want to try it, install LC_baseTools from your friendly IDE library manager.. bla bla bla..

Good luck!

-jim lee

aarg:
You can also perform smoothing with a digital filter. It behaves slightly differently but it performs very similarly if smoothing is all you are doing.

The moving average is also a digital filter as it happens, a finite impulse response filter.
The way I describe for calculating it more quickly corresponds to some simple algebra
on its response to express it as a combination of two simpler filter stages.

The first-order exponential filter you describe is about the simplest infinite impulse digital filter.

potzli:
Nice this digital filter. I think it was what I wanted to do.

I just saw that it is very smooth.
Maybe it could be more "nervous like that ? *:

sum = ( (oldVal *2)  + newVal) / 3;

You can express it to avoid having two separate constants:

oldVal += (newVal-oldVal)/3 ;

Then you only have one parameter (here 3) to set the response and no chance to get it wrong.

I tried the digital filter and works fine. I would like to use it for other input values
The code is now:

#include <stdio.h>

unsigned int valSmooth1, valSmooth2;
unsigned int oldVal1, oldVal2;
unsigned int value1 =100;
unsigned int value2 =2000;

unsigned int smooth(unsigned int newVal, unsigned int oldVal )
{
  unsigned long sum;

  sum = ( (oldVal << 1) + oldVal + newVal) >> 2; //sum = (oldVal * 3 + newVal) / 4
  oldVal = sum;
  return oldVal;
}



void loop()
{
    valSmooth1 = smooth(value1,oldVal1);
    oldVal1 = valSmooth1;
    valSmooth2 = smooth(value2,oldVal2);
    oldVal2 = valSmooth2;
    printf("valSmooth1: %d", valSmooth1);
    printf("\tvalSmooth2: %d", valSmooth2);
    return 0;
}

But it's needed to store old value after calling smooth.
Is it a nicest way/clearer to do that ?
Thanks

You could use an object oriented approach.

Here's a very similar example using the EMA class of the Arduino Filters library I maintain. The template argument <2> is the number of bits to shift by, i.e. the >> 2 in your code.

#include <Filters.h> // https://github.com/tttapa/Arduino-Filters
#include <AH/Filters/EMA.hpp>

// Digital exponential moving average filters with poles in 1 - 2⁻²:
EMA<2> smooth1;
EMA<2> smooth2;

// Make sure that the filter supports the input range you need:
static_assert(smooth1.supports_range(0u, 1023u),
              "Integer overflow, use wider integer types for filter");

void setup() {
  // Optional: reset the filters to the current value of the analog
  // input to prevent initial transients:
  smooth1.reset(analogRead(A0));
  smooth2.reset(analogRead(A1));
}

void loop() {
  unsigned int value1 = analogRead(A0);
  unsigned int value2 = analogRead(A1);
  // Feed new data to the filter and get the filtered output:
  unsigned int valSmooth1 = smooth1(value1);
  unsigned int valSmooth2 = smooth2(value2);
  Serial.print("valSmooth1: "), Serial.print(valSmooth1);
  Serial.print("\tvalSmooth2: "), Serial.println(valSmooth2);
}

You can read more about the mathematics and the implementation here: https://tttapa.github.io/Pages/Mathematics/Systems-and-Control-Theory/Digital-filters/Exponential Moving Average
The documentation for the EMA class can be found here: Arduino Filters: EMA< K, input_t, state_t > Class Template Reference

Pieter

Thanks Pieter!!
You library is fine and goes much further that my use. Thanks for this nice work

But my question was also to learn something new; how to have a "more efficient/logical " code !

With a moving average of 10 values, I think there's an opportunity to gain an order of magnitude of precision and accuracy. Verify this small function with a calculator ... it will be remain accurate as you enter each data point.

void setup() {
  Serial.begin(9600);
  for (int i = 0; i < 10; i++) { // clear
    movingAvg(0);
  }
  Serial.println(movingAvg(1), 1);
  Serial.println(movingAvg(2), 1);
  Serial.println(movingAvg(2), 1);
  Serial.println(movingAvg(2), 1);
  Serial.println(movingAvg(2), 1);
  Serial.println(movingAvg(2), 1);
  Serial.println(movingAvg(2), 1);
  Serial.println(movingAvg(2), 1);
  Serial.println(movingAvg(2), 1);
  Serial.println(movingAvg(2), 1);
  Serial.println(movingAvg(2), 1);
  Serial.println(movingAvg(2), 1);
}

void loop() {
}

float movingAvg(uint16_t dat) {
  static uint16_t arrDat[10];
  static uint32_t sum;
  static int pos;
  pos++;
  if (pos >= 10) pos = 0;
  sum = sum - arrDat[pos] + dat;
  arrDat[pos] = dat;
  return (float)sum / 10.0;
}

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.