KY039 Arduino Heart rate code

sonyhome: I managed to tweak generic ebay code after looking at the output to generate a somewhat responsive heart monitor code. Since there is no amplification, the resolution of the signal is very low, since the dynamic range is small while the finger is on the sensor (960~967), but it is enough to get a coarse heart rate signal (see included graph for about 2 sec of data).

I think the signal becomes better when more light arrives at the detector. Most likely the LED sends its beam a bit in the wrong direction.

Most likely you have to bend the LED a bit so that max. IR beam intensity is sent directly to the IR receiver.

I think if your signal is detected in the range of 400...600 it will be much better than at nearly end of range at 950+.

Simply leave the center pin (+) unpluged

you only need to conect 2 pins:

pin - to GND
pin S to A0

central pin (+) leave unconnected

Jurs is correct. My code works all the same but detection is much improved.

=> This sensor is actually becoming good enough to be used as a heart rate monitor! <=

The way the board is sold, the sensor ishorizontal (detector faces up), and the LED is bent 90 degrees, facing the sensor but emiting the light at right angle from the sensor.

By bending the LED to face down onto the sensor, the fully lit sensor now reads in the 20’s instead or mid 40’s.
When I put my finger the signal is now in the 700’s. The dynamic range has not much improved but I suspect since there’s more LED light there’s more rejection from reflected lights and such.

So same code, new readout from the serial console: The blue signal is when the LED blips, the red is the RAW signal.

Overall the code could be simplified to just detect the light RAW and do the same max detectin on the RAW value instead of the math blurb done that generates a delta. No math, no floats, nothing…

luis_xxl: Simply leave the center pin (+) unpluged

you only need to conect 2 pins:

pin - to GND pin S to A0

central pin (+) leave unconnected

That does not make sense. The LED needs power. If I do that the reading will be 0.

sonyhome:
By bending the LED to face down onto the sensor, the fully lit sensor now reads in the 20’s instead or mid 40’s.
When I put my finger the signal is now in the 700’s.

I’ve made a photo of my sensor. The main beam of the LED should be directed to the sensor to provide max IR intensity. That way I can even get value readings below 500 when putting my finger between LED and sensor.

sonyhome:
Overall the code could be simplified to just detect the light RAW and do the same max detectin on the RAW value instead of the math blurb done that generates a delta. No math, no floats, nothing…

It’s interesting to see how many different codes can detect the heartrate. My solution would be much more complicated: Setting up a timer interrupt for sampling the signal to a FIFO buffer at a fixed interval, despite of what is going on at the same time (calculations, statistics, printing things on LCD). Then pulling the data from FIFO buffer and do all the calculations with sliding averages.heartrateSensor.jpg

Jurs: Yes that’s how I bent the LEDs to make it work well.

Here’s a new version of the code that computes BPM.

The main routine heartbeatDetected() can be converted into a generic library for
that kind of sensor… With my board it works very well now, and the sleep can
be adjusted up to 150ms before the data becomes garbage.

If you want to tie the call to the routine to an interrupt handler there is no limitation.
It’s not really needed though and what is needed is to know the latency between calls
(more or less) to adjust the decay of the detection levels.

Of course the main loop needs an exact time delta because it prints the BPM. For the
LED blinks however we don’t care too much.

I tried 10ms ~150ms with a solid output.

Caveat: If you move the sensor, it may take a while to re-pick up the heart beat.
If that’s an issue for some people I have plenty of ways to fix that I could come up
with, but I wanted to keep this all at a minimal memory footprint…

////////////////////////////////////////////////////////////////////////
/// Copyright (c)2015 Dan Truong
/// Permission is granted to use this software under the MIT
/// licence, with my name and copyright kept in source code
/// http://http://opensource.org/licenses/MIT
///
/// KY039 Arduino Heartrate Monitor V1.0 (April 02, 2015)
////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////
/// @param[in] IRSensorPin Analog pin on which IR detector is connected
/// @param[in] delay (msec) delay between calls to this method. It is
///                  best to call it at least 5 times per beat, aka
///                  no slower than 150msec for 70bpm. An ideal value
///                  is 60ms or faster to handle up to 200 BPM.
///
/// @brief
/// True if heartbeat is detected on the sensor.
/// This code is trivial and just does a peak detection, instead of
/// trying to detect the heart's pulse waveform.
/// Note: I am fudging sensor data with the delay to make the integer
/// math after that uses constants, somewhat independant of the sleep
/// delay used in the main loop. Otherwise if maxValue decays too slow
/// or too fast, it causes glitches and false beat detection.
////////////////////////////////////////////////////////////////////////
#define HBDEBUG(i) i
//#define HBDEBUG(i)

bool
heartbeatDetected(int IRSensorPin, int delay)
{
  static int maxValue = 0;
  static bool isPeak = false;
  int rawValue;
  bool result = false;
    
  rawValue = analogRead(IRSensorPin);
  // Separated because analogRead() may not return an int
  rawValue *= (1000/delay);
  HBDEBUG(Serial.print(isPeak); Serial.print("p, "));
  HBDEBUG(Serial.print(rawValue); Serial.print("r, "));
  HBDEBUG(Serial.print(maxValue); Serial.print("m, "));

  // If sensor shifts, then max is out of whack.
  // Just reset max to a new baseline.
  if (rawValue * 4L < maxValue) {
    maxValue = rawValue * 0.8;
    HBDEBUG(Serial.print("RESET, "));
  }
  
  // Detect new peak
  if (rawValue > maxValue - (1000/delay)) {
    // Only change peak if we find a higher one.
    if (rawValue > maxValue) {
      maxValue = rawValue;
    }
    // Only return true once per peak.
    if (isPeak == false) {
      result = true;
      Serial.print(result); Serial.print(",  *");
    }
    isPeak = true;
  } else if (rawValue < maxValue - (3000/delay)) {
    isPeak = false;
    // Decay max value to adjust to sensor shifting
    // Note that it may take a few seconds to re-detect
    // the signal when sensor is pushed on meatier part
    // of the finger. Another way would be to track how
    // long since last beat, and if over 1sec, reset
    // maxValue, or to use derivatives to remove DC bias.
    maxValue-=(1000/delay);
 }
  HBDEBUG(Serial.print("\n"));
  return result;
}


////////////////////////////////////////////////////////////////////////
// Arduino main code
////////////////////////////////////////////////////////////////////////
int ledPin=13;
int analogPin=0;

void setup()
{
  // Built-in arduino board pin for the display LED
  pinMode(ledPin,OUTPUT);
  
  // Init serial console
  Serial.begin(9600);
  Serial.println("Heartbeat detection sample code.");
}

const int delayMsec = 60; // 100msec per sample

// The main loop blips the LED and computes BPMs on serial port.
void loop()
{
  static int beatMsec = 0;
  int heartRateBPM = 0;
  
  if (heartbeatDetected(analogPin, delayMsec)) {
    heartRateBPM = 60000 / beatMsec;
    digitalWrite(ledPin,1);

    // Print msec/beat and instantaneous heart rate in BPM
    Serial.print(beatMsec);
    Serial.print(", ");
    Serial.println(heartRateBPM);
    
    beatMsec = 0;
  } else {
    digitalWrite(ledPin,0);
  }
  // Note: I assume the sleep delay is way longer than the
  // number of cycles used to run the code hence the error
  // is negligible for math.
  delay(delayMsec);
  beatMsec += delayMsec;
}

Darn I forgot to comment out the debug statements.
Remember you may have to wait like 30s to see your bpm.

Version that’s non verbose on the console:

////////////////////////////////////////////////////////////////////////
/// Copyright (c)2015 Dan Truong
/// Permission is granted to use this software under the MIT
/// licence, with my name and copyright kept in source code
/// http://http://opensource.org/licenses/MIT
///
/// KY039 Arduino Heartrate Monitor V1.0 (April 02, 2015)
////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////
/// @param[in] IRSensorPin Analog pin on which IR detector is connected
/// @param[in] delay (msec) delay between calls to this method. It is
///                  best to call it at least 5 times per beat, aka
///                  no slower than 150msec for 70bpm. An ideal value
///                  is 60ms or faster to handle up to 200 BPM.
///
/// @brief
/// True if heartbeat is detected on the sensor.
/// This code is trivial and just does a peak detection, instead of
/// trying to detect the heart's pulse waveform.
/// Note: I am fudging sensor data with the delay to make the integer
/// math after that uses constants, somewhat independant of the sleep
/// delay used in the main loop. Otherwise if maxValue decays too slow
/// or too fast, it causes glitches and false beat detection.
////////////////////////////////////////////////////////////////////////
//#define HBDEBUG(i) i
#define HBDEBUG(i)

bool
heartbeatDetected(int IRSensorPin, int delay)
{
  static int maxValue = 0;
  static bool isPeak = false;
  int rawValue;
  bool result = false;
    
  rawValue = analogRead(IRSensorPin);
  // Separated because analogRead() may not return an int
  rawValue *= (1000/delay);
  HBDEBUG(Serial.print(isPeak); Serial.print("p, "));
  HBDEBUG(Serial.print(rawValue); Serial.print("r, "));
  HBDEBUG(Serial.print(maxValue); Serial.print("m, "));

  // If sensor shifts, then max is out of whack.
  // Just reset max to a new baseline.
  if (rawValue * 4L < maxValue) {
    maxValue = rawValue * 0.8;
    HBDEBUG(Serial.print("RESET, "));
  }
  
  // Detect new peak
  if (rawValue > maxValue - (1000/delay)) {
    // Only change peak if we find a higher one.
    if (rawValue > maxValue) {
      maxValue = rawValue;
    }
    // Only return true once per peak.
    if (isPeak == false) {
      result = true;
      HBDEBUG(Serial.print(result); Serial.print(",  *"));
    }
    isPeak = true;
  } else if (rawValue < maxValue - (3000/delay)) {
    isPeak = false;
    // Decay max value to adjust to sensor shifting
    // Note that it may take a few seconds to re-detect
    // the signal when sensor is pushed on meatier part
    // of the finger. Another way would be to track how
    // long since last beat, and if over 1sec, reset
    // maxValue, or to use derivatives to remove DC bias.
    maxValue-=(1000/delay);
 }
  HBDEBUG(Serial.print("\n"));
  return result;
}


////////////////////////////////////////////////////////////////////////
// Arduino main code
////////////////////////////////////////////////////////////////////////
int ledPin=13;
int analogPin=0;

void setup()
{
  // Built-in arduino board pin for the display LED
  pinMode(ledPin,OUTPUT);
  
  // Init serial console
  Serial.begin(9600);
  Serial.println("Heartbeat detection sample code.");
}

const int delayMsec = 60; // 100msec per sample

// The main loop blips the LED and computes BPMs on serial port.
void loop()
{
  static int beatMsec = 0;
  int heartRateBPM = 0;
  
  if (heartbeatDetected(analogPin, delayMsec)) {
    heartRateBPM = 60000 / beatMsec;
    digitalWrite(ledPin,1);

    // Print msec/beat and instantaneous heart rate in BPM
    Serial.print(beatMsec);
    Serial.print(", ");
    Serial.println(heartRateBPM);
    
    beatMsec = 0;
  } else {
    digitalWrite(ledPin,0);
  }
  // Note: I assume the sleep delay is way longer than the
  // number of cycles used to run the code hence the error
  // is negligible for math.
  delay(delayMsec);
  beatMsec += delayMsec;
}

Guys…need help…i was not using ky039 module…i only use red led,ir transmitter and receiver…and i have connect to op amp to make it smooth…but when i was run i get the result is 700++…but i find the person who are done this project and used the same component but get different result…i wonder how can i get the same result as that person…

int ledPin = 13;
int sensorPin = 0;
double alpha = 0.75; //using 75% of the average of the previous values and 25% of the current value
int period = 10; // This is how long the code delays in milliseconds between readings (10 mSec)
double change = 0.0; //to be used to detect the peaks

void setup()
{
pinMode(ledPin, OUTPUT);
Serial.begin(115200);
}
void loop()
{
static double oldValue = 0; // used for averaging.
static double oldChange = 0;
int rawValue = analogRead(sensorPin); // This reads in the value from the analog pin
double value = alpha * oldValue + (1 - alpha) * rawValue; // Calculate an average using 75% of the previous value and 25% of the new
change = value - oldValue;
digitalWrite(ledPin, (change < 0.00 && oldChange > 0.00));

delay(1000);
Serial.println(“Red IR”);
Serial.print(rawValue);
Serial.print(" “);
Serial.print(value);
Serial.println(” ");
oldValue = value;
oldChange = change;
}

skylion: wonder how can i get the same result as that person..

Why do you ask?

Because you don't understand anything about the code you posted?

Perhaps preplace:

delay(1000);
Serial.println("Red IR");

with

delay(period);

and watch if something different happens.

I don't know whether it may work, but to me it sounds much more reasonable to measure the input each 10 milliseconds instead of once per second only.

skylion, try my code, and also look at the desvription on how the led is oriented through the finger. you seem to be getting data pulses. btw, 1000 is 1 sample per second, and you want like minimum 5 samples per pulse.

Thx sonyhome for your help…i think i already get the result for the oxygen saturation…now i want to ask about measure heartbeat and oxygen saturation in one program…i have 2 sepatare coding

This is for measuring heartbeat:

// Pins
const int ledPin = 13;
const int sensePin = 0;

// LED blink variables
int ledState = LOW;
long ledOnMillis = 0;
long ledOnInterval = 50;

// Hearbeat detect variables
int newHeartReading = 0;
int lastHeartReading = 0;
int Delta = 0;
int recentReadings[8] = {0,0,0,0,0,0,0,0};
int historySize = 8;
int recentTotal = 0;
int readingsIndex = 0;
boolean highChange = false;
int totalThreshold = 2;

// Heartbeat Timing
long lastHeartbeatTime = 0;
long debounceDelay = 150;
int currentHeartrate = 0;

void setup() {
// initialize the serial communication:
Serial.begin(115200);
// initialize the digital pin as an output:
pinMode(ledPin, OUTPUT);
}

void loop() {
// Turn off LED
digitalWrite(ledPin, LOW);

// Read analogue pin.
newHeartReading = analogRead(sensePin);
//Serial.println(newHeartReading);
//Calculate Delta
Delta = newHeartReading - lastHeartReading;
lastHeartReading = newHeartReading;

// Find new recent total
recentTotal = recentTotal - recentReadings[readingsIndex] + Delta;
// replace indexed recent value
recentReadings[readingsIndex] = Delta;
// increment index
readingsIndex = (readingsIndex + 1) % historySize;

//Debug
//Serial.println(recentTotal);

// Decide whether to start an LED Blink.
if (recentTotal >= totalThreshold) {
// Possible heartbeart, check time
if (millis() - lastHeartbeatTime >= debounceDelay) {
// Heartbeat
digitalWrite(ledPin, HIGH);
currentHeartrate = 60000 / (millis() - lastHeartbeatTime);
lastHeartbeatTime = millis();
// Print Results
//Serial.println(“Beat”);
if (currentHeartrate <= 200)
{ Serial.println(currentHeartrate); } } } delay(10); }

This one for measure oxygen level;

int ledPin = 13;
int sensorPin = 0;
double alpha = 0.75; //using 75% of the average of the previous values and 25% of the current value
int period = 5; // This is how long the code delays in milliseconds between readings (10 mSec)
double change = 0.0; //to be used to detect the peaks
int z;

void setup()
{
pinMode(ledPin, OUTPUT);
Serial.begin(115200);
}
void loop()
{
static double oldValue = 0; // used for averaging.
static double oldChange = 0;
int rawValue = analogRead(sensorPin)-700; // This reads in the value from the analog pin
double value = alpha * oldValue + (1 - alpha) * (rawValue);// Calculate an average using 75% of the previous value and 25% of the new
change = value - oldValue;
z = log(1/value)/(log(1/rawValue));
digitalWrite(ledPin, (change < 0.00 && oldChange > 0.00));

delay(2000);
Serial.println(“IR Intensity led intensity”);
Serial.print(rawValue);
Serial.print(" “);
Serial.print(value);
Serial.println(” ");
oldValue = value;
oldChange = change;
}

and also the formula is unread…when i click the serial monitor the value z was 0…

anyone can help?

z = log(1/value)/(log(1/rawValue));

replace with:

z = log(1.0/value)/(log(1.0/rawValue));

You may want to say what is your native language, maybe I or someone else speaks it.

I have tried hard to work with this module both, powering from arduino and externally powering it. In both cases I do not get values that make sense for bpm (i.e., let’s say 60-200bpm).
IR LED is working, since I am able to see it in the dark with a camera.

When using external power, intensity is higher (arduino pins are around 40mA aprox.) and the IR LED is brighter.

Any new idea not detailed in this forum?

mlinaje: Any new idea not detailed in this forum?

Check the alignment of LED beam and sensor direction like shown in reply #27.

Then the analogRead values should be "in the middle" of the measuring range (i.e. 300...800) and not at the threshold of sensitivity.

HELLO. This is my first post so sorry if I say something stupid.

I used the schematic and the code from this page : Arduino KY-039 Detect the heartbeat module - TkkrLab

Can anyone tell me how to interpret the numbers so that I can get the heart beat/pulse?

I posted the numbers I measured in attachment.

Thank you.

heartbeat.txt (1.01 KB)

AlexMolnar: Can anyone tell me how to interpret the numbers so that I can get the heart beat/pulse?

I posted the numbers I measured in attachment.

For correct sensor adjustment see my reply #27 and the picture I posted.

Your data with many, many 0 measurements show that the LED in your hardware is not correctly adjusted to send its beam to the IR sensor.

As far as the code is concerned, you are doing measurement of the raw sensor data in a given moment:

   int rawValue = analogRead (sensorPin);

And then you are doing a low-pass filtering ("smooting", "averaging") of the values:

   double value = alpha * oldValue + (1 - alpha) * rawValue;

So "rawValue" is a momentary value measured at a single moment. This number can change very quickly from one measure to the next.

And "value" is an filtered average value, created from several measured values. This number can change slowly from one measure to the next, as it is an average created from several measurements.

So when printing both you will see this in your data: - sometimes "rawValue" is higher than "value" - sometime "rawValue" is lower than "value"

If you'd do that correctly (LED adjustment, low-pass filter value), you could see that the speed of this change will be in the rhythm of the heartbeat.

I’d say try the piece of code I submitted instead of that calculated value that does not work too good.

I built one of these pulse rate sensors for a company whilst at university back in 1971. Using similar components that were then available - but the solution didn't include any processors!!

Key things to think about. The level of the signal you are interested in is 'swamped' by the level of "DC" voltage seen by the sensor. This "DC" offset voltage will vary enormously between people and also for the same person in different conditions (e.g. hot or cold, well or ill).

You should shield the sensor from external light - especially mains driven - which adds a further signal conditioning problem - that's why the ones used in (eg) hospitals clip over the finger. With care you should be able to get some readings but I suspect that the variations in output levels will make it difficult to detect accurately.

On my KY-039 the little yellow men put a red LED in wrong direction >:( Any questions?

HI all. I used the same code and get the output between 4080-4094. What should be the output of this sensor ? Is there any equation so I can convert this output numbers in regular pulse number , eg. Like 60-100.

this is my code. int ledPin = 13; int sensorPin = A0; int alpha = 0.75; //try with double int period = 1000; //double change = 0.0;

//double minval = 0.0; void setup () { pinMode(ledPin,OUTPUT); Serial.begin(9600); } void loop () { static int oldValue = 0; // static double oldChange = 0;

int rawValue = analogRead(A0); Serial.println(rawValue); digitalWrite(ledPin,HIGH); int value = alpha * oldValue + (1 - alpha) * rawValue;

// Serial.println(rawValue); Serial.println(" and, "); Serial.println (value);

digitalWrite(ledPin, LOW); oldValue = value;

delay (period); }