If a finger is held between the infrared light-emitting diode and the photo transistor, the pulse can be detected at the signal output.

The way a phototransistor works is explained as follows: This usually works like a normal transistor - the higher the control voltage that is applied to it, the higher the current is allowed to pass through it. In the case of a phototransistor, however, the incident light represents the control voltage - the higher the incident light, the higher the current that is let through.

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 a lot of light shines on the transistor or if it is bright outside, you can measure a low voltage close to 0V - that's that Transistor in the dark, it lets a relatively small current through and a voltage close to + V is measured.

The measurement setup with infrared diode and phototransistor built up in this sensor module now enables us to measure the pulse by placing a finger between the diode and transistor. Explanation: just as you know it from the flashlight, the skin on the hand can be x-rayed. If you come across a blood vein when x-raying, you can see the pumping of the blood very faintly. This pumping can be recognized because the blood has a different density at different points in the vein and thus differences in brightness in the blood flow can be seen. Exactly these differences in brightness can be recorded with the sensor module and thus the pulse can be recognized. This becomes clear when looking at the following oscilloscope picture.


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

As you can see on the oscilloscope image above, the recorded signal is relatively small or the transistor is very sensitive to be able to record the weak signal. In order to get an optimal result, we recommend to prepare the module for measuring as shown in the following pictures:

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

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

pin assignment

Cable assignment

Cable color Cable meaning
Black Signal
Blue + V
Gray GND

Code example Arduino

Pin assignment Arduino

Arduino Sensor
Pin A0 Signal
5V + V
Mass GND
//////////////////////////////////////////////////// //////////////////////
/// Copyright (c) 2015 Dan Truong
/// Permission is granted to use this software under the MIT
/// license, with my name and copyright kept in source code
/// http: // http: //opensource.org/licenses/MIT
/// KY039 Arduino Heartrate Monitor V1.0 (April 02, 2015)
//////////////////////////////////////////////////// //////////////////////
// German Comments by Joy-IT
//////////////////////////////////////////////////// //////////////////////
/// @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 ("Heartbeat detection sample code.");
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);
    // Serial data output
    Serial.print ("Pulse detected:");
    Serial.println (heartRateBPM);
    beatMsec = 0;
  } else {
    digitalWrite (ledPin, 0);
  delay (delayMsec);
  beatMsec + = delayMsec;

Sample program download


Code example Raspberry Pi

Pin assignment Raspberry Pi

Raspberry Pi Sensor
KY-053 A0 Signal
3.3V [pin 1] + V
Ground [pin 6] GND
Sensor KY-053
Signal A0
+ V 3.3V [pin 1]
GND Ground [pin 6]
Raspberry Pi KY-053
GPIO 3 [pin 5] SCL
Gpio 2 [pin 3] SDA

Analog sensor, therefore the following must be observed

In contrast to the Arduino, the Raspberry Pi has no analog inputs or there is no ADC (analog digital converter) integrated in the Raspberry Pi chip. This limits the Raspberry Pi if you want to use sensors where digital values are not output [voltage value exceeded -> digital ON | Voltage value fallen below -> digital OFF | Example: button pressed [ON] button released [OFF]], but it should be a continuously variable value (example: potentiometer -> other position = different voltage value)

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

We therefore recommend connecting the KY-053 module with the ADC in between for analog sensors in this set. You can find more information on the information page for the [KY-053] (https://sensorkit.joy-it.net/sensors/ky-053) Analog Digital Converter

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;

              # 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


To start with the command:

sudo python3 KY039.py

Code example Micro: Bit

Pin assignment Micro: Bit:

Micro: bit Sensor
3V + V
Mass GND
Pin 2 Signal

This is a MakeCode example for Micro: Bit which essentially does the same as the examples of the other two variants. However, this example is fundamentally slower than the other two variants. In this example it should be noted that the box that says "output serial number" must be removed after setting the "if" query below, as this only exists for this one purpose and is therefore only required when the sensor is used for the first time should. Therefore, it can easily be removed from the code without risk after the first use.