Exponential Moving Average

February 13, 20132 comments Coded in C

The exponential moving average is a type of IIR filter that is easy to implement in C and uses minimal resources.  Unlike a simple moving average, it does not require a RAM buffer to store previous samples.  It just has to store one value (the previous average).  

An exponential moving average is expressed as the following equation:  avg[n] = (in * alpha) + avg[n-1]*(1-alpha).  Implementing this equation using floating point math is straightforward but using fixed point variables is a little tricky.  The code snippet here use 32-bit signed integers for the average and input values.  Intermediate values need to use 64-bit math to avoid overflow errors.

Alpha values close to zero represent heavy averaging while an alpha value of one has no averaging.

//This macros defines an alpha value between 0 and 1
#define DSP_EMA_I32_ALPHA(x) ( (uint16_t)(x * 65535) )
 
int32_t dsp_ema_i32(int32_t in, int32_t average, uint16_t alpha){
  int64_t tmp0; //calcs must be done in 64-bit math to avoid overflow
  tmp0 = (int64_t)in * (alpha) + (int64_t)average * (65536 - alpha);
  return (int32_t)((tmp0 + 32768) / 65536); //scale back to 32-bit (with rounding)
}

//here is a function that uses the averaging code
int32_t my_avg_func(void){
     static int32_t average = 0;
     int32_t adc_value;    
     adc_value = read_the_adc_value();
     average = dsp_ema_i32(adc_value, average, DSP_EMA_I32_ALPHA(0.1));
     return average;
}

Comments:

Gary Beckmann
Said:
On the line where temp0 is calculated, i believe the end of the line should read
(65535 - alpha)
Otherwise an alpha of 1 would improperly include the previous average as well as the new value.
3 years ago
0
Reply
Sorry, you need javascript enabled to post any comments.
erik
Said:
Unfortunately, the shown code does has two major bugs, due to the way the average is stored as an integer.

To see this, lets choose alpha to be 1024.

We start with adc_value = 0, then dsp_ema_i32 will return 0 as expected.

Then raise adc_value to 1. tmp0 in dsp_ema_i32 will be:
tmp0 = (int64_t)1 * (1024) + (int64_t)0 * (65536 - 1024)
= 1024 + 0 * 64512
= 1024
so the returned value is:
(int32_t)((tmp0 + 32768) / 65536) = (1024 + 32768) / 65536
= 33792 / 65536
= 0

So dsp_ema_i32 will keep on returning 0, while it should (after long enough filtering time) in the end return 1.

The code effectively implements a filter with a "dead zone", not changing until the input differs from the average by 32768/alpha or more, or differs by -(32768/alpha) or less.

Following the above example, raise adc_value to 31 (which is less than 32768/alpha). tmp0 in dsp_ema_i32 will be:
tmp0 = (int64_t)31 * (1024) + (int64_t)0 * (65536 - 1024)
= 31744 + 0 * 64512
= 31744
so the returned value is:
(int32_t)((tmp0 + 32768) / 65536) = (31744 + 32768) / 65536
= 64512 / 65536
= 0

So dsp_ema_i32 will keep on returning 0.

When raising adc_value to 32 instead, tmp0 in dsp_ema_i32 will be:
tmp0 = (int64_t)32 * (1024) + (int64_t)0 * (65536 - 1024)
= 32768 + 0 * 64512
= 32768
so the returned value is:
(int32_t)((tmp0 + 32768) / 65536) = (32768 + 32768) / 65536
= 65536 / 65536
= 1

So at least the average is moving toward the input value by 1. That is good.

But then:
tmp0 = (int64_t)32 * (1024) + (int64_t)1 * (65536 - 1024)
= 32768 + 1 * 64512
= 97280
so the returned value is:
(int32_t)((tmp0 + 97280) / 65536) = (97280 + 32768) / 65536
= 130048 / 65536
= 1

So dsp_ema_i32 will keep on returning 1, never reaching the input value of 32. Not good.

The second bug is the integer division (tmp0 + 32768) / 65536 . In C / C++, integer division will round toward 0, so in that situation, the dead zone is even larger.

Much better (and much simpler) is the algorithm as shown by david.prentice on http://www.avrfreaks.net/comment/824765#comment-824765 :

long total = 0;
int average = 0;
int N = 0; // working number of samples
...
total += ADCW; // add to running total
if (N >= MAX_SAMPLES) // enough samples ?
total -= average; // remove one
else N++; //
average = total / N; // integer

1 year ago
0
Reply
Sorry, you need javascript enabled to post any comments.
Sorry, you need javascript enabled to post any comments.