Go Down

### Topic: Heart Rate Monitor (Read 10078 times)previous topic - next topic

#### Francis Poisson

##### Mar 13, 2009, 12:35 am
Hi everyone,
I'm doing my first project with Arduino, I am doing a heart rate monitor on 7 segments display and I'd like some tips about the code I have.

#### Francis Poisson

#1
##### Mar 13, 2009, 12:36 amLast Edit: Mar 13, 2009, 12:41 am by fpoisson Reason: 1
First, I am receiving the signal from a Polar chest belt with the Rick Moll's circuit (http://rick.mollprojects.com/hrm/)

** I will soon replace it with the Polar Heart Rate module from Sparkfun: http://www.sparkfun.com/commerce/product_info.php?products_id=8660.

I  put the signal on Port 2 of Arduino to use an interrupt in my code (which is at the bottom of this topic).

I, then, use port 3 to port 6 to send the BCD code to the the 7 segments and port 7 and 8 to choose which segment I use.
Here is my first version of the schematic:
http://3.bp.blogspot.com/_E0659_W7LV4/SbmWuarmQwI/AAAAAAAAPd4/i4JX3dA1UTQ/s1600-h/HRM_0000.jpg

Here is my code, I would like to know a better way to calculate the heart rate. Now I am doing an average of some value than i do it again and again. The beat per minute showing on my watch is varying slower than what I have on the 7-segments and changes in the beat per minute are less important. How could I improve this? Thank you for your help.

Code: [Select]
`volatile int moyennefrequence, D3 = 3, D4 = 4, D5 =5, D6 = 6, D7 = 7, D8 = 8;volatile int state = 0, lastTime, thisTime; void setup(){  pinMode(D3, OUTPUT);                                   //P3 to P8 as output  pinMode(D4, OUTPUT);  pinMode(D5, OUTPUT);  pinMode(D6, OUTPUT);  pinMode(D7, OUTPUT);  pinMode(D8, OUTPUT);  attachInterrupt(0, pulse, CHANGE);                     //call pulse function when an interrupt is detected on Port 2;  }void loop(){  static int i = 0, m, fc[5], waitTime, sommefrequence = 0, frequence;  long DEAD_TIME     = 2000;    if (state == 2) {                                    //if 2nd pulse detected    frequence = (60000 / (thisTime-lastTime));         //calculate bpm using time between the two pulses      if ((frequence >= 40) && (frequence <= 220)) {   //if bpm is between 40 and 220        fc[i] = frequence;                             //add bpm at the position "i" in the array        i++;                                           //change array position    }    }    waitTime = millis()-lastTime;                        //Time since no pulses were detected  if (waitTime > DEAD_TIME) {                          //if too long    moyennefrequence = 0;                              //bpm = 0;  }  if (i == 4) {                                        //if array is full    for(m = 0; m < 5; m++) {    sommefrequence = fc[m] + sommefrequence;           //addition of the bpm in the array    }  moyennefrequence = (sommefrequence / 4);             //calculate average  i = 0;                                               //reset array position  sommefrequence = 0;                                  //reset addition result }  ssegments();                                         //call ssegments function                                  }void pulse(){  if (state == 2) {                                   //if second pulse is detected    lastTime = thisTime;                              //the time of the first pulse is put in the lastTime variable    state = 1;                                        //reset pulse counter at 1  }  if (state < 2) {                                    //if less than 2 pulse has been detected    thisTime = millis();                              //put the current time in thisTime variable    state = state++;                                  //increment pulse counter  }}void ssegments() {    int unite, dizaine, centaine;  unite = (moyennefrequence % 10);                  //put the first number of the decimal value of bpm in unite variable  dizaine = (((moyennefrequence % 100) - unite)/10);//put the second number of the decimal value of bpm in dizaine variable  centaine = (((moyennefrequence % 1000) - dizaine - unite)/100);//put the third number of the decimal value of bpm in centaine variable      digitalWrite(D7, LOW);  //address of first number  digitalWrite(D8, HIGH);  chiffre(unite);         //send unite to funtion chiffre to be displayed  delay(1);  digitalWrite(D7, HIGH);//address of second number  digitalWrite(D8, LOW);  chiffre(dizaine);      //send dizaine to funtion chiffre to be displayed  delay(1);  digitalWrite(D7, HIGH);//address of third number  digitalWrite(D8, HIGH);  chiffre(centaine);     //send centaine to funtion chiffre to be displayed  delay(1);  }void chiffre(int i) {  //send bcd value of decimal number to ports D3 to D6  switch(i){    case 1:      digitalWrite(D6, HIGH);      digitalWrite(D3, LOW);      digitalWrite(D4, LOW);      digitalWrite(D5, LOW);      break;    case 2:      digitalWrite(D6, LOW);      digitalWrite(D3, HIGH);      digitalWrite(D4, LOW);      digitalWrite(D5, LOW);      break;    case 3:      digitalWrite(D6, HIGH);      digitalWrite(D3, HIGH);      digitalWrite(D4, LOW);      digitalWrite(D5, LOW);      break;    case 4:      digitalWrite(D6, LOW);      digitalWrite(D3, LOW);      digitalWrite(D4, HIGH);      digitalWrite(D5, LOW);      break;    case 5:      digitalWrite(D6, HIGH);      digitalWrite(D3, LOW);      digitalWrite(D4, HIGH);      digitalWrite(D5, LOW);      break;    case 6:      digitalWrite(D6, LOW);      digitalWrite(D3, HIGH);      digitalWrite(D4, HIGH);      digitalWrite(D5, LOW);      break;    case 7:      digitalWrite(D6, HIGH);      digitalWrite(D3, HIGH);      digitalWrite(D4, HIGH);      digitalWrite(D5, LOW);      break;    case 8:      digitalWrite(D6, LOW);      digitalWrite(D3, LOW);      digitalWrite(D4, LOW);      digitalWrite(D5, HIGH);      break;    case 9:      digitalWrite(D6, HIGH);      digitalWrite(D3, LOW);      digitalWrite(D4, LOW);      digitalWrite(D5, HIGH);      break;    case 0:      digitalWrite(D6, LOW);      digitalWrite(D3, LOW);      digitalWrite(D4, LOW);      digitalWrite(D5, LOW);      break;   }}`

Sorry about my english, I usually speak french.

#### Francis Poisson

#2
##### Mar 13, 2009, 04:19 amLast Edit: Mar 13, 2009, 04:20 am by fpoisson Reason: 1
I have change the code a little bit. Instead of detecting the interrupt on changes, I detect it on rising front, this is more accurate because I calculate the time between two rising fronts instead of one falling front and one rising front.

Although, the arduino is not seeming to always see it, so the heart rate takes times to refresh. I will try to see on the scope tomorrow if my signal is taking all the amplitude from 5V to 0V when there is a pulse. I will probably play with the peak detector capacitor. I think I will enjoy the polar module from sparkfun...

Here is my new code:
Code: [Select]
`volatile int fc, D3 = 3, D4 = 4, D5 =5, D6 = 6, D7 = 7, D8 = 8, state = 0, lastTime, thisTime;void setup(){  pinMode(D3, OUTPUT);                                   //P3 to P8 as output  pinMode(D4, OUTPUT);  pinMode(D5, OUTPUT);  pinMode(D6, OUTPUT);  pinMode(D7, OUTPUT);  pinMode(D8, OUTPUT);  attachInterrupt(0, pulse, RISING);                     //call pulse function when an interrupt is detected on Port 2;  }void loop(){  int frequence;  if (state == 2) {    frequence = (60000 / (thisTime-lastTime));         //calculate bpm using time between the two pulses      if ((frequence >= 40) && (frequence <= 220)) {   //if bpm is between 40 and 220        fc = frequence;      }  }  ssegments();                                         //call ssegments function                                  }void pulse(){  switch(state) {    case 0:    case 1:      thisTime = millis();      state++;      break;    case 2:      lastTime = thisTime;      state = 0;      break;  }}void ssegments() {    int unite, dizaine, centaine;  unite = (fc % 10);                  //put the first number of the decimal value of bpm in unite variable  dizaine = (((fc % 100) - unite)/10);//put the second number of the decimal value of bpm in dizaine variable  centaine = (((fc % 1000) - dizaine - unite)/100);//put the third number of the decimal value of bpm in centaine variable      digitalWrite(D7, LOW);  //address of first number  digitalWrite(D8, HIGH);  chiffre(unite);         //send unite to funtion chiffre to be displayed  delay(1);  digitalWrite(D7, HIGH);//address of second number  digitalWrite(D8, LOW);  chiffre(dizaine);      //send dizaine to funtion chiffre to be displayed  delay(1);  digitalWrite(D7, HIGH);//address of third number  digitalWrite(D8, HIGH);  chiffre(centaine);     //send centaine to funtion chiffre to be displayed  delay(1);  }void chiffre(int i) {  //send bcd value of decimal number to ports D3 to D6  switch(i){    case 1:      digitalWrite(D6, HIGH);      digitalWrite(D3, LOW);      digitalWrite(D4, LOW);      digitalWrite(D5, LOW);      break;    case 2:      digitalWrite(D6, LOW);      digitalWrite(D3, HIGH);      digitalWrite(D4, LOW);      digitalWrite(D5, LOW);      break;    case 3:      digitalWrite(D6, HIGH);      digitalWrite(D3, HIGH);      digitalWrite(D4, LOW);      digitalWrite(D5, LOW);      break;    case 4:      digitalWrite(D6, LOW);      digitalWrite(D3, LOW);      digitalWrite(D4, HIGH);      digitalWrite(D5, LOW);      break;    case 5:      digitalWrite(D6, HIGH);      digitalWrite(D3, LOW);      digitalWrite(D4, HIGH);      digitalWrite(D5, LOW);      break;    case 6:      digitalWrite(D6, LOW);      digitalWrite(D3, HIGH);      digitalWrite(D4, HIGH);      digitalWrite(D5, LOW);      break;    case 7:      digitalWrite(D6, HIGH);      digitalWrite(D3, HIGH);      digitalWrite(D4, HIGH);      digitalWrite(D5, LOW);      break;    case 8:      digitalWrite(D6, LOW);      digitalWrite(D3, LOW);      digitalWrite(D4, LOW);      digitalWrite(D5, HIGH);      break;    case 9:      digitalWrite(D6, HIGH);      digitalWrite(D3, LOW);      digitalWrite(D4, LOW);      digitalWrite(D5, HIGH);      break;    case 0:      digitalWrite(D6, LOW);      digitalWrite(D3, LOW);      digitalWrite(D4, LOW);      digitalWrite(D5, LOW);      break;   }}`

#3
Very nice!

#### mem

#4
##### Mar 13, 2009, 08:27 pm
Francis, I have not run this code but here are some ideas for simplifying your sketch

Code: [Select]
`int  D3 = 3, D4 = 4, D5 =5, D6 = 6, D7 = 7, D8 = 8;volatile unsigned long beats = 0, lastTime, period;void setup(){  pinMode(D3, OUTPUT);                                   //P3 to P8 as output  pinMode(D4, OUTPUT);  pinMode(D5, OUTPUT);  pinMode(D6, OUTPUT);  pinMode(D7, OUTPUT);  pinMode(D8, OUTPUT);  attachInterrupt(0, pulse, RISING);                     //call pulse function when an interrupt is detected on Port 2;  }void loop(){  static int fc;  int frequence;  if (beats > 1) {    frequence = (60000 / period);         //calculate bpm using time between the two pulses    if ((frequence >= 40) && (frequence <= 220)) {   //if bpm is between 40 and 220      fc = frequence;    }  }  ssegments(fc);                                         //call ssegments function                                  }void pulse(){  if (beats > 0){    period =  millis() - lastTime;    beats++;  }  lastTime = millis();}void ssegments(int fc) {  // rest of code is unchanged}`

#### Francis Poisson

#5
##### Mar 13, 2009, 10:35 pm
When do you reset beats to 0?

#### mem

#6
##### Mar 13, 2009, 11:19 pmLast Edit: Mar 13, 2009, 11:23 pm by mem Reason: 1
beats maintins a count of the number of heartbeats, keeps counting for as along as your heart is beating.  the code only wants to know that at least two beats have occured so it can caluclate the period since the last beat, so you could replace it with a flag if you wanted to.

period is the variable holding the duration of the last heartbeat. The sketch should really disable interrupts when this value is used.

as I said, the code above is off the top of my head, but I hope it gives you some ideas.

#### Francis Poisson

#7
##### Mar 14, 2009, 01:13 am
Thank you for this nice idea! I've try it but it does not seem to work, although some things were not working properly, I made somage changes to make it theorically working but it doesn't, here it is:

Code: [Select]
`int  D3 = 3, D4 = 4, D5 =5, D6 = 6, D7 = 7, D8 = 8;volatile unsigned long beats = 0, lastTime, period;void setup(){  pinMode(D3, OUTPUT);                                   //P3 to P8 as output  pinMode(D4, OUTPUT);  pinMode(D5, OUTPUT);  pinMode(D6, OUTPUT);  pinMode(D7, OUTPUT);  pinMode(D8, OUTPUT);  attachInterrupt(0, pulse, FALLING);                     //call pulse function when an interrupt is detected on Port 2;  Serial.begin(9600);}void loop(){  static int fc;  int frequence;  if (beats > 1) {    Serial.println(period, DEC);    frequence = (60000 / period);         //calculate bpm using time between the two pulses    if ((frequence >= 40) && (frequence <= 220)) {   //if bpm is between 40 and 220      fc = frequence;    }  }  ssegment(fc);                                         //call ssegments function}void pulse(){  if (beats > 0){    period =  millis() - lastTime;    beats = 0;  }  lastTime = millis();  beats++;  }}void ssegment(int fc) {  `

#### mem

#8
##### Mar 14, 2009, 01:24 amLast Edit: Mar 14, 2009, 01:45 am by mem Reason: 1
Why are you resetting beats to 0 in the interrupt handler.  Does the heartbeat monitor generate two pulses per heartbeat?

edit:
I looked at some information on the hardware you are using, it seems it sends an analog output. I am not sure if you will get reliable triggering of the interrupt from this. I noticed some sample code that monitors the output using the equivalent of analogRead and you could do something like that, or you could condition the signal using a Schmitt trigger or similar. But the Sparkfun part looks like it outputs a 1ms pulse so it may not be worth the trouble trying to get the analog signal working. But if you do want to, you can read more about Schmitt triggers here: http://en.wikipedia.org/wiki/Schmitt_trigger

#### Francis Poisson

#9
##### Mar 14, 2009, 02:02 am
that's right, I should not reset it to 0, I just understand.

The code seems to work now when I detect the interrupt on "CHANGE" instead of "FALLING" but this would probably change if I used a schmitt trigger or the polar module from sparkfun that I've just ordered.

Code: [Select]
`int  D3 = 3, D4 = 4, D5 =5, D6 = 6, D7 = 7, D8 = 8;volatile unsigned long beats = 0, lastTime, period;void setup(){  pinMode(D3, OUTPUT);                                   //P3 to P8 as output  pinMode(D4, OUTPUT);  pinMode(D5, OUTPUT);  pinMode(D6, OUTPUT);  pinMode(D7, OUTPUT);  pinMode(D8, OUTPUT);  attachInterrupt(0, pulse, FALLING);                     //call pulse function when an interrupt is detected on Port 2;  Serial.begin(9600);}void loop(){  static int fc;  int frequence;  if (beats > 1) {    Serial.println(period, DEC);    frequence = (60000 / period);         //calculate bpm using time between the two pulses    if ((frequence >= 40) && (frequence <= 220)) {   //if bpm is between 40 and 220      fc = frequence;    }  }  ssegment(fc);                                         //call ssegments function}void pulse(){  if (beats > 0){    period =  millis() - lastTime;  }  lastTime = millis();  beats++;}void ssegment(int fc) { `

#### mem

#10
##### Mar 14, 2009, 02:15 amLast Edit: Mar 14, 2009, 02:16 am by mem Reason: 1
I would think that "CHANGE" would give different values every other pulse unless the on time of the pulse are the same as the off times.

You may want to see if you get better readings if you only use the odd or even beats.
In loop you could do something like this to have a look:

Code: [Select]
`   if( beats % 2 == 0 )  // only display if even// if( beats % 2 != 0 ) // only display if odd   <- comment above and uncomment this to see odd beats       ssegments(fc);                 //call ssegments function      `

#### Francis Poisson

#11
##### Mar 14, 2009, 02:26 amLast Edit: Mar 14, 2009, 02:28 am by fpoisson Reason: 1
No changes on reading. That's not that bad, it is showing the value I have on my watch +/- 10 but sometimes there's a little peak at 140 for example, probably due to environment magnetic noises. The bandpass I am using right now is only of first order with a high gain, so it is sensisite to the environment. The module will probably get better results.

Do you think it would be better to count the time between 3 beats for example and do the average? I would probably get a more stable  output. I don't need the value that is showing to resfresh every beat.

#### mem

#12
##### Mar 14, 2009, 02:39 am
Yes, I would smooth the data over at least four beats. There is a tutorial  on smoothing if you need it here: http://www.arduino.cc/en/Tutorial/Smoothing

#### Francis Poisson

#13
##### Apr 01, 2009, 11:07 pm
I have finally receive the polar heart rate module and I have updated my code. With the module, it is working with coded and non coded belt transmitter. You can follow my project on my blog: (http://22wire.blogspot.com)

Go Up