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.
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:
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.
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. ![]()
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:
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;
}
}
Very nice!
Francis, I have not run this code but here are some ideas for simplifying your sketch
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
}
When do you reset beats to 0?
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.
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:
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) {
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: Schmitt trigger - Wikipedia
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.
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) {
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:
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
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.
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
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)