Problem with exponential moving average

Hello guys. I have to do a project for a school subject.
in this project i use an heartbeat sensor to get the heart pulses.
I managed to get the readings working, but i can't still make the moving average to do so. In fact in an older version of the project it does work, but in the one that i'm working on it right now it doesn't. It only says Nan when i'm printing the value on the serial console. Why? I think that something regarding the EMA calculation is wrong.
Thanks to everyone

#include <LiquidCrystal.h>
#define bInizio 8
double dt, t1;
int t_sample = 20;
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
bool premuto=false;
int milInizio;
long sum;
double avg = 0;
int campioni = 0;
int buttonState;
double alpha=0.75;
double oldValue;
double value;


void setup() {
  Serial.begin(9600);
  lcd.begin(16,2);
}

void misurazione()
{
  dt = millis() - t1;
  if (dt < t_sample) {
    sum += analogRead(A0);
    campioni++;
  } else {
    avg = sum / (float)campioni;
    value = alpha * oldValue + (1 - alpha) * avg;
    Serial.print (avg);
    Serial.print (",");
    Serial.println (value);
    oldValue=value;
    campioni = 0;
    sum = 0;
    t1 = millis();
    
  } 
}


void loop() {

  while(premuto==false)
  {
    lcd.setCursor(0, 0);
    lcd.write("Pronto");
    buttonState = digitalRead(bInizio);
    if (buttonState == HIGH)
    {
      premuto = true;
      milInizio = millis();
    
    }    
  }
  misurazione();
  
}

This is my code (sorry for the italian names)

The very first time you call misurazione(), t1 is zero and dt is probably greater than t_sample which means campioni is 0 and avg = 0/0

You should initialize t1 inside loop() when the button is pressed

1 Like

ok, i'll try now. Thanks

no, it doesn't work. Still the same.
Btw i can get a value in the readings but the problem seems to be only on the EMA calculation

Post the revised code in your next reply.

'dt' and 't1' should be 'unsigned long' and not 'float'.

This seems to be working for me. It produces the expected results for A0 connected to GND (0.00), 3.3V (661.3-ish) or 5V (1023.0).

const int bInizio = 8;
unsigned long dt, t1;
const unsigned t_sample = 500;
bool premuto = false;
int milInizio;
long sum;
double avg = 0;
int campioni = 0;
int buttonState;
const double alpha = 0.75;
double oldValue;
double value;


void setup()
{
  Serial.begin(115200);
  delay(200);
}

void misurazione()
{
  dt = millis() - t1;

  if (dt < t_sample)
  {
    sum += analogRead(A0);
    campioni++;
  }
  else
  {
    Serial.print (sum);
    Serial.print (",");
    Serial.print (campioni);
    
    avg = sum / (float)campioni;
    value = alpha * oldValue + (1 - alpha) * avg;

    Serial.print (",");
    Serial.print (avg);
    Serial.print (",");
    Serial.println (value);
    
    oldValue = value;
    campioni = 0;
    sum = 0;
    t1 = millis();
  }
}


void loop()
{
  misurazione();
}

i believe a moving avg is the following

avg += (sample - avg) / K;     // 1 < K

in your case

avg += (analogRead (A0) - avg) * (1-alpha);

in the above, avg will be 95+% of the input after 3 * K iterations, so it makes sense to perform an update periodically.

shouldn't the button pin be configured as INPUT_PULLUP, is connected between the pin and ground and is active when LOW?

It works, but only if t_sample is at 500. I need a faster reading. If i use 20 or 100 i get nan.

It works like it is :grin:

I'm using an exponential moving average. Btw it still doesn't work either.

Maybe i'm pushing into arduino's limits?

#include <LiquidCrystal.h>
#define bInizio 8
unsigned long dt,t1;
int t_sample = 500;
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
bool premuto=false;
int milInizio=0;

int r=10;
int g=6;
int b=9;
int battiti;
void setup() {
  Serial.begin(9600);
  lcd.begin(16,2);  
  pinMode(10, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(9, OUTPUT);
  delay(200);
}
float avg=0;
int campioni = 0;
int buttonState;
const double alpha=0.7;
double oldValue=0;
double value=0;
long sum=0;

void misurazione()
{
  dt = millis() - t1;
  if (dt < t_sample) {
    sum += analogRead(A0);
    campioni++;
  } else {
    avg = sum / (float)campioni;
    value = avg*oldValue + oldValue +(1-alpha)*avg;
    Serial.print (avg);
    Serial.print (" ");
    Serial.println (value);
    oldValue=value;
    campioni = 0;
    sum = 0;
    t1 = millis();
    
  } 
}

void led()
{
  if(battiti<70)
  {
    digitalWrite(b,255);
  }
  else if(battiti>=70&&battiti<=120)
  {
    digitalWrite(g,255);
  }
  else if(battiti>120)
  {
    digitalWrite(r,255);
  }
}
void loop() {

  while(premuto==false)
  {
    lcd.setCursor(0, 0);
    lcd.write("Pronto");
    buttonState = digitalRead(bInizio);
    if (buttonState == HIGH)
    {
      premuto = true;
      milInizio = millis();

    }    
  }
  if(millis()-milInizio>5000)
  {
    misurazione();
  }
  else if(millis()-milInizio<5000)
  {
    while(millis()-milInizio<5000)
    {
      lcd.clear();
      lcd.print("Avvio in corso");
      delay(500);
    }
    
  }
  else if(millis()-milInizio>25000)
  {
    if(battiti*3>50)
    {
      lcd.print("OK");
      lcd.setCursor(0,1);
      //lcd.print("Battiti: %i",battiti*6);
      led();
    }
    else
    {
      lcd.setCursor(0,1);
      lcd.print("Ripetere misura!");
    }
    
  }
  
  
}

I added some things to my code but the mesurament is always the same

That is because you did not follow the advice in post #2.

It is a good idea to actually read the replies to your post.

the code below produces the following results

1020  255.00
1020  446.25
1020  589.69
1020  697.27
1020  777.95
1020  838.46
1020  883.85
1020  917.88
1020  943.41
1020  962.56
1020  976.92
1020  987.69
1020  995.77
1020  1001.83
1020  1006.37
1020  1009.78

const byte PinBut  = A1;
const byte PinAnlg = A2;

unsigned long msecLst;
#define SampleMsec  20

int    sample;
double avg;
double K = 0.25;

void
loop (void)
{
    unsigned long msec = millis ();

    if (LOW == digitalRead (PinBut))  {
        if ( (msec - msecLst) > SampleMsec)  {
            msecLst = msec;

            sample = analogRead (PinAnlg);
            avg += (sample - avg) * K;

            Serial.print   (sample);
            Serial.print   ("  ");
            Serial.println (avg);
        }
    }
}

void
setup (void)
{
    Serial.begin (9600);

    pinMode (PinBut, INPUT_PULLUP);
}

I also answered to that post. The fact is that back then it was working even without t1 initialized inside loop() and also johnwassser's code works without it but only on a certain time interval
Btw i'm only a 17 yo so i cannot understand everything about coding and i still don't understand how to initialise it inside the loop (maybe i need to pass it as a parameter to misurazione()).

Thanks for the code. I'm gonna try it soon.

Looks like the problem is the initialization of 't1'. If the global is left at 0, the first calculation of (millis() - t1) can produce a number greater than 't_sample'. That causes 'sum / campioni' to divide by 0 and 'avg' becomes 'nan'. that causes 'value' to be 'nan' which then gets copied into 'oldValue' which causes every future calculation of 'value' to be 'nan'.

The fix is to put 't1 = millis()' at the end of setup(). That works for t_sample = 20 and even 10. Figure about 8.7 samples per millisecond.

As clearly explained in the very first reply to this thread.

1 Like

Thanks so much, it works!!!

Sorry guys, but i have another problem.
I wanted to do that if the avg is greater than the moving average, a led will turn on.
The problem is that when the led turns on, also the avg var increments by 30. Whenever the led turns on. Why does it happen?

const int bInizio = 8;
unsigned long dt, t1;
const unsigned t_sample = 20;
bool premuto = false;
int milInizio;
long sum;
double avg = 0;
int campioni = 0;
int buttonState;
const double alpha = 0.75;
double oldValue;
double value;
int batt;
bool battuto=true;


void setup()
{
  Serial.begin(9600);
  delay(200);
  pinMode(7, OUTPUT);
  t1 = millis();
}

void misurazione()
{
  dt = millis() - t1;

  if (dt < t_sample)
  {
    sum += analogRead(A0);
    campioni++;
  }
  else
  {
    
    
    avg = sum / (float)campioni;
    value = alpha * oldValue + (1 - alpha) * avg;
    
   
    Serial.print (avg);
    Serial.print (",");
    Serial.println (value);

    
    oldValue = value;
    campioni = 0;
    sum = 0;
    t1 = millis();
  }
}


void loop()
{
  misurazione();
  if(avg>value)
    {
      battuto=true;
      batt++;
      digitalWrite(7, HIGH);
    }
    else
    {
      digitalWrite(7, LOW);
    }
}

This is the code i added to make the sum of beats(it is just an example)

if(avg>value)
    {
      battuto=true;
      batt++;
      digitalWrite(7, HIGH);
    }
    else
    {
      digitalWrite(7, LOW);
    }

This is the graph with the code

I can't understand why this happens

Whitouth the code, the graph works as it should.

what are you measuring and how close is it to the wires of the LED? do they share a ground wire?

(so you code is combination of linearly average 30 msec of data and exponential averaging)?

If the additional load on the power drops the Analog Reference by 3% (146 mV), the analog readings will be 3% high. Use a good meter to measure the voltage at the AREF pin to see if it changes at all when the LED turns on.