Exact time between heart beats with MH-ET-LIVE-max30102 sensor

Hi! I'm trying to get the exact time in ms between heart beats with 2 ms. precision and I found the problem that, apparently, the checkForBeat() takes 20ms. for execution. So all beat times I get are multiples of 20 with no precision of 2ms.

If I do a Serial.print(millis()) with a void loop I can see this:

20:53:04.602 -> 8680
20:53:04.640 -> 8680
20:53:04.640 -> 8681
20:53:04.640 -> 8681
20:53:04.640 -> 8682
20:53:04.640 -> 8682
20:53:04.640 -> 8683
20:53:04.640 -> 8683
20:53:04.640 -> 8684
20:53:04.640 -> 8684
20:53:04.640 -> 8685
20:53:04.640 -> 8686
20:53:04.640 -> 8686
20:53:04.640 -> 8687
20:53:04.640 -> 8687
20:53:04.640 -> 8688

So I can deduce that the loop is taking 0,5 ms for its execution. But if I add the if (checkForBeat(irValue) == true) then the output is like this:

21:02:10.509 -> Outside millis: 34681
21:02:10.541 -> Outside millis: 34701
21:02:10.587 -> Outside millis: 34721
21:02:10.587 -> Outside millis: 34741
21:02:10.629 -> Outside millis: 34761
21:02:10.629 -> Outside millis: 34781
21:02:10.629 -> Outside millis: 34801
21:02:10.665 -> Outside millis: 34821
21:02:10.709 -> Outside millis: 34841
21:02:10.709 -> Outside millis: 34861
21:02:10.709 -> Outside millis: 34881
21:02:10.742 -> Outside millis: 34901
21:02:10.774 -> Outside millis: 34921
21:02:10.774 -> Outside millis: 34941
21:02:10.809 -> Outside millis: 34961
21:02:10.809 -> Outside millis: 34981
21:02:10.850 -> Outside millis: 35001
21:02:10.850 -> Outside millis: 35021
21:02:10.850 -> millis: 35023
21:02:10.884 -> Outside millis: 35041
21:02:10.930 -> Outside millis: 35061
21:02:10.930 -> Outside millis: 35081
21:02:10.930 -> Outside millis: 35101
21:02:10.970 -> Outside millis: 35121
21:02:10.970 -> Outside millis: 35141
21:02:11.007 -> Outside millis: 35161
21:02:11.038 -> Outside millis: 35181
21:02:11.038 -> Outside millis: 35201
21:02:11.070 -> Outside millis: 35221
21:02:11.070 -> Outside millis: 35241
21:02:11.111 -> Outside millis: 35261
21:02:11.111 -> Outside millis: 35281
21:02:11.148 -> Outside millis: 35301
21:02:11.191 -> Outside millis: 35321
21:02:11.191 -> Outside millis: 35341
21:02:11.191 -> Outside millis: 35361
21:02:11.224 -> Outside millis: 35381
21:02:11.271 -> Outside millis: 35401
21:02:11.271 -> Outside millis: 35421
21:02:11.311 -> Outside millis: 35441
21:02:11.311 -> Outside millis: 35461
21:02:11.311 -> Outside millis: 35481
21:02:11.350 -> Outside millis: 35501
21:02:11.391 -> Outside millis: 35521
21:02:11.391 -> Outside millis: 35541
21:02:11.391 -> Outside millis: 35561
21:02:11.427 -> Outside millis: 35581
21:02:11.471 -> Outside millis: 35601
21:02:11.471 -> Outside millis: 35621
21:02:11.471 -> Outside millis: 35641
21:02:11.506 -> Outside millis: 35661
21:02:11.551 -> Outside millis: 35681
21:02:11.551 -> Outside millis: 35701
21:02:11.551 -> Outside millis: 35721
21:02:11.583 -> Outside millis: 35741
21:02:11.630 -> Outside millis: 35761
21:02:11.630 -> Outside millis: 35781
21:02:11.672 -> Outside millis: 35801
21:02:11.672 -> Outside millis: 35821
21:02:11.672 -> Outside millis: 35841
21:02:11.707 -> Outside millis: 35861
21:02:11.752 -> Outside millis: 35881
21:02:11.752 -> Outside millis: 35901
21:02:11.752 -> millis: 35903
21:02:11.752 -> Outside millis: 35921
21:02:11.784 -> Outside millis: 35941
21:02:11.831 -> Outside millis: 35961
21:02:11.831 -> Outside millis: 35981
21:02:11.874 -> Outside millis: 36001
21:02:11.874 -> Outside millis: 36022
21:02:11.874 -> Outside millis: 36042
21:02:11.910 -> Outside millis: 36062
21:02:11.955 -> Outside millis: 36082
21:02:11.955 -> Outside millis: 36102
21:02:11.955 -> Outside millis: 36122
21:02:11.987 -> Outside millis: 36142
21:02:12.033 -> Outside millis: 36162
21:02:12.033 -> Outside millis: 36182
21:02:12.033 -> Outside millis: 36202
21:02:12.074 -> Outside millis: 36222

That's why I can deduce that the time to execute the checkForBeat() is 20 ms. so there's no way to know the exact time of the beat and the exact difference between beats with 2ms precission.

Any idea for this? Any other way to do it or any other function on the library? I heard something about interruptions, but I'm not sure how to do it using them.

Thank you very much!
Miguel Gisbert

  Optical Heart Rate Detection (PBA Algorithm) using the MAX30105 Breakout
  By: Nathan Seidle @ SparkFun Electronics
  Date: October 2nd, 2016
  https://github.com/sparkfun/MAX30105_Breakout

  This is a demo to show the reading of heart rate or beats per minute (BPM) using
  a Penpheral Beat Amplitude (PBA) algorithm.

  It is best to attach the sensor to your finger using a rubber band or other tightening
  device. Humans are generally bad at applying constant pressure to a thing. When you
  press your finger against the sensor it varies enough to cause the blood in your
  finger to flow differently which causes the sensor readings to go wonky.

  Hardware Connections (Breakoutboard to Arduino):
  -5V = 5V (3.3V is allowed)
  -GND = GND
  -SDA = A4 (or SDA)
  -SCL = A5 (or SCL)
  -INT = Not connected

  The MAX30105 Breakout can handle 5V or 3.3V I2C logic. We recommend powering the board with 5V
  but it will also run at 3.3V.
*/

#include <Wire.h>
#include "MAX30105.h"
#include "heartRate.h"
#include <hd44780.h>
#include <hd44780ioClass/hd44780_I2Cexp.h>

hd44780_I2Cexp lcd;

MAX30105 particleSensor;

const byte RATE_SIZE = 4; //Increase this for more averaging. 4 is good.
byte rates[RATE_SIZE]; //Array of heart rates
byte rateSpot = 0;
long lastBeat = 0;
long lastBeat2 = 0;
int numBeats = 0;
long delta=0;
long delta2=0;

float beatsPerMinute;
int beatAvg;

void setup() {

  Serial.begin(115200);
  Serial.println("Initializing...");
  lcd.begin(16,2);
  lcd.clear();
  lcd.backlight();
  
  // Initialize sensor
  while ( !particleSensor.begin(Wire, I2C_SPEED_FAST) ) { //Use default I2C port, 400kHz speed
    lcd.print("Sensor error.");
  }

  // particleSensor.setup(); //Configure sensor with default settings
  // particleSensor.setPulseAmplitudeRed(0x0A); //Turn Red LED to low to indicate sensor is running
  // particleSensor.setPulseAmplitudeGreen(0); //Turn off Green LED

  byte ledBrightness = 80; //Options: 0=Off to 255=50mA
  byte sampleAverage = 2; //Options: 1, 2, 4, 8, 16, 32
  byte ledMode = 2; //Options: 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green
  byte sampleRate = 100; //Options: 50, 100, 200, 400, 800, 1000, 1600, 3200
  int pulseWidth = 69; //Options: 69, 118, 215, 411
  int adcRange = 16384; //Options: 2048, 4096, 8192, 16384

  particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange);

}

void loop() {
  long irValue = particleSensor.getIR();
  Serial.print("Outside millis: ");
  Serial.print(millis());
  Serial.println();

  if (checkForBeat(irValue) == true) { // Beat detected
    
    // delta = millis() - lastBeat;
    
    Serial.print("   millis: ");
    Serial.print(millis());
  Serial.println();
    // Serial.println();
    // Serial.print("   lastBeat: ");
    // Serial.print(lastBeat);
    // Serial.print("   Delta: ");
    // Serial.print(delta);
    // Serial.println();

  //   lastBeat = millis();

  //   // lcd.clear();
  //   // lcd.setCursor(0,0);
  //   // lcd.print(delta);
  //   // lcd.setCursor(0,1);
  //   // lcd.print(delta2);
  //   // lcd.setCursor(10,1);
  //   // lcd.print("bts:");
  //   // lcd.setCursor(13,1);
  //   // lcd.print(numBeats);

    

  //   // numBeats += 1;

  //   // beatsPerMinute = 60 / (delta / 1000.0);

  //   // if (beatsPerMinute < 255 && beatsPerMinute > 20)
  //   // {
  //   //   rates[rateSpot++] = (byte)beatsPerMinute; //Store this reading in the array
  //   //   rateSpot %= RATE_SIZE; //Wrap variable

  //   //   //Take average of readings
  //   //   beatAvg = 0;
  //   //   for (byte x = 0 ; x < RATE_SIZE ; x++)
  //   //     beatAvg += rates[x];
  //   //   beatAvg /= RATE_SIZE;
  //   // }
  }

  // if (irValue < 50000) {
  //   lcd.clear();
  //   lcd.setCursor(0,0);
  //   lcd.print("Put your finger");
  //   lcd.setCursor(0,1);
  //   lcd.print("on the sensor");
  // }
  // else {
  //   lcd.setCursor(0,0);
  //   lcd.print("HR: ");
  //   lcd.print(beatAvg);
  // }

  // Serial.print("IR=");
  // Serial.print(irValue);
  // Serial.print(", RRms=");
  // Serial.print(delta);
  // Serial.print(", RRus=");
  // Serial.print(delta2);
  // Serial.println();

}

Here is your code with time measuring how much time the different parts need to execute

/*
  Optical Heart Rate Detection (PBA Algorithm) using the MAX30105 Breakout
  By: Nathan Seidle @ SparkFun Electronics
  Date: October 2nd, 2016
  https://github.com/sparkfun/MAX30105_Breakout

  This is a demo to show the reading of heart rate or beats per minute (BPM) using
  a Penpheral Beat Amplitude (PBA) algorithm.

  It is best to attach the sensor to your finger using a rubber band or other tightening
  device. Humans are generally bad at applying constant pressure to a thing. When you
  press your finger against the sensor it varies enough to cause the blood in your
  finger to flow differently which causes the sensor readings to go wonky.

  Hardware Connections (Breakoutboard to Arduino):
  -5V = 5V (3.3V is allowed)
        - GND = GND
                - SDA = A4 ( or SDA)
                        - SCL = A5 ( or SCL)
                                - INT = Not connected

                                        The MAX30105 Breakout can handle 5V or 3.3V I2C logic. We recommend powering the board with 5V
                                        but it will also run at 3.3V.
                                          /
*/
#include <Wire.h>
#include "MAX30105.h"
#include "heartRate.h"
#include <hd44780.h>
#include <hd44780ioClass/hd44780_I2Cexp.h>

hd44780_I2Cexp lcd;

MAX30105 particleSensor;

const byte RATE_SIZE = 4; //Increase this for more averaging. 4 is good.
byte rates[RATE_SIZE]; //Array of heart rates
byte rateSpot = 0;
long lastBeat = 0;
long lastBeat2 = 0;
int numBeats = 0;
long delta = 0;
long delta2 = 0;

float beatsPerMinute;
int beatAvg;

void setup() {

  Serial.begin(115200);
  Serial.println("Initializing...");
  lcd.begin(16, 2);
  lcd.clear();
  lcd.backlight();

  // Initialize sensor
  while ( !particleSensor.begin(Wire, I2C_SPEED_FAST) ) { //Use default I2C port, 400kHz speed
    lcd.print("Sensor error.");
  }

  // particleSensor.setup(); //Configure sensor with default settings
  // particleSensor.setPulseAmplitudeRed(0x0A); //Turn Red LED to low to indicate sensor is running
  // particleSensor.setPulseAmplitudeGreen(0); //Turn off Green LED

  byte ledBrightness = 80; //Options: 0=Off to 255=50mA
  byte sampleAverage = 2; //Options: 1, 2, 4, 8, 16, 32
  byte ledMode = 2; //Options: 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green
  byte sampleRate = 100; //Options: 50, 100, 200, 400, 800, 1000, 1600, 3200
  int pulseWidth = 69; //Options: 69, 118, 215, 411
  int adcRange = 16384; //Options: 2048, 4096, 8192, 16384

  particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange);

}


unsigned long justAfter_getIR;
unsigned long justBefore_getIR;

unsigned long  justBefore_resultCheck;
unsigned long  justAfter_resultCheck;

boolean firstTrue  = false;
boolean secondTrue = false;

unsigned long  first_resultCheck;
unsigned long  second_resultCheck;


void loop() {
  justBefore_getIR = micros();
  long irValue = particleSensor.getIR();
  justAfter_getIR = micros();
  Serial.print("time it takes to execute particleSensor.getIR() is ");
  Serial.print( justAfter_getIR - justBefore_getIR );
  Serial.println (" microseconds");

  Serial.print("Outside millis: ");
  Serial.print(millis());
  Serial.println();

  justBefore_resultCheck = micros();
  resultCheckForBeat = checkForBeat(irValue);
  justAfter_resultCheck = micros();

  Serial.print("time it takes to execute checkForBeat(irValue) is ");
  Serial.print( justAfter_resultCheck - justAfter_resultCheck );
  Serial.println (" microseconds");


  if (firstTrue == false && secondTrue == false && resultCheckForBeat == true) {
    firstTrue = true;
    first_resultCheck = micros();
  }

  if (firstTrue == true && secondTrue == false && resultCheckForBeat == true) {
    secondTrue = true;
    second_resultCheck = micros();
    Serial.print("time it takes between two times checkForBeat(irValue) == true is ");
    Serial.print( second_resultCheck - first_resultCheck );
    Serial.println (" microseconds");
    firstTrue  = false;
    secondTrue = false;
  }


  Serial.print("time it takes between two times resultCheckForBeat == true");
  Serial.print( justAfter_getIR - justBefore_getIR );
  Serial.println (" microseconds");


  if ( resultCheckForBeat == true) { // Beat detected
    // delta = millis() - lastBeat;
    Serial.print("   millis: ");
    Serial.print(millis());
    Serial.println();
  }
}
// Serial.println();
// Serial.print("   lastBeat: ");
// Serial.print(lastBeat);
// Serial.print("   Delta: ");
// Serial.print(delta);
// Serial.println();

//   lastBeat = millis();

//   // lcd.clear();
//   // lcd.setCursor(0,0);
//   // lcd.print(delta);
//   // lcd.setCursor(0,1);
//   // lcd.print(delta2);
//   // lcd.setCursor(10,1);
//   // lcd.print("bts:");
//   // lcd.setCursor(13,1);
//   // lcd.print(numBeats);



//   // numBeats += 1;

//   // beatsPerMinute = 60 / (delta / 1000.0);

//   // if (beatsPerMinute < 255 && beatsPerMinute > 20)
//   // {
//   //   rates[rateSpot++] = (byte)beatsPerMinute; //Store this reading in the array
//   //   rateSpot %= RATE_SIZE; //Wrap variable

//   //   //Take average of readings
//   //   beatAvg = 0;
//   //   for (byte x = 0 ; x < RATE_SIZE ; x++)
//   //     beatAvg += rates[x];
//   //   beatAvg /= RATE_SIZE;
//   // }
}

// if (irValue < 50000) {
//   lcd.clear();
//   lcd.setCursor(0,0);
//   lcd.print("Put your finger");
//   lcd.setCursor(0,1);
//   lcd.print("on the sensor");
// }
// else {
//   lcd.setCursor(0,0);
//   lcd.print("HR: ");
//   lcd.print(beatAvg);
// }

// Serial.print("IR=");
// Serial.print(irValue);
// Serial.print(", RRms=");
// Serial.print(delta);
// Serial.print(", RRus=");
// Serial.print(delta2);
// Serial.println();

curious why you don't think that is sufficiently accurate?

woldn't it not indicate a heartbeat most of the time?

Nearly all of which is time waiting to print those values on the Serial Monitor. Without printing, it would be much faster.

Where did you get those libraries? Exact GitHub link, please.

Where is the code for checkForBeat()?

the duration of the main part of a heartbeat, QRS, is longer thatn 2 ms

normal_intervals

  • P-R interval = 0.12 - 0.20 sec (3 - 5 small squares)
  • QRS width = 0.08 - 0.12 sec (2 - 3 small squares)
  • Q-T interval 0.35 - 0.43 sec
    • The PR interval should really be referred to as the PQ interval; however it is commonly referred as the PR interval

So what?

@miguel_gisbert wants to measure the time from one heartbeat to the next.

Pick any letter, let's use R, and help her measure accurately the time from one R to the next.

a7

is the device simply a peak detector or a heartbeat detector?

should it recognize a P, R and/or T as a heartbeart? should it report 3 peaks within the period of a heartbeat?

Thank you very much to everyone!

What I need to calculate is the HRV (Heart Rate Variability), for that I need the exact time elapsed between beats with 2 ms precission (according to most papers) for the health and sports purpouses I want. If we are strict, the time between beats should be calculated between R wave peaks on an electrocardiogram. But I'm not going to do electrocardiograms so I want to do an aproximation (also validated on some papers) getting the time between PPG wave peaks on an optical sensor like the MH-ET LIVE I bought: Amazon.es

I think this is the library: GitHub - MHEtLive/MH-ET-LIVE-max30102: Code for MH-ET LIVE MAX30102 testing

But there are several versions and sensors.

Anybody knows if there's a better sensor for that or a better way like getting the peaks directly from the PPG wave or something like that?

Thanks a lot!
Miguel Gisbert

It looks like you might be able to make a career out of it.

I found this, maybe you've seen it or other things that pop right up when all I did was google PPG, didn't recognize the acronym but I am familiar with doing heart bear detection (simple pulse rate only, close enough be close enough).

HTH

a7

Probably, but that requires you to understand what was done in the first place.

For ideas about processing the raw data, do a web search for "peak detection algorithms".

Autocorrelation analysis is often used to detect and measure time between repeating patterns in a waveform.

Thanks @jremington, there's another example at the same library that shows the raw IR data on a graph and you can see the HR peaks. I suppose I have to get that wave and use one of those alghorithms to get the peaks. I'll try that, I hope I can find the proper config for the sensor to get a clean enough signal to be able to get the peaks.

When I was a kid I built one just like this (including the clothes pin) and it worked very well.
It's not an oximeter just a Plethysmograph

Just to be clear about what you want, are you wanting the time of the relaxing period between the end of one heart beat and the beginning of the next? Or are wanting to know the period of time between some other parts of the heart beat so as to compute the beating rate?

Maybe the library for heartrate will help...

Hi all again! I realised that it's not possible what I wanted with that MAX30102 because there's no analog signal output, the output is digital and it's not possible to get the time between beats with ms precision.

So I tried a diferent option: AD8232. This one uses an electrocardiogram instead of PPG so it should create the electrical signal with the real R wave to calculate the time between R waves. The problem I found is that the signal at the Plotter is not even close to an ECG signal. I thought it might be because of the electrode (the look bad quality) so I got a clip ones and I got more or less same result. I'm thinking now maybe I need a conduction gel on the electrodes to make a good connection.

Anyway, has anybody ever tried this? has anybody got the signal problems with this AD8232 or tried a diferent one? I also found these two, I don't know if they would be any better:

Thanks a lot!
Miguel Gisbert

You should not be looking for a trace, only an interval. What data do you have? If you used micros() for time keeping, does the module still only work at 20ms intervals?

Many have.

Thanks @xfpd, I also checked that page and I did exactly the same, same connections and same code. I tried diferent cables as well and difrenten board pins, just in case they were broken or something. But my signal is not correct, I can't get the intervals between peaks because the peaks are not correct, it's just like a random signal. If I press the electrodes it changes, that's why I thought it was about electrodes connections. I ordered the conduction gel to try to get better connection.

I was asking just in case anybody had the same problem.

Thank you,
Miguel Gisbert

curious if you've considered sampling the ekg signals, detecting the R peak and measuring the periods more precisely. for my thesis on Arrythmia detection did 250 samples/sec.