Become a subscriber (Free)

Join 29,000 other subscribers to receive subscriber sale discounts and other free resources.
Don't worry -- your e-mail address is totally secure. I promise to use it only to send you MicroZine.

Easy Pulse Rate Sensor Project

This is a PPG or photoplethysmograph project – or pulse rate sensor using infrared reflected light. Yes I absolutely hate that word (photoplethysmograph) and have to think how to say it. But first, since it is such a ridiculous word what does that word mean? Photo added to the front end and From Greek Plethysmos (increasing,enlargement), graphos (to write). So it is a Light (Photo) Volume increasing (Plethysmos) written (Graph) measurement.


Photoplethysmography is a method for measuring the change in volume of an organ resulting from changes in the volume of air or blood it contains and then drawing it out in a graphical form. In this case you are measuring the operation of the heart by detecting reflected infrared light, from a fingertip sensor, which is changing due to the blood flowing in the finger.


From now on we'll just stick to PPG for the rest of this discussion!

This project uses a matched pair of infrared transmitter and receiver components (Phototransistor and LED) to produce a useful PPG signal that can be amplified using an opamp. The output is then converted using a microcontroller ADC and processed by the microcontroller to give a heart rate reading in beats-per-minute.

Warning: This project is not for medical use.

The originating signal is the most critical part of the project and to get a good signal requires the most appropriate sensor (a matched Infrared pair) used in reflection mode. You can arrange the sensors beside each other (reflection of IR detected) or one on top and one below the finger (transmission of IR light through the finger).

The pair of sensors in this project are used in reflection mode providing a useful output that is extremely sensitive to changes in reflected light from blood flow in the finger.

In fact the output is so sensitive that any standard light bulbs will trigger the output, just by waving your hand between the light and sensor) This is why professional devices use a light excluding cover to stop false triggering as you need that high sensitivity to get a good PPG output. I made up a simple little cardboard cover stuck together with a glue-gun!


At first I tried using the KY-039 device (very frustrating) as it's output is far too low to get useful operation; You need a gain of 10000, and even then you have to keep very still to allow the signal to just peak above the noise! (that is if it is positioned exactly right – really just luck).

Using and matched pair, and especially those that are designed to emit and receive Infrared light allows a far bigger signal that is not mired in noise and it only requires a modest nominal gain of 100. Basically do not use the KY-039 and don't waste any time on it.

Pulse Rate Sensor: Infrared TX, RX pair

For this project a matched pair of emitter 409 and receiver is used 309:

SHF409 – Emitter If (max) 100mA, and Vf 1.3V, Output : 950nm

SFH309 – Receiver Ic=15mA (surge 75mA,Vce 35V, Input Range : 380 ~ 1180nm, Max sensitivity 860nm

Note: You can use any similar matched pair of IR TX and RX Diode and transistor but note that the SHF409 emitter is capable of handling a maximum continuous current of 100mA (even though it is in a very small package) and that a standard remote IR TX diode is not capable of this current level. If you use a different IR transmitter then change the current limit resistor to stop blowing it up! i.e. check the datasheet for your device.

Warning: These devices emit highly concentrated invisible light which can be hazardous to the human eye. So cover the devices and do not put them near your eyes.

Pulse Rate Sensor Software Operation

The objective for the pulse rate sensor software is to detect peaks and record the number of peaks over a 10 second period (changeable in software) and then work out the heart rate in beats-per-minute. Detecting peaks requires assumptions about the incoming signal since a normal PPG has many troughs and valleys that have to be ignored.

One approach is to use a moving average filter to entirely smooth out the signal so that reduced data is processed i.e. smoothing out all those troubling noise fluctuations. That is slightly boring as it is interesting to see a more real-life signal (with interesting signal transitions) so in this project the output from the microcontroller is left intact i.e. the raw ADC outputs are used. Instead of smoothing, an algorithm is used to ignore the parts of the signal that are fluctuating all over the place.

The plot below uses the Serial Monitor of the Arduino IDE (opDebug is defined in the code) showing the variables in the software : rawValue (PPG – blue), max_peak (maximum peak PPG signal- red ) and avg (average – yellow – the left side shows the start of the measurement period where the average is high – and later settles to the “average” signal level):

Typical Pulse Rate Sensor PPG output (blue) with pulse detected (red line) and average (yellow)

heart rate sensor ppg waveform with average and peak

The algorithm has found the maximum peak (indicated by the red line at the top of the signal) and has stored this result in software (incrementing the variable num_peaks). Note how the top of the max_peak value is held for a time (set in definition DLY_IGNORE) which shows the rest of the algorithm ignoring the input signal for a set millisecond period (in this case 130ms) – a separate part of the program holds the peak value (max_peak variable) constant while this time passes (which is not strictly necessary but allows visualisation of the delay). After this, the max peak value is decayed away allowing the algorithm to find the next peak even if it is not as high as the previous one i.e. for an erratic pulse or when the signal is not as large if you move your finger away from the sensor etc.

Pulse Rate Sensor Detecting a PPG signal

If there is a real PPG signal the the difference between the 'max_peak' and 'avg' variables give a signal rejection method. In the software if the difference is below 60 ADC values set in definition NOISE_THRESHOLD (with 5V PSU this is 292mV) then the signal is considered to be noise and is therefore ignored. In the image above you can see that the signal peak that we are interested in (blue) is a long way above the average level (yellow).

The software effectively ignores anything below the noise threshold level i.e when no PPG signal is output the signal varies between 170 to 185 (ADC value) and is therefore ignored.

Pulse Rate Sensor Detecting the peak

Reliably detecting the signal peak requires a few calculated variables to process the incoming signal:

  • rawValue - current ADC reading.
  • up - 1 if we are traversing the signal on the way up, and 0 for going down.
  • raw_tot- total of all samples within the measurement period.
  • raw_num- number of samples within the measurement period.
  • avg, (from raw_tot and raw_num) – the average ADC reading (over the measurement period).
  • max_peak- Max ADC value in the measurement period (decayed down slowly).
  • threshold- max-peak less 1/8th of max_peak.

The simple idea for the software algorithm is to average out the incoming ADC samples and compare this value to the peak value. If the incoming ADC sample is over a certain threshold and the peak to average value is above a noise threshold then assume this is a heartbeat pulse.

Pulse Rate Sensor Example Code

Note: Clicking any text in the box below will copy the whole lot to the clipboard.

// Copyright John Main
#include <Wire.h>

#define sensorPin A0
#define buzzerPin 11
#define ledPin 13

// Coment out one of the following,
//#define opText  // For serial monitor
#define opDebug // For serial plotter

#define MEASURE_TIME 10000
#define DLY_IGNORE 130
#define PSEP Serial.print(" ")

int period = 0, cline = 0;
int num_peaks,max_peak,avg_peak;
unsigned long time_was,ctimer;
unsigned long peak_time;
unsigned long raw_tot,raw_num;
unsigned long threshold;

void setup() {

  pinMode(ledPin, OUTPUT); // Inbuilt LED

  Serial.println("Heart Rate Monitor");
  Serial.println("sec update...");

  peak_time = time_was = millis();
  num_peaks = 0;

  threshold = 0;
  raw_tot = raw_num = 0;
  ctimer = millis();

void loop() {
   static int up=1;
   unsigned long ms;
   int period=0;
   int avg = 0; // temp var.
   int rawValue = analogRead (sensorPin);

   raw_tot += rawValue;
   avg = raw_tot/raw_num;

   // Decay max peak to allow change to lower peak values.
   // debug plot (millis()-peak_time) - not algo. shows delay on plot = straight line.
   if ( (millis()-peak_time)>DLY_IGNORE ) max_peak--;

   // Capture max peak.
   if (rawValue>max_peak  ) max_peak = rawValue;

   // Set the threshold 1/8th below the max peak.
   threshold = (max_peak-(max_peak>>4));

#ifdef opDebug
   // Raw outputs for serial plotter
   Serial.print(rawValue);   PSEP;
   Serial.print(avg);        PSEP;
   Serial.print(max_peak);   PSEP;


#ifdef opText
   if ((millis()-ctimer)>1000) { // Confidence timer
      if (cline%61==0) Serial.println(); // Line wrap

   // Value must be > minimum,
   // and bigger than a fraction below the max measured peak,
   // and not found within x ms of the last peak (100ms :max 300bpm).
   if (up==1 && threshold > avg &&
               rawValue > threshold &&
               rawValue-avg > NOISE_THRESHOLD &&
                ) {
      ms = millis();
      peak_time = ms;
      up = 0;

      // Check n second timeout after a peak
      period = (ms-time_was);
      if (period>MEASURE_TIME) { // Output and restart if > n secs period finished.

#ifdef opText
         Serial.print(" sec: ");
         Serial.print(" n: ");
         Serial.print(" peak: ");
         Serial.print(" num peaks ");

         // Since this is not an interrupt driven timer, nominal n sec period will be larger than 5000 ms.
         // so work out beats in period for 1min
         Serial.print(" BPM ");
         Serial.println(num_peaks * (60000/(float)period));

         time_was = ms;
         num_peaks = 0;
         max_peak = 0;
         avg = 0;
         cline = 0; // Don't need newline as output is generated.


      // Indicators


   } else if (up==0 && rawValue<threshold)  up = 1;

Pulse Rate Sensor Code operation

In the main loop the sequence of operations is as follows:

Get an ADC sample : rawValue

Update raw_tot and raw_num and calculate avg.

If the time since a peak was last found is greater than DLY_IGNORE then decay max_peak.

Capture the new max_peak value if the ADC value is greater than the current rawValue.

Work out the threshold value (max_peak minus 1/8th).

Print values if opDebug is defined.

Print confidence output if opText is defined.

Work out if we have found a peak i.e. when:

The threshold is greater than the average signal and...

The rawValue is greater than the threshold and...

The difference between the rawValue and average is > the noise level and...

We are not in an ignore signal period.

If the above is true then ask the question:

has the measurement period ended?

If yes then display BPM. And reset period measurement variables.

If this is not a peak then check for down traversing ADC value:

by checking that

We found a peak – when up is set to 0 by the peak detection calculation and

and that the rawValue has fallen below the threshold (definitely going down).

If the above is true the set up to 1 to find the next peak signal

Pulse Rate Sensor Hardware

The pulse rate sensor hardware consists of the matched Infrared Transmitter and receiver pair and an AC configured opamp amplifier. The opamp used is an older chip. A more modern chip can go rail to rail but the CA3140 goes only to about 2.6V for a 5V supply. If you don't have a CA3140 use a more modern opamp (I have a few of these older devices and just used that type). A better choice would be an MCP601 as this has a higher specification and is rail-to-rail output although rail-to-rail output is not strictly required for this project to operate. What ever you do make sure that the datasheet states that the device can operate on a single supply i.e. 0 to 5V.

Note however that a higher spec. opamp would not improve the circuit too much, as the frequencies involved are extremely low and the gain roll-off is dominated by the filter values (1uF,470k,100k). Noise performance would be improved a little though.

In fact you don't need rail-to-rail operation (since the circuit operates just fine without that). The full ADC range is not used but the circuit provides enough range for useful output using between ¼ to ½ of the ADC range depending on the input signal i.e. how your finger is located over the sensor.

Note: You could feed in a reference voltage to the Arduino to allow the full ADC range over the 2.6V (with a few adjustments to the ADC code) which would the give finer resolution. However the most important aspect of the circuit is obtaining a good signal in the first place and that depends on the IR matched pair.

ppg ac amplifier circuit

Note: Also connect a piezo speaker across Arduino Uno pin 11 and GND.

Pulse Rate Sensor D.C. Bias for the AC opamp

The resistor R3 and R4 set the d.c. bias the level for the opamp at 5.0 *(100.0/(100.0+470)) = 0 .877V to raise the input signal level above zero volts. This bias is not amplified (since the a.c. Amplifier configuration only allows changing current to pass) so this level is passed straight to the output.

Note: This is also why the Vos parameter of the opamp can be ignored.

This is done primarily to locate the PPG signal for viewing in the Serial Plotter. If you look back at the PPG plot and at the yellow line (the average), this is also the average D.C. level and it has a value of about 180 ADC counts which is : (5/1024.0)*180 = 0.878V.

You could just forget about this D.C. Level (but it looks so much better on the output). The other reason for the unbalanced values of R3 and R4 is that the output of the CA3140 is only able to go up to 2.6V so this D.C. Level also keeps the signal from hitting the top voltage.

If you used a rail-to-rail opamp you would be more likely to make a design with equal R3 and R4 to set the D.C. Level to the power supply mid point.

Pulse Rate Sensor High Pass Corner Frequency : C1, R3, R4

The output from the IR receiver is fed into a 1uF capacitor and then to the level setting voltage divider R3 and R4 (see above for the bias level). This capacitor and the two resistors in parallel form a high pass RC filter with a corner frequency of :

1.0/(2*3.14159*82e3*1e-6) = 1.94Hz.

Frequencies above this are amplified, below this they are reduced.

This is a bit high for a PPG (really 0.15Hz is needed – check professional specifications for exact frequency ranges - but you would need a larger capacitor or smaller resistors). However the chosen corner frequency gives good enough results.

Pulse Rate Sensor A.C. Coupling capacitor : C4 R6

The A.C. amplifier uses an a.c. Coupling capacitor value 10uF (C4). High frequency signals are passed through but at low frequency a.c. is blocked all the way down to d.c. The higher this capacitance value the lower the corner frequency above which signals are amplified.

This is the problem with the A.C. amplifier as the frequencies involved are very low so you need a large coupling capacitor to let the desired signal pass through i.e. a low corner frequency. In this case this is not really the action you want from the amplifier; You want gain at lower frequency but the capacitor reduces it at lower frequency so to allow lower frequency the corner frequency must be lowered (by either increasing R or C).

The solution is to make this capacitor large. It then provides a corner frequency (R6 and C4) of:

1.0/(2*3.14159*1e3*10e-6) = 15.91 Hz

Frequencies above this frequency are amplified, below it they are reduced. It is not really a low enough corner frequency but it allows the signal to be seen quite well anyway.

Pulse Rate Sensor Extra noise removal C3, R5

Electrolytics, as with all capacitors, are not ideal so C3 provides an extra smoothing by reducing gain at high frequencies (C3 and R5). The corner frequency is:

1.0/(2*3.14159*1000e3*100e-9) = 1.59Hz

Above this frequencies are reduced due to gain reduction (partly smoothing the output signal = good).

Pulse Rate Sensor Parts List

Item Part Description IDs




























Arduino Uno

Piezo Disc

Ceramic Capacitor >6V


Electrolytic capacitor

Light emitting diode

Light detecting diode

Output pin







Operational Amplifier

Microcontroller board



C2 C3














Note: C1 - this must not be an electrolytic – it must be non polarising i.e. does not have a polarity.

Pulse Rate Sensor Limitations and improvements

The pulse rate sensor algorithm as it stands provides a simple and interesting BPM result and waveform output but does use interrupts. Instead the code goes round a loop checking whether 10000 ms has passed and if it has it will calculate the BPM using the actual time that has passed and this will be greater than 10000 ms (depending on how much debug data is output). Since the time is known accurately that measurement is still good enough to show the correct rate. As it is measured over 10 seconds the BPM value will vary slightly but is broadly accurate.

You could average out several readings for a more stable reading but of course that takes more time. Another approach would be to measure the peak to peak times and display an BPM value - this would also require averaging the output (the readings would vary over time) but would give an output instantaneous output

New! Comments

Have your say about what you just read! Leave me a comment in the box below.

Claim Your: Useful

"Arduino Software Guide"

   Right Now...

Privacy Policy | Contact | About Me

Site Map | Terms of Use

Visit our Facebook Page:

   Click Here

Recent Articles

  1. Hitachi HD44780: A very Useful Guide to using 1602 LCD displays.

    Use an Hitachi HD44780 as a simple display for any project.

    Read more

  2. Easily make an IR Pulse Rate Sensor with one opamp, an Arduino, and a matched Infrared phototransistor and LED pair.

    How to make a Pulse Rate Sensor using a simple single opamp circuit with an Arduino and a few other components.

    Read more

  3. 74HC595

    74HC595 : How to add nearly unlimited outputs to any microcontoller.

    Read more

Sign up for MicroZine
''The'' Microcontroller Newsletter

Enter your first Name and primary email address in the form below:

And receive absolutely FREE a full project for:

"Measuring Analogue Voltages
Without An ADC"

(Using only one pin).

Instant Download:
You Can
Get It Right Now

Warning: This project could be  Removed 
at any time.  

It will  NOT be 
available indefinitely SO
To avoid 
disappointment  get it:


Don't worry -- your e-mail address is totally secure. I promise to use it only to send you MicroZine
Remember this is a project with full description and fully debugged C Source code - and it's not available from the main website.

You can only get it through this newsletter.

To get exclusive access Enter your first name Name and primary email address Now in the form above.:

But wait !

There's more...

You'll receive more
free and exclusive reports as well as site information and site product updates

Scroll up to the form above and sign up NOW. Don't forget it's FREE and if you don't like it, you can unsubscribe at any time.

Click Here Now to use the form above to get your Valuable information absolutely free.

Readers Comments

"I wanted to thank
you so so so much
for all the information
you have provided in
your site it's


- Ranish Pottath

"This site really is
the best and my favorite.
I find here many useful
projects and tips."

- Milan


"Awesome site,
very, very easy and nice
to navigate!"

- Matt

Learn Microcontrollers

"Interested in

Sign up for The
Free 7 day guide:


"I am a newbie to PIC
and I wanted to say
 how great your
site has been for me."

- Dave


"Your site is a great
and perfect work.

- Suresh


"I couldn't find the correct
words to define
yourweb site.

Very useful, uncovered,
honest and clear.

Thanks so much for
your time and works.

- Anon