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)
blh64
December 9, 2021, 3:48pm
2
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
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.
miki130504:
dt = millis() - t1;
'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();
}
gcjr
December 9, 2021, 5:10pm
7
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
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.
gcjr
December 9, 2021, 6:07pm
10
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.
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.
gcjr
December 10, 2021, 6:07pm
16
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.