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);
}
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
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);
}
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)
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.
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.
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.
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);
}
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.