KY039 Arduino Heart rate code

The Keyes-039 or KY039 Heart rate monitor consists of two things, an Infrared LED, and an Infrared Phototransistor. The IR LED should come on whenever the sensor has power and stay on (because it is infrared, you won't be able to see that it is on, but if you look through your cell phone camera as suggested previously you will). The IR photo transistor causes the voltage to change on a "sensor" wire, and this should be connected to one of the Arduino's analog pins.

Here is a picture of the sensor:

And here is a schematic:

There are 3 pins. The top pin (with the backwards S by it) should go to one of the 6 analog pins of the Arduino (A0 to A5), The middle pin should be plugged into 5 volts, and the bottom pin (with the - next to it) should be plugged into ground.

Once the Arduino is powered the IR LED should come on and stay on (check with a cell phone camera).

Commented code for seeing the values is as follows:

// Pulse Monitor Test Script
int ledPin = 13; // This pin is connected on the Arduino board to an LED, if you want to blink this LED
                       // to indicate a pulse is being detected, defining it here is a good idea, however
                       // in the code that follows, this is never done, so this line could be deleted.
int sensorPin = 0; // This is the analog sensor pin the backwards S pin is connected to
                           // you can use any of the analog pins, but would need to change this to match
double alpha = 0.75; // This code uses a rather cool way of averaging the values, using 75% of the 
                               // average of the previous values and 25% of the current value.
int period = 20; // This is how long the code delays in milliseconds between readings (20 mSec)
double change = 0.0; // My guess is that this was going to be used to detect the peaks, but the 
                                // code never does... you can delete this line as well.

// This code runs on startup, it sets ledPin to output so it can blink the LED that is built into the
// Arduino (but then never does), and sets the serial port up for fast 115,200 baud communication
// of the values (make sure you change your serial monitor to match).
void setup ()
{
  pinMode (ledPin, OUTPUT); // not used, can remove
  Serial.begin (115200); // the baud rate of the serial data
}
void loop ()
{
    static double oldValue = 0; // used for averaging.
    static double oldChange = 0; // not currently used
    int rawValue = analogRead (sensorPin); // This reads in the value from the analog pin.
                                                              // this is a 10 bit number, and will be between 0 and 1023
                                                              // If this value doesn't change, you've connected up
                                                              // something wrong


    double value = alpha * oldValue + (1 - alpha) * rawValue; // Calculate an average using 75% of the
                                                                                         // previous value and 25% of the new 
    
    Serial.print (rawValue); // Send out serially the value read in
    Serial.print (",");          // Send a comma
    Serial.println (value);   // Send out the average value and a new line
    oldValue = value;        // Save the average for next iteration
    delay (period);            // Wait 20 mSec
}

Hi guys, thank you for your effort for explaining it. I followed the commented code, I can understand it now however it is still giving me the same results.

yttytyt.png

Hello,

I just got one of those, and i was able to (sort of) make a led blink with the same frequency of my heart.

As you can see at the image below, in a heart beat there is more than one area of "no pressure variation". Consider this while you coding.

Do not press your finger too tight, or the sensor wont catch the blood pressure change.

Using the code below, the led will light when there is no pressure variation at the sensor.

int ledc = 10;  
int sensorPin = 0;
int period = 100;
int change, oldvalue, value;
void setup ()
{
  pinMode(ledc, OUTPUT);
  Serial.begin (9600);
}
void loop ()
{
  value = analogRead (sensorPin);
  change = abs(oldvalue - value);
  if(change >= 2 )
  {
    Serial.println (change);
    analogWrite(ledc, 0); 
  }
  else
  { 
    analogWrite(ledc,255); 
  }
  oldvalue = value;
  delay (period);
}

Hi there,
I have bought the very same chinese sensor. KY-039, but it has not been a good deal!

Have a look at Weekend Projects - Infrared Pulse Sensor - YouTube or on other project on youtube..
and you can understand that you may wanna get something better than just a led, and IR receiver and two resistors. you may want to amplify and clean the signal here.

BTW here is my two-mins simple attempt to get something useful from this incomplete sensor.

int ledc = 13;
int sensorPin = 0;
int period = 20;
int change, oldvalue, value;
int time = 0;

void setup ()
{
pinMode(ledc, OUTPUT);
Serial.begin (9600);
}
void loop ()
{
value = analogRead (sensorPin);
Serial.print (" ");
Serial.println(value);
change = abs(oldvalue - value);
if(change > 1 )
{
Serial.println (change);
time +=1;
}
else
{
time -=1;
if (time < 0)
time = 0;
}

if (time > 0)
analogWrite(ledc, 255);
else
analogWrite(ledc,0);

oldvalue = value;
delay (period);
}

Glad to see I'm not alone with this cheap finger pulse detector.

Any updates on this sensor? Has anyone been able to actually use it as a halfway decent pulse detector?
What about (god forbid) estimating current heart rate from this data?
Can you use micros() to figure out the time interval between pulses? currenttime - oldtime = RRinterval? 60000(ms per minute)/RRinterval = bpm (for just that 1 pulse interval if it continued at that same pace for a minute)?

Are you guys just guessing what jump in sensor value (between current and last value) signifies a heart beat? If it's too sensitive, up the value, if it's too infrequent, lower the value? Keep changing it until you feel the light is blinking at the same rate as your heart beat?

How come you don't have to specify that the sensor is connected to an analog pin? Shouldn't it be:
"int sensorPin = A0;"
instead of just
"int sensorPin = 0;"

Is there any suggestions for other cheap pulse sensors that might work better? I have seperate IR led & detector, but I don't see what advantage that would have over this current sunfounder sensor.

Part of the fun was only spending a very little on hardware and making it work, but it looks like you can get a decent one for 25 bucks from http://pulsesensor.com/ .

OK, this is my first post! 8)

I just got a very similar board, and the discussion here allowed me to confirm how it can be connected since the pinout is not very clearly printed.

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).

The solution is to do a simple peak detection, done by finding the max, decaying it and resetting it when a new peak is found. The code blips the LED and also outputs to the serial console.

If you rest the sensor without pressing hard, and you don't move, you get a heartbeat display, with some beats missed or duped.

The bottom line is with proper filtering code this thing can work.

I have yet to find a use for it as a sensor to activate fun stuff, but I'm not sure I care to make this a real heart rate monitor. It would not be convenient anyways as it is as cumbersome as an oxymeter.

That said this is an infrared detector with a light source. There must be something I can do with that.

Note: I tried to take a photo of the rig to see the infrared beam, and I could not see it.

int ledPin=13;
int sensorPin=0;

float alpha=0.75;
int period=50;
float change=0.0;

void setup()
{
  // Built-in arduino board pin
  pinMode(ledPin,OUTPUT);

  Serial.begin(9600);
  Serial.print("Heartrate detection.\n");
  delay(100);
}

float max =0.0;

void loop()
{
  static float oldValue=1009;
  static float oldChange=0.2;

  // This is generic code provided with the board.
  int rawValue=analogRead(sensorPin);
  float value= alpha*oldValue +(1-alpha)* rawValue;
  change=value-oldValue;

  // Display data on the LED via a blip:
  // Empirically, if we detect a peak as being X% from
  // absolute max, we find the pulse even when amplitude
  // varies on the low side.
  
  // Reset max every time we find a new peak
  if (change >= max) {
    max= change;
    Serial.println("  |");
    digitalWrite(ledPin,1);
  } else {
    Serial.println("|");
    digitalWrite(ledPin,0);
  }
  // Slowly decay max for when sensor is moved around
  // but decay must be slower than time needed to hit
  // next heartbeat peak.
  max = max * 0.98;
  
  // Display debug data on the console
//  Serial.print(value);
//  Serial.print(", ");
//  Serial.print(change);
//  Serial.print(", ");
//  Serial.println(max);

  oldValue=value;
  oldChange=change;
  delay(period);
}

heartrateRawData.png

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.

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.