• Arduino
  • Raspberry Pi
  • Raspberry Pi Pico
  • Micro:Bit

The heartbeat sensor detects the pulse when a finger is held between the infrared light-emitting diode and the phototransistor. The pulse can then be read at the signal output.

How a phototransistor works is as follows: A phototransistor works in a similar way to a normal transistor, with a higher current flowing through it the higher the control voltage applied to it. In a phototransistor, however, the incident light represents the control voltage. This means that the more light hits the phototransistor, the more current it lets through. In this sensor, the change in the amount of light caused by the blood flow in the finger is detected by the phototransistor and converted into a signal that can be used for pulse detection.

This sensor is ideal for projects where heart rate monitoring is required, such as in fitness trackers, health monitoring systems or other biometric applications.

If you connect a resistor in series in front of the transistor, the following behavior results when you measure the voltage across the transistor: If there is a lot of light shining on the transistor or if it is bright outside, you can measure a low voltage close to 0V - if the transistor is in the dark, it lets a relatively small current through and you measure a voltage close to +V.

The measurement setup with infrared diode and phototransistor built in this sensor module now allows us to measure the pulse by putting a finger between diode and transistor. Explanation: just as we know it from the flashlight, the skin on the hand can be shone through. If you hit a blood vessel, you can faintly see the blood pumping. This pumping can be recognized because the blood has a different density at different points in the vein and thus differences in brightness can be seen in the blood flow. Exactly these differences in brightness can be recorded with the sensor module and thus the pulse can be detected. This becomes clear when looking at the following oscilloscope image.

KY-039

This shows on the Y-axis the change of the voltage at the phototransistor - thus the brightness changes caused by the flowing blood. The peaks marked above thus result in the beating of the heart. If you now calculate the registered beats per recorded time, you get a pulse of about 71 beats/minute (bpm).

As you can see on the upper oscilloscope picture, the recorded signal is relatively small and the transistor is very sensitive to pick up the weak signal. To get an optimal result, we recommend to prepare the module for measuring as shown in the following pictures:

To increase the sensitivity of the sensor, we recommend fixing the sensor to the fingertip of the little finger using a plaster / adhesive tape / insulating tape.

The heartbeat is recorded/registered particularly well when the sensor is located over a larger blood vessel - to improve the signal, we also recommend changing the position of the sensor on the fingertip if necessary.

Pin assignment

Cable color Cable meaning
Black Signal
White + V
Gray GND
Arduino Sensor
Pin A0 Signal
5 V + V
GND GND

Code example

To load the following code example onto your Arduino, we recommend using the Arduino IDE. In the IDE, you can select the appropriate port and board for your device.

Copy the code below into your IDE. To upload the code to your Arduino, simply click on the upload button.

////////////////////////////////////////////////////////////////////////
/// 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 PI to which the sensor is connected
/// @param [in] delay (msec) The delay between calling up the sampling function.
//                           You get the best results when you scan 5 times per heartbeat.
///                          Not slower than 150mSec for e.g. 70 BPM pulse
///                          60 mSec would be better for e.g. up to a pulse of 200 BPM.
///
/// @ short description
/// This code represents a so-called peak detection.
/// No heartbeat history is recorded, but it
/// a search is made for "peaks" within the recorded data,
/// and indicated by LED. By means of the well-known delay intervals, you can
/// the pulse can be roughly calculated.
////////////////////////////////////////////////////////////////////////
  
int rawValue;

bool heartbeatDetected(int IRSensorPin, int delay) {
  static int maxValue = 0;
  static bool isPeak = false;
  bool result = false;
  rawValue = analogRead(IRSensorPin);
  // Here the current voltage value at the photo transistor is read out and stored temporarily in the rawValue variable
  rawValue *= (1000/delay);
  // Should the current value deviate too far from the last maximum value
  // (e.g. because the finger was put on again or taken away)
  // So the MaxValue is reset to get a new base.
  if (rawValue * 4L < maxValue) maxValue = rawValue * 0.8; // Detect new peak
  if (rawValue > maxValue - (1000/delay)) {
    // The actual peak is detected here. Should a new RawValue be bigger
    // as the last maximum value, it will be recognized as the top of the recorded data.
    if (rawValue > maxValue) {
      maxValue = rawValue;
    }
    // Only one heartbeat should be assigned to the recognized peak
    if (isPeak == false) {
      result = true;
    }
    isPeak = true;
  } 
  else if (rawValue < maxValue - (3000/delay)) {
    isPeak = false;
    // This is the maximum value for each pass
    // slightly reduced again. The reason for this is that
    // not only the value is otherwise always stable with every stroke
    // would be the same or smaller, but also,
    // if the finger should move minimally and thus
    // the signal would generally become weaker.
    maxValue-=(1000/delay);
 }
  return result;
}
////////////////////////////////////////////////////////////////////////
// Arduino main code
////////////////////////////////////////////////////////////////////////
int ledPin=13;
int analogPin=0;

void setup() {
  // The built-in Arduino LED (Digital 13) is used here for output
  pinMode(ledPin,OUTPUT);
  // Serial output initialization
  Serial.begin(9600);
  Serial.println("KY-039 Heart rate measurement");
}
const int delayMsec = 60; // 100msec per sample
// The main program has two tasks:
// - If a heartbeat is recognized, the LED flashes briefly
// - The pulse is calculated and output on the serial output.
  
void loop() {
  static int beatMsec = 0;
  int heartRateBPM = 0;
  if (heartbeatDetected(analogPin, delayMsec)) {
    heartRateBPM = 60000 / beatMsec;
    /// LED output at heartbeat
    digitalWrite(ledPin,1);
    // Exclude values that are too high or too low
    if (heartRateBPM > 30 && heartRateBPM < 200){
      // Serial data output
      Serial.print("Pulse detected: ");
      Serial.println(heartRateBPM);
    }
    beatMsec = 0;
  } 
  else {
    digitalWrite(ledPin,0);
  }
  delay(delayMsec);
  beatMsec += delayMsec;
}

The heartbeat sensor detects the pulse when a finger is held between the infrared light-emitting diode and the phototransistor. The pulse can then be read at the signal output.

How a phototransistor works is as follows: A phototransistor works in a similar way to a normal transistor, with a higher current flowing through it the higher the control voltage applied to it. In a phototransistor, however, the incident light represents the control voltage. This means that the more light hits the phototransistor, the more current it lets through. In this sensor, the change in the amount of light caused by the blood flow in the finger is detected by the phototransistor and converted into a signal that can be used for pulse detection.

This sensor is ideal for projects where heart rate monitoring is required, such as in fitness trackers, health monitoring systems or other biometric applications.

If you connect a resistor in series in front of the transistor, the following behavior results when you measure the voltage across the transistor: If there is a lot of light shining on the transistor or if it is bright outside, you can measure a low voltage close to 0V - if the transistor is in the dark, it lets a relatively small current through and you measure a voltage close to +V.

The measurement setup with infrared diode and phototransistor built in this sensor module now allows us to measure the pulse by putting a finger between diode and transistor. Explanation: just as we know it from the flashlight, the skin on the hand can be shone through. If you hit a blood vessel, you can faintly see the blood pumping. This pumping can be recognized because the blood has a different density at different points in the vein and thus differences in brightness can be seen in the blood flow. Exactly these differences in brightness can be recorded with the sensor module and thus the pulse can be detected. This becomes clear when looking at the following oscilloscope image.

KY-039

This shows on the Y-axis the change of the voltage at the phototransistor - thus the brightness changes caused by the flowing blood. The peaks marked above thus result in the beating of the heart. If you now calculate the registered beats per recorded time, you get a pulse of about 71 beats/minute (bpm).

As you can see on the upper oscilloscope picture, the recorded signal is relatively small and the transistor is very sensitive to pick up the weak signal. To get an optimal result, we recommend to prepare the module for measuring as shown in the following pictures:

To increase the sensitivity of the sensor, we recommend fixing the sensor to the fingertip of the little finger using a plaster / adhesive tape / insulating tape.

The heartbeat is recorded/registered particularly well when the sensor is located over a larger blood vessel - to improve the signal, we also recommend changing the position of the sensor on the fingertip if necessary.

Pin assignment

Cable color Cable meaning
Black Signal
Blue + V
Gray GND
Raspberry Pi Sensor
3,3 V [Pin 1] +V
GND [Pin 6] GND
- Signal
Sensor KY-053
Signal A0
+V -
GND -
Raspberry Pi KY-053
GPIO 3 [Pin 5] SCL
GPIO 2 [Pin 3] SDA
3,3 V [Pin 1] VDD
GND [Pin 6] GND

Analog sensor, therefore the following must be considered: The Raspberry Pi has, in contrast to the Arduino, no analog inputs or there is no ADC (analog digital converter) integrated in the chip of the Raspberry Pi. This limits the Raspberry Pi, if you want to use sensors, which do not output digital values, but a continuously changing value (example: potentiometer -> different position = different voltage value).

To avoid this problem, our sensor kit X40 contains the KY-053, a module with a 16-bit ADC, which you can use on the Raspberry to expand it with 4 analog inputs. This module is connected to the Raspberry Pi via I2C, takes over the analog measurement and transfers the value digitally to the Raspberry Pi.

So we recommend to connect the KY-053 module with the mentioned ADC in between for analog sensors of this set. You can find more information on the KY-053 Analog Digital Converter information page.

Code example

The program uses the corresponding ADS1x15 and I2C Python libraries from Adafruit to control the ADS1115 ADC. These have been published at the following link https://github.com/adafruit/Adafruit_CircuitPython_ADS1x15 under the MIT license. The required libraries are not included in the download package below.

With the help of the ADS1115 ADC, the program measures the current voltage value at the ADC, calculates the current resistance of the NTC from this, calculates the temperature with the help of values determined in advance for this sensor and outputs them to the console.

Please note that you must enable I2C on your Raspberry Pi before using this example.

import time
import board
import busio
import adafruit_ads1x15.ads1115 as ADS
from adafruit_ads1x15.analog_in import AnalogIn

# Create the I2C bus
i2c = busio.I2C (board.SCL, board.SDA)

# Create the ADC object using the I2C bus
ads = ADS.ADS1115 (i2c)

# Create single-ended input on channels
chan0 = AnalogIn (ads, ADS.P0)
chan1 = AnalogIn (ads, ADS.P1)
chan2 = AnalogIn (ads, ADS.P2)
chan3 = AnalogIn (ads, ADS.P3)

if __name__ == '__main__':

   
    # initialization
    GAIN = 2/3
    curState = 0
    thresh = 525 # mid point in the waveform
    P = 512
    T = 512
    stateChanged = 0
    sampleCounter = 0
    lastBeatTime = 0
    firstBeat = True
    secondBeat = False
    Pulse = False
    IBI = 600
    rate = [0] * 10
    amp = 100

    lastTime = int (time.time () * 1000)

    # Main loop. use Ctrl-c to stop the code
    while True:
        # read from the ADC
        Signal = chan0.value #TODO: Select the correct ADC channel. I have selected A0 here
        curTime = int (time.time () * 1000)

        sampleCounter + = curTime - lastTime; ## keep track of the time in mS with this variable
        lastTime = curTime
        N = sampleCounter - lastBeatTime; # # monitor the time since the last beat to avoid noise
       

        ## find the peak
        if Signal <thresh and N> (IBI / 5.0) * 3.0: # # avoid dichrotic noise by waiting 3/5 of last IBI
            if Signal <T: # T is the through
              T = signal; # keep track of lowest point in pulse wave

        if Signal> thresh and Signal> P: # thresh condition helps avoid noise
            P = signal; # P is the peak
                                                # keep track of highest point in pulse wave

         
          # signal surges up in value every time there is a pulse
        if N> 250: # avoid high frequency noise
            if (Signal> thresh) and (Pulse == False) and (N> (IBI / 5.0) * 3.0):
              Pulse = true; # set the pulse flag when we think there is a pulse
              IBI = sampleCounter - lastBeatTime; # measure time between beats in mS
              lastBeatTime = sampleCounter; # keep track of time for next pulse

              if secondBeat: # if this is the second beat, if secondBeat == TRUE
                secondBeat = False; # clear secondBeat flag
                for i in range (0,10): # seed the running total to get a realistic BPM at startup
                  rate [i] = IBI;

              if firstBeat:
                firstBeat = False;
                secondBeat = True;
                continue


              # keep a running total of the last 10 IBI values
              runningTotal = 0; # clear the runningTotal variable

              for i in range (0.9): # shift data in the rate array
                rate [i] = rate [i + 1];
                runningTotal + = rate [i];

              rate [9] = IBI;
              runningTotal + = rate [9];
              runningTotal / = 10;
              BPM = 60000 / runningTotal;
              print ('BPM: {}'. format (BPM))

        if Signal> thresh and Pulse == True: # when the values are going down, the beat is over
            Pulse = false;
            amp = P-T;
            thresh = amp / 2 + T;
            P = thresh;
            T = thresh;

        if N> 2500: # if 2.5 seconds go by without a beat
            thresh = 512; # set thresh default
            P = 512; # set P default
            T = 512; # set T default
            lastBeatTime = sampleCounter; # bring the lastBeatTime up to date
            firstBeat = True; # set these to avoid noise
            secondBeat = False; # when we get the heartbeat back
            print ("no beats found")

        time.sleep (0.005)

Sample program download

KY039-RPi.zip

Start with the command:

sudo python3 KY039.py

The heartbeat sensor detects the pulse when a finger is held between the infrared light-emitting diode and the phototransistor. The pulse can then be read at the signal output.

How a phototransistor works is as follows: A phototransistor works in a similar way to a normal transistor, with a higher current flowing through it the higher the control voltage applied to it. In a phototransistor, however, the incident light represents the control voltage. This means that the more light hits the phototransistor, the more current it lets through. In this sensor, the change in the amount of light caused by the blood flow in the finger is detected by the phototransistor and converted into a signal that can be used for pulse detection.

This sensor is ideal for projects where heart rate monitoring is required, such as in fitness trackers, health monitoring systems or other biometric applications.

If you connect a resistor in series in front of the transistor, the following behavior results when you measure the voltage across the transistor: If there is a lot of light shining on the transistor or if it is bright outside, you can measure a low voltage close to 0V - if the transistor is in the dark, it lets a relatively small current through and you measure a voltage close to +V.

The measurement setup with infrared diode and phototransistor built in this sensor module now allows us to measure the pulse by putting a finger between diode and transistor. Explanation: just as we know it from the flashlight, the skin on the hand can be shone through. If you hit a blood vessel, you can faintly see the blood pumping. This pumping can be recognized because the blood has a different density at different points in the vein and thus differences in brightness can be seen in the blood flow. Exactly these differences in brightness can be recorded with the sensor module and thus the pulse can be detected. This becomes clear when looking at the following oscilloscope image.

KY-039

This shows on the Y-axis the change of the voltage at the phototransistor - thus the brightness changes caused by the flowing blood. The peaks marked above thus result in the beating of the heart. If you now calculate the registered beats per recorded time, you get a pulse of about 71 beats/minute (bpm).

As you can see on the upper oscilloscope picture, the recorded signal is relatively small and the transistor is very sensitive to pick up the weak signal. To get an optimal result, we recommend to prepare the module for measuring as shown in the following pictures:

To increase the sensitivity of the sensor, we recommend fixing the sensor to the fingertip of the little finger using a plaster / adhesive tape / insulating tape.

The heartbeat is recorded/registered particularly well when the sensor is located over a larger blood vessel - to improve the signal, we also recommend changing the position of the sensor on the fingertip if necessary.

Pin assignment

Cable color Cable meaning
Black Signal
Blue + V
Gray GND
Micro: bit Sensor
3 V + V
GND GND
Pin 2 Signal

Code example

	
		let hrNo = 0
		let d = 0
		let c = 0
		let b = 0
		let a = 0
		let bpm = 0
		basic.showIcon(IconNames.Heart)
		serial.setBaudRate(BaudRate.BaudRate115200)
		pins.analogSetPeriod(AnalogPin.P2, 20000)
		let timer = 0
		let peakCount = 0
		basic.forever(function () {
		    for (let index = 0; index < 5; index++) {
		        timer += 1
		        basic.pause(1000)
		    }
		    bpm = peakCount / 5 * 6
		    timer = 0
		    peakCount = 0
		    serial.writeLine("BPM is " + Math.round(bpm))
		    basic.pause(1000)
		})
		basic.forever(function () {
		    a = pins.analogReadPin(AnalogPin.P2)
		    basic.pause(20)
		    b = pins.analogReadPin(AnalogPin.P2)
		    basic.pause(20)
		    c = pins.analogReadPin(AnalogPin.P2)
		    basic.pause(20)
		    d = pins.analogReadPin(AnalogPin.P2)
		    basic.pause(20)
		    hrNo = (a + b + c + d) / 4
		    serial.writeNumber(hrNo)
		    if (hrNo > 520) {
		        peakCount += 1
		    }
		    serial.writeLine("")
		})
	

Sample program download

microbit-KY-039.zip

The heartbeat sensor detects the pulse when a finger is held between the infrared light-emitting diode and the phototransistor. The pulse can then be read at the signal output.

How a phototransistor works is as follows: A phototransistor works in a similar way to a normal transistor, with a higher current flowing through it the higher the control voltage applied to it. In a phototransistor, however, the incident light represents the control voltage. This means that the more light hits the phototransistor, the more current it lets through. In this sensor, the change in the amount of light caused by the blood flow in the finger is detected by the phototransistor and converted into a signal that can be used for pulse detection.

This sensor is ideal for projects where heart rate monitoring is required, such as in fitness trackers, health monitoring systems or other biometric applications.

If you connect a resistor in series in front of the transistor, the following behavior results when you measure the voltage across the transistor: If there is a lot of light shining on the transistor or if it is bright outside, you can measure a low voltage close to 0V - if the transistor is in the dark, it lets a relatively small current through and you measure a voltage close to +V.

The measurement setup with infrared diode and phototransistor built in this sensor module now allows us to measure the pulse by putting a finger between diode and transistor. Explanation: just as we know it from the flashlight, the skin on the hand can be shone through. If you hit a blood vessel, you can faintly see the blood pumping. This pumping can be recognized because the blood has a different density at different points in the vein and thus differences in brightness can be seen in the blood flow. Exactly these differences in brightness can be recorded with the sensor module and thus the pulse can be detected. This becomes clear when looking at the following oscilloscope image.

KY-039

This shows on the Y-axis the change of the voltage at the phototransistor - thus the brightness changes caused by the flowing blood. The peaks marked above thus result in the beating of the heart. If you now calculate the registered beats per recorded time, you get a pulse of about 71 beats/minute (bpm).

As you can see on the upper oscilloscope picture, the recorded signal is relatively small and the transistor is very sensitive to pick up the weak signal. To get an optimal result, we recommend to prepare the module for measuring as shown in the following pictures:

To increase the sensitivity of the sensor, we recommend fixing the sensor to the fingertip of the little finger using a plaster / adhesive tape / insulating tape.

The heartbeat is recorded/registered particularly well when the sensor is located over a larger blood vessel - to improve the signal, we also recommend changing the position of the sensor on the fingertip if necessary.

Pin assignment

Cable color Cable meaning
Black Signal
White + V
Gray GND
Raspberry Pi Pico Sensor
3,3 V +V
GND GND
GPIO26 (A0) Signal

You can also use an ADC such as the KY-053. This ADC has a higher resolution than the internal ADC of the Raspberry Pi Pico, which means that the sensor can be analysed more accurately.

Code example

To load the following code example onto your Pico, we recommend using the Thonny IDE. All you have to do first is go to Run > Configure interpreter ... > Interpreter > Which kind of interpreter should Thonny use for running your code? and select MicroPython (Raspberry Pi Pico).

Now copy the code below into your IDE and click on Run.

# Import the libraries
from machine import ADC
import utime

# Use GPIO26 as analog input
analog_pin = ADC(0)  

# Initialization variables
raw_value = 0
max_value = 0
is_peak = False

# Function for detecting a heartbeat
def heartbeat_detected(ir_sensor_pin, delay_msec):
    global raw_value, max_value, is_peak
    
    result = False
    # Read the current voltage value (0 to 65535)
    raw_value = ir_sensor_pin.read_u16()
    # Adjust the value to the delay
    raw_value = (raw_value * 1000) // delay_msec 
    
    # Reset the maximum value if the difference is too large
    if raw_value * 4 < max_value:
        max_value = int(raw_value * 0.8)
    
    # Peak detection
    if raw_value > max_value - (1000 // delay_msec):
        if raw_value > max_value:
            max_value = raw_value
        # Only one heartbeat should be assigned to the detected peak
        if not is_peak:
            result = True
        is_peak = True
    elif raw_value < max_value - (3000 // delay_msec):
        is_peak = False
        # Here, the maximum value is lowered slightly with each run
        # is slightly reduced again. The reason for this is that
        # not only would the value otherwise always be stable with each stroke
        # would become the same or smaller, but also
        # if the finger should move minimally and thus
        # the signal would generally become weaker.
        max_value -= 1000 // delay_msec
    
    return result

# Delay in milliseconds per scan
delay_msec = 60
beat_msec = 0

print("KY-039 Heart rate measurement")

while True:
    heart_rate_bpm = 0
    if heartbeat_detected(analog_pin, delay_msec):
        # Only calculate heartbeat if beat_msec is not 0
        if beat_msec > 0:
            heart_rate_bpm = 60000 // beat_msec       
        # Only output the pulse if it is within the realistic range
        if 30 < heart_rate_bpm < 200:
            print("Pulse detected: {} BPM".format(heart_rate_bpm))
        # Reset after detection
        beat_msec = 0  
        
    utime.sleep_ms(delay_msec)
    beat_msec += delay_msec