Project tutorial
Detecting Heart Rate with a Photoresistor

Detecting Heart Rate with a Photoresistor © MIT

A photoresistor can sense the subtle changes in light intensity caused by your blood to detect your heartbeat!

  • 46 respects

Components and supplies

Apps and online services

About this project


This project was inspired by a YouTube video posted by CapitanoRed, where the author made a photoresistor-based heart rate monitor to display on an oscilloscope. After seeing their video, I wanted to replicate the project and add an Arduino to calculate and display the heart rate. That way you could look at the waveform on an oscilloscope while also getting a readout of your heart rate.

When the heart contracts and pushes blood around the body, the momentary fluctuations in blood pressure are detectable. This is the reason we can feel our pulse. In areas of the body where the skin and flesh is thin enough, these pulses can be detected in the slight variations of light that passes through. While our eyes aren't sensitive enough to even see the light passing through our body, not to mention the fluctuations, photoresistors do have this level of sensitivity.

A photoresistor varies its resistance with the intensity of light that is falling on it. Although the variation in resistance can be quite small for subtle changes in lighting intensity, they can be amplified using a couple of operational amplifier ICs.

Principles of Operation

The diagrams below show the schematic of the circuit as well as the breadboard used in this project.

The photoresistor is in a voltage divider with the 20kΩ resistor, which means that as the amount of light on the resistor increases, the voltage at the divider also increases. The first operational amplifier ("Amp1" in the image above) serves as a filter for the voltage divider, removing high-frequency noise from the signal. The second op amp ("Amp2") serves as an inverting amplifier, set to maximize the signal coming through the filter. The third operational amplifier ("Amp3") sets the virtual ground, which centers the signal at 2.5V. This ensures that the op amps are able to provide maximum signal swing from 0V to 5V. After being filtered and amplified the signal looks something like this.

In order to allow the Arduino to measure your heart rate, the signal needs to pass through a comparator ("Cmp" in the schematic). A comparator is a specialized op amp that is designed to output either a high or low signal. When the voltage at the positive input is greater than the negative input the comparator outputs high, and when the voltage at the positive input is less than the negative input the comparator outputs low. In its most basic configuration a comparator serves as a threshold detector, signaling when the measured voltage is above or below that threshold. Since a comparator outputs either high (5V) or low (0V), it's perfect for interfacing with an Arduino's digital pins.

Taking a closer look at the image of the signal coming from the op amp, it's clear that there is a secondary pulse before the voltage goes down (this is called the dicrotic notch). Additionally, there is a significant amount of noise in the signal. These two facts mean that basic comparator configuration will not be able to properly detect pulses. Instead of a single square pulse for every heartbeat, the comparator will generate several pulses. This will occur because noise will cause the signal to cross the threshold several times on the upswing and downswing, and, depending on where the thresholds are set, possibly during the dicrotic notch. This will result in the Arduino counting many more pulses than were actually present.

A noisy signal can be dealt with by using hysteresis. This document by Texas Instruments presents an excellent discussion on the topic of signal conditioning with hysteresis, shows how signal noise affects comparator performance, and how to deal with these issues. I used their schematics and derived equations (pages 5 and 7, respectively, of the document) to design the asymmetrical comparator for this project. The overall idea is that a feedback loop from the output will change the voltage at the positive input, which will mean that the threshold to go from low to high will be different than the threshold to go from high to low. What this means in the context of heart pulses is that the comparator can be set to trigger at one point on the upswing, and then at a different point on the downswing, preferably after the dicrotic notch. This way the Arduino will see a single square pulse for each heartbeat, as shown below.

Preparation of the Photoresistor

I highly recommend using some sort of clear cover on the photoresistor, such as clear heat shrink. At the very least, make sure the leads are completely covered to avoid touching them to your skin. The voltages generated by your body are well within the range that the photoresistor is creating when it detects your pulse, so contact with your skin could skew the results.

Tuning the Heart Rate Monitor

For the potentiometer in the schematic (R7), use a single-turn potentiometer and adjust it to maximize the op amp's gain without going into saturation. Start by setting one side of the potentiometer to approximately 375Ω and connecting the filter ("Amp1") to this side. This amount of gain should generate enough of a pulse that you'll be able to see it in WaveForms Live. After following the steps in the next section ("Viewing Pulses in WaveForms Live") and successfully viewing your pulse, you can change the gain if necessary. Increase the pulse amplitude by turning the potentiometer to make the filter-side resistance smaller. If the amplitude is already too large and causing clipping, make the filter-side resistance larger. Read the captions on the images that follow to determine what a desirable signal looks like.

I recommend using multi-turn potentiometers for resistors R3 and R4 in order to precisely set the thresholds for the comparator. Resistor R5 can be any resistor in the 10-100kΩ range, as long as it is precisely measured. You can use the spreadsheet in the attachments section to determine what values to set R3 and R4 to based on the measured value of R5 and the threshold voltages. The thresholds "Vl" and "Vh" will need to be changed based on the pulse you'll see through the oscilloscope (see the section titled "Setting Comparator Thresholds").

Resistors R8 and R9 can be replaced by one potentiometer, with the middle pin connecting to the positive input of the op amp. This way the virtual ground can easily be adjusted without having to find matching resistors. Use a voltmeter or the OpenScope while adjusting the output to be 2.5V.

Viewing Pulses in WaveForms Live

In order to view your heartbeat through WaveForms Live, you'll need to change some settings in the menu. By default, the output will either be stretched out and difficult to interpret, or the update speed will be very slow and it will be difficult to adjust your grip on the photoresistor to generate clear pulses.

Connect the OpenScope oscilloscope Channel 2 (blue wire) to the output of "Amp2" and make sure the ground wire is connected to the ground on the breadboard. In WaveForms Live change Time to "1s" and in the Trigger menu press the OFF button. For both Osc Ch 1 and Osc Ch2 menus set Offset to 2.5V and next to Samples click the lock icon and type "1000" into the field that becomes available. This will make the signal appear on a timescale that is easy to interpret but will make the updates occur more often than they would by default. The screen should update every 4 seconds or so. If this is still too slow you can increase the Samples value, but at the expense of a shorter snippet of the signal ("2000" will capture about one heartbeat at a time).

Press the RUN button and take your pulse with your finger on the photoresistor. You'll need to figure out the best way to get consistent results. The system is very sensitive to changes in pressure, so you need to find a way to keep your finger very still. I found the best place to take my pulse was in the first joint of the index finger. It takes a little bit of time to train yourself, but eventually you'll find the best method. If your pulses are looking too small, adjust the potentiometer as instructed in the first paragraph of the previous section ("Tuning the Heart Rate Monitor").

Setting Comparator Thresholds

Once the signal is visible on the oscilloscope, you'll need to set the thresholds at which the comparator will trigger and signal the Arduino. Get a representative waveform and stop the capture to keep the waveform on the display. At the bottom of the screen press the CURSORS button. Under Type select "Voltage" and set both CursorChannels to "Osc 2". Two horizontal dashed lines will appear on the display. Drag the triangles on the left side to move them. Set one of the lines at a point near the peak of the pulse, and the other at a point below the dicrotic notch. Look at the bottom of the screen and record the two voltages shown in the parentheses. Enter these values as the threshold voltages "Vl" and "Vh" in the spreadsheet attached at the end. The smaller value will be "Vl" and the larger one will be "Vh". Based on these values and the resistor R5 value you chose, set the potentiometer R3 and R4 values that the spreadsheet calculates.

After the comparator is set, it should start outputting a signal resembling the one before the section "Preparation of the Photoresistor."

Arduino Code

The Arduino code consists of a frequency counter and a method for calculating the heart rate in beats per minute. The frequency counter takes into account the width of the pulse coming from the comparator and rejects anything less than 200 milliseconds or greater than 800 milliseconds. This will prevent it from displaying false data when the photoresistor is not being used for measurements and the comparator may be high, low, or rapidly switching between the two states. The code keeps a running average of the heart rate over the previous 15 seconds in order to filter out missed pulses due to signal noise caused by accidental movements.

Future Improvements

This project's main drawback is that it's difficult to hold the photoresistor in such a way that the pulses are the same amplitude between uses. Since the sensor depends on ambient light for pulse detection, changing light levels throughout the day can cause different results. I noticed that on cloudy days my comparator thresholds were too wide, and on brightly-lit days the dicrotic notch was very pronounced and could cause false pulse detection. In addition, even subtle light changes caused by movement in the room are detectable by the photoresistor. The issue of consistency could be addressed in a few different ways.

The sensor system could include an LED to provide consistent light. This would resemble the heart monitoring clips used at doctor's offices or the heart rate monitors seen in cell phones. I tried holding a red LED against the top of my finger with the photoresistor on the other side. The results were encouraging, so this is probably a viable option if a good enclosure could be made.

On the software side, a potential solution would be to use an auto-ranging algorithm in the Arduino. It would detect the pulses and find their peaks and troughs. However, this wouldn't be enough to perform a heart rate measurement. A proper frequency counter requires interrupts. Without interrupts, the Arduino's processor might be doing something else besides checking the input pins and miss a pulse. Since interrupts are only available on digital pins, the best way to implement the auto-ranging would be through the use of digital potentiometers. The Arduino would use the analog pin to find the range that the pulse is appearing in, determine what the comparator thresholds should be, apply them via the digital potentiometers, and then use the digital signal from the comparator to perform the frequency count.


comparator calcs.xlsxArduino
This Excel spreadsheet will calculate the R3 and R4 values to set asymmetrical thresholds for the comparator. You can change the R5 column to a measured resistor value. Vl and Vh need to be changed to whatever thresholds are necessary to get a clean pulse from the heart rate.
No preview (download only).
Arduino Heart Rate CounterArduino
This code will calculate your heart rate based on pulses sent to it by the comparator.
#define INT0 3
float frequency;
long timeCount;
int counter;
long pulseStart;
int inputPin = 3;
boolean low;
float freqAvg;
float total;

void setup() {
  attachInterrupt(digitalPinToInterrupt(INT0), intrruption, CHANGE);
  counter = 0;
  timeCount = 0;
  frequency = 0;
  low = false;
  total = 15;

void loop() {
  timeCount = millis();
  while(millis() - timeCount < 5000){
    frequency = counter;
  if(frequency > 3){
    total += frequency;
    total -= freqAvg;
    freqAvg = total/3;
  counter = 0;


void intrruption(){
  if(digitalRead(3) == 0){
    low = true;
  else if(digitalRead(3) == 1){

void fallDetect(){
  pulseStart = millis();

void widthCheck(){
  long pulseEnd = millis();
  if((pulseEnd - pulseStart > 200) && (pulseEnd - pulseStart < 800) && low){
    low = false;

void showHR(){
  Serial.print("Heart rate = ");
  Serial.println(freqAvg * 12);


Pulse Detector Fritzing Schematics/Breadboard


Similar projects you might like

Heart Rate Monitoring System

Project tutorial by HRMS

  • 41 respects

Heart Rate Monitor Using IoT

Project tutorial by Technopaths

  • 41 respects

Measure Heart Rate and SpO2 with MAX30102

Project tutorial by SurtrTech

  • 256 respects

Heart Rate Monitor (Wearable and Wireless Using ECG)

Project tutorial by Dmitry Dziuba

  • 71 respects

Photoresistor Characterization

by 3 developers

  • 17 respects
Add projectSign up / Login