Go Down

Topic: KY039 Arduino Heart rate code (Read 62090 times) previous topic - next topic

ik0jre

In the code I think there is error.

You can verify if trasmitter LED ( 5mm round ) lights with you smartphone camera (keep a trasmitter led near camera and if you must see a blue light). If didn't, try to modify code:

void setup ()
{
  pinMode (ledPin, OUTPUT);
  digitalWrite(ledPin,HIGH);  //new line to power on led
  Serial.begin (115200);
}

Retry to see led light.
With led on you must read min value.

Christian_R

Christian

brianivie

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:
Code: [Select]

// 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
}



WilFerraciolli

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.

rggassner

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.

Code: [Select]

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);
}

GNUton

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

Have a look at https://www.youtube.com/watch?v=psTa5ZrqAyo 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.

Quote
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);
}


sglandry

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

sonyhome

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.

Code: [Select]

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);
}

jurs

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

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

sonyhome

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

sonyhome

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.

jurs

#27
Apr 02, 2015, 11:23 am Last Edit: Apr 02, 2015, 11:24 am by jurs
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.

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.


sonyhome

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


Code: [Select]

////////////////////////////////////////////////////////////////////////
/// 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;
}

sonyhome

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:

Code: [Select]

////////////////////////////////////////////////////////////////////////
/// 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;
}

Go Up