Arduino Tutorial: KY-039 Heartbeat Sensor Module
Abstract
Learn how to use the KY-039 Heartbeat Sensor Module with Arduino. This module measures the change in blood volume under the skin using an infrared light source and phototransistor—a technique called Photoplethysmography (PPG). This tutorial focuses on configuring an Analog Pin to read the variable signal and processing it to detect the pulse, ultimately calculating the Heart Rate (BPM).
1. Introduction
The KY-039 is a simple optical pulse sensor. It works by shining a light (usually infrared) onto a fingertip or earlobe and measuring the amount of light that is reflected or transmitted back to the detector. Since blood absorbs light, the tiny volume changes in the blood vessels caused by the heart’s pulse create a small, periodic variation in the detected light signal. This variation needs to be read as an Analog Input and then carefully processed.
In this episode, you’ll learn:
- The basics of Photoplethysmography (PPG).
- How to read the noisy, continuous pulse signal using analogRead().
- The necessity of filtering and baseline analysis to stabilize the reading.
- A basic method for counting pulses over a period to calculate BPM.
This project provides real-world biometric data acquisition using simple hardware.
2. Prerequisites
Make sure you have:
- An Arduino Uno or compatible board.
- One KY-039 Heartbeat Sensor Module.
- Jumper Wires.
- Arduino IDE
3. Wiring and Setup for Arduino
The KY-039 module has three pins and requires an analog input pin.
Step 1 – Identify Pins
The module has three pins: Signal (S or OUT), VCC (+), and Ground (− or GND).
Step 2 – Connect the Module
Wire the module to the Arduino as follows. The signal pin must be connected to one of the Analog Input Pins (A0-A5).
- Connect the – / GND pin of the KY-039 to the GND pin on the Arduino.
- Connect the + / VCC pin of the KY-039 to the 5V pin on the Arduino.
- Connect the S / Signal pin of the KY-039 to Arduino Analog Pin A0.
This image was created with Fritzing
Step 3 – Sensor Placement
The sensor works best when firmly, but not tightly, held against a fingertip. Any motion or excessive pressure will introduce significant noise, making pulse detection extremely difficult.
4. Writing Pulse Detection Code
Calculating BPM requires timing the peaks and troughs of the signal. The provided code implements a simplified, non-blocking peak-detection method.
Open main.ino and implement the following code.
const int PULSE_PIN = A0;
// Variables for timing and pulse detection
volatile int BPM; // Heart Rate (Beats Per Minute)
volatile int signal; // Analog reading from the sensor
volatile int IBI = 600; // Inter-Beat Interval (average milliseconds between beats)
volatile boolean Pulse = false; // True when a pulse is detected
long lastBeatTime = 0; // Used to time the pulse interval
int THRESHOLD = 550; // Threshold to detect a beat (adjust based on raw reading)
int P = 512; // Peak signal value
int T = 512; // Trough signal value
void setup() {
Serial.begin(9600);
Serial.println("KY-039 Heartbeat Sensor Ready. Place Finger...");
}
void loop() {
signal = analogRead(PULSE_PIN); // Read the analog signal (0-1023)
// 1. Adaptive Thresholding (Simple Baseline Tracking)
// T will track the lowest point (trough), P will track the highest (peak)
if(signal < T) {
T = signal; // Trough found
}
if(signal > P) {
P = signal; // Peak found
}
// Dynamically calculate the detection threshold (halfway between peak and trough)
// This helps adapt to noise and ambient light changes
THRESHOLD = (T + (P - T) / 2);
// 2. Pulse Detection (Checking for a falling edge across the threshold)
// We detect the start of the beat as the signal drops below the threshold (after the peak)
if (signal > THRESHOLD && Pulse == false) {
// Pulse detected: signal just crossed threshold
// Time the beat interval
long now = millis();
IBI = now - lastBeatTime; // Calculate time since last beat
lastBeatTime = now;
// Calculate BPM from IBI
BPM = 60000 / IBI;
// Set flag to prevent double counting while signal is above threshold
Pulse = true;
Serial.print("BPM: ");
Serial.print(BPM);
Serial.print(" | IBI: ");
Serial.print(IBI);
Serial.print("ms | Threshold: ");
Serial.println(THRESHOLD);
}
// 3. Reset Pulse Flag
// If the signal drops below the threshold, reset the flag to wait for the next beat
if (signal < THRESHOLD && Pulse == true) {
Pulse = false;
}
// 4. Resetting Peak/Trough for continuous tracking
// Slowly reset T and P back to 512 to adapt to signal changes
P -= 1;
T += 1;
P = constrain(P, 512, 1023);
T = constrain(T, 0, 512);
delay(10); // Short delay for loop control
}
Code Explanation
- signal = analogRead(PULSE_PIN): Reads the raw data. This value typically varies between 400 and 700.
- Peak/Trough Tracking: The T (Trough) and P (Peak) variables continuously track the lowest and highest points of the signal.
- THRESHOLD Calculation: The dynamic threshold is set halfway between the peak and trough. A beat is detected when the signal crosses this threshold.
- Inter-Beat Interval (IBI): Measured in milliseconds (millis()). The time between two consecutive beat detections.
- BPM Calculation: BPM=60000/IBI (since there are 60,000ms in a minute).
5. Uploading and Running the Project
Step 1 – Build
Click the Verify button (checkmark icon) in the Arduino IDE to compile the sketch.
Step 2 – Upload
- Connect your Arduino board via USB.
- Select the correct board and COM port.
- Click the Upload (arrow icon) button.
Step 3 – Test
- Open the Serial Monitor (Tools > Serial Monitor).
- Place your finger gently on the sensor’s LED/Phototransistor pad. Keep your hand still.
- Wait a few seconds for the T and P values to stabilize and capture the pulse.
- The Serial Monitor should display the calculated BPM and IBI for each detected pulse. Note: Accuracy is highly dependent on stillness and ambient light.
6. Hands-On Lab Recap
You’ve learned:
- The principle of Photoplethysmography (PPG).
- How to handle a noisy analog signal from a sensitive sensor.
- The technique of adaptive thresholding for signal detection.
- The calculation to convert the Inter-Beat Interval (IBI) into Heart Rate (BPM).
This concludes the series on basic KY-series modules, equipping you with comprehensive I/O skills.
7. Common Issues & Fixes
| Issue | Cause | Solution |
|---|---|---|
| Readings jump wildly or display 0. | Movement or Ambient Light Noise. | Ensure your finger is still and the sensor is shielded from bright light. Place a piece of dark material over the sensor/finger. |
| BPM is fixed at 60000. | No beat detected, IBI remains at the initial 600ms or BPM calculation is flawed. | The sensor is likely outputting a constant value. Check the raw signal reading; if it's outside 400-700, adjust the THRESHOLD starting point. |
| Sensor seems to detect the pulse, but BPM is too low/high. | Threshold is too high or too low for your finger/light level. | Watch the raw signal reading. If it hovers around 600, a THRESHOLD of 550 might be too high. Manually tweak THRESHOLD until detection stabilizes. |


