Heartrate Interface For Arduino

Bob Harper, code by Mike Hansell

Issue 5, November 2017

This article includes additional downloadable resources.
Please log in to access.

Log in

Need to check you're still alive? This project will help (unless you're a robot).

The Arduino, and all other microprocessors, are pretty useless without a method to get information into their computer brain, and then out again. In fact, without Inputs and outputs, computers are just expensive heaters. This project is aimed at showing you how to interface your own body with a computer.

“Hello Body”

Although humans can hear the human heart beating, and can usually tell if it’s beating fast or slow, and can even count the beats against a clock, or at least a watch, there are a lot of inventors out there creating instruments like the Fitbit and it’s mates, to monitor the athlete’s heartbeat. So this project aims at explaining one method of creating an instrument to monitor your exercise. We will help you interface your heart to your computer, so you can decide what you want to do with this information.


The heart pumps blood around the body and if we over exert it, well, let’s try not to. That means that the blood is pushed into the corners of the body causing the flesh to fill with blood one moment and drain away another. You may have even seen your fingers pulsing with blood on a cold winter day. So we are going to use that change of colour to detect heart beats.

The method is called “Photoplethysmography” which means using light to detect blood and recording it’s flow. Our method uses an Infra Red Light Emitting Diode, IR-LED, to illuminate the blood as it is pumped into a finger or earlobe, or other thin body part. Most detectors pass light through the flesh, but ours can work on reflectance alone, so it does not require any clip or clamp.

An Infra-Red Photo-Transistor, IR-PD, then detects the pulses as a voltage change, and that signal is filtered and amplified until it is strong enough to flash an LED, or be squared off to drive an I/O pin on a micro-controller.

Finally, that signal, now digital, is converted by the Arduino or such to be displayed as “Beats Per Minute”. Of course, that’s our mission; you may prefer to graph your heart rate over the period of exercise, or sound an alarm if you go over your optimal workout rate, or whatever you can think of.


There is no peace when a teacher is about. The project is based on an LM324N Quad Op-Amp Integrated Circuit. That’s four amplifier circuits each as complex as an old audio amplifier. In fact the circuit for an Operational Amplifier is very similar to the classic transistor audio amplifier, and not a lot different to even older valve amplifiers. The Op-Amp was originally designed as an element used in Analog Computers.

What is important to us is that the Op-Amp is designed to have two inputs, one inverting, and for the lack of a better name, one non-inverting. Any signal let into the non-inverting input would come out the output pin in the same sense as it went in. That means the same phase, or ‘the same way up’.

As you might expect by now, a signal entering the inverting input will come out inverted, upside down! Just to confuse you a little, the output is proportional to the difference between the inputs, and that’s the input current, not voltage.

Op-Amps have a very high input impedance, which means it doesn’t take a lot of current to make them operate, and 25 nano-Amps at each input is typical of the older Op-Amps such as the LM741.

What’s even more special about Op-Amps, is they have a very high gain. That means that very little change to the input causes a very large change in the output. That same old LM741 has a gain value around 100dB, or an amplification of 10,000,000,000:1.

In fact most Op-Amp circuits are designed to avoid such high gain as the outputs would be always in the red! i.e. always slammed against the power supply rails. To avoid this, op-amps require feedback, another trick they learned from audio power amplifiers.

Feedback returns some of the output signal back to the inputs, particularly the inverting input which makes it negative feedback. Now you may know what it’s like to get negative feedback, and that it’s usually meant to reduce some over-energetic behaviour on your part. It’s the same for Op-Amps.

The design gain of an Op-Amp can be determined by two resistors, or in one special case by a single wire from output to the inverting input, making the gain value of one!

So why would anybody want an amplifier with a gain of one? Our project has one of it’s amplifiers set to a gain of one, so rather than have any amplification of voltage, it has a current gain. The input current of some nano-amps, can be used to drive the maximum rating of the Op-Amp output, perhaps 100mA under some conditions.


The feedback can also be filtered to use the amplifier to remove unwanted aspects of a signal, such as noise, or drift. Filtering uses capacitors, or in some cases inductors. Our project has four filters built using two of the four Op-Amps. When the filters are used in the feedback circuit the Op-Amp is said to become an Active Filter, and the filtering effect is greater than a filter that is not-active, i.e. a passive filter.

So let us go through the circuit stage by stage.


Referring to the circuit diagram, or schematic shown above, D1 is a LED generating an Infra-Red spectrum of light, that is particularly good at lighting up blood, or more accurately, the Iron in the blood. R1 limits the current flow in the LED so it doesn’t burn out, which would be an issue as we cannot usually see Infra-Red. We’ll explain how to when we build the circuit.

R2 also limits the current in the D2, which is really a transistor with only two legs. The Base of the transistor isn’t available for us as it has no connection. Rather, the base-emitter and base-collector junctions are turned on by photons of Infra-Red light instead of base current. So R2 is really a load resistor for D2 as a transistor, and the voltage found between D2 and R2 is a function of the IR light entering the window to the junctions of D2.

That voltage representing the pulse is fed to the non-inverting input of amplifier 1, LM324, pin 3 after passing through a filter formed by C1 and R3, C1/R3 form a High Pass Filter, (HPF), which can be calculated from a formula, f = 1/(2 π R C), or in this case 1/(6.283 x 47E3 x 4.7E-6) = ~0.73Hz which seemingly says it treats anything below 0.7 x 60 = 42 beats per minute as not a heartbeat. In fact, it doesn’t cut of completely at 0.7Hz, but the gain is reduced from 0.7 Hz down. This filter is to cut out DC drift at very low frequencies.

Amplifier one also has a filter in the feedback circuit formed from C2 and R5, and R4. Note that R5 and R4 have a ratio of 100:1. C2 is also a part of the feedback so this time the feedback is a filter. Using the same formula as for C1R3 above calculates the critical frequency as 2.3 Hz.

So the gain begins to be reduced as the value of C2 becomes more important to the amplifier gain than the value of R5, beginning at a frequency of about 2.3 Hz. That is above 138 Beats Per Minute. Once again we have to state that the filter reduces the gain beginning at that heart rate. It doesn’t suddenly stop above the critical frequency. In fact the gain will be reduced to a gain value of 1:1, at a heart rate of about 1400 BPM. I don’t think we would ever measure that except maybe for a humming bird in deep stress.

So Stage one has a gain of about 100:1, and has both a High Pass Filter (HPF) at 2.3 Hz to filter out higher frequency noise, and a Low Pass Filter (LPF) at 0.7 Hz to filter out DC drift.

If you compare you will see that stage two is exactly the same circuit. Gain values are always multiplied, so together, two gains of 100:1 become a total gain of 10,000:1, for heart beat detections from about 42 to 138 BPM.

A gain of 100:1 in electronics is known as 20dB or 20 deci-Bells. Gain values given in dB are added rather than multiplied, so a gain of 10,000:1 is a gain of 40dB, i.e. 20db + 20dB.In simple terms, a deciBell is one tenth of a Bell, which is not normally used, and a Bell is a gain of 10, so 40dB is four Bells or a gain of 10^4.

The trimpot, or variable resistor, VR1, allows us to reduce the gain for people with thin or pale skin, and increase the gain for people with thick or darker skin, or low blood flow. Medical devices may have “Automatic Gain Control”, which is another type or level of negative feedback, but we live in the “Keep It Simple” world so maybe another time?

The third amplifier has a more modest gain that can be calculated from (R9 +R10)/R10, which gives you a gain of 11:1. After this point the pulses are no longer analog, but are not quite digital either, so amplifier 3 is really to sharpen up the sides of the pulses.

Here you can see the "rough" pulses received from the device, as well as the sanitised output.

The Oscilloscope trace image shows, from top to bottom, the outputs from each of the four amplifiers. So the top trace is already amplified by a factor of 100, and has also had filtering to remove DC and almost all of the visible noise. The peak value of the signal at Pin 1 is about 130mV, so at 100 times the input, the input would have been a 1.3mV difference, which is why it it a little touchy until you figure out just how to place your finger.

The second trace is on pin 7 where the signal is amplified by another 100x, and the same filtering, so although it looks similar it is smoother and the peak value is about 6.5 volts, but why not 100 x 130mV = 13 Volts? Well we only have a 9 volt battery, and the trimpot was adjusted to get a good pulse, so we are not using full gain.

The signal at pin 14 is for amplifier three appears as trace three, and with another gain of 11x you see it is almost a square wave, and could be presented to some microcontrollers with suitable internal protection, but to be sure, we used amplifier four and the following resistor and diode.

Amplifier four has a very simple gain to calculate, and however you go about it, you should get an answer of 1:1. i.e. the output voltage is equal to the input voltage. The purpose of amplifier four is to maintain the voltage, but increase the current available. The circuit is called a “Buffer” because it buffers the load away from the previous stage.

Finally we come to a resistor, R12, which is there to absorb the energy of any voltage above the break over voltage of the zener diode D4. Zener diodes have a useful property of having a very predictable break over voltage. In this case your choices are:-

  1. none, leaving the output voltage maximum, at almost the supply voltage.
  2. 5.1volt, 1N4733 which is OK for most TTL circuits and commonly used.
  3. 4.7volt, 1N4732 which is a little safer on TTL at 5 volts.
  4. 3.3volt, 1N4728 which is good for 3.3volt TTL and should work most 5v TTL circuits.

The zener diode limits the voltage on JP1 to protect the inputs to your precious Arduino or other development board. You can see this in practice in trace four, set on pin 8, showing the output voltage of 5.1 volts because I used a 5.1 volt zener. I regret to admit I didn’t have a 4.7volt zener handy!


Ref :Components:JaycarAltronics
R1220R Resistor RR0556 R7542
R239k Resistor RR0610 R7596
R3 47k Resistor RR0612 R7598
R4 6k8 Resistor RR0592 R7578
R5 680k Resistor RR0640 R7626
R6 47k Resistor RR0612 R7598
R7 680k Resistor RR0640 R7626
R8 6k8 Resistor RR0592 R7578
R9 68k Resistor RR0616 R7602
R10 6k8 Resistor RR0592 R7578
R11 6k8 Resistor RR0592 R7578
R12 220R Resistor RR0556 R7542
VR1 5k0 Cermet Variable Resistor RT4648 R7606
C1 4u7 Electrolytic RY6806 R5048
C2 1nF MKT 100V RM7010 R3001B
C3 4u7 Electrolytic RY6806 R5048
C4 1nF MKT 100V RM7010 R3001B
D1 IR LED ZD1945 Z0880
D2 IR Photo Transistor ZD1948 Z1613
D3 1N4733 5.1V Zener ZR1403 Z0614
LED1 Red 3mm LED ZD0100 Z0700
IC1 LM324N Quad Comparator ZL3324 Z2524
Ref : Other Hardware:
PWR 9V Clip and Lead
JP1 2-pin Header Socket
PCB DIY1710-1 Dims 56 x 31mm
Case DIY1710-1-3D Finger Clip (Optional)

The full list of components and parts appears above, however a few comments are required.


The IR-LED and the associated IR-Tx must have a similar center wavelength and sufficient bandwidth to work together. The pair in the Parts List have been tested and proven to work together, but other types and mounting styles will also work, if you match their functional spectrum. It's also worth noting that the PCB isn't the best location for them. We have designed a 3D printed finger cup that holds the LED and photo transistor. This provides a much better result. It provides good alignment for the two devices, while also helping to prevent light pollution providing interference.

The Zener diode can be chosen to meet your intended use, therefore three diodes are listed but only one is required. If unsure, use the 3.3 volt Zener, for the safest option, as the output is greater than the 2 volt requirement for TTL complying hardware.

Power is supplied by a simple 9v "Transistor" Battery, however other options may be tried. The various resistors are calculated for 9 volts, so higher or lower voltages may require re-calculating some values, or failures may result.



This circuit can be built on a breadboard, proto-board or strip-board, or a home made pcb used. The pattern is purposely made as a single sided Pattern, with larger pads for most components, and wider tracks, and the gaps have been increased to make the “DIY” PCB possible for school or TAFE projects, or keen home builders.

Check the circuit board for shorts and cracks. Use a multimeter on the continuity (beeper) setting and test tracks from near end to the far end for continuity. Then test adjacent tracks to ensure they are not unintentionally joined. If you finds any faults, they are easier to find and fix now rather than when all components are populated (i.e. installed and soldered into place).

Connect the battery clip or battery pack if you prefer, making sure you have the correct polarity. Note that a power switch hasn’t been used, but you can add one in the positive battery wire if you like. A good insurance test is to install the battery, and test the power pads against the ground pads. The battery voltage should be measured across every pair.

Note: You can measure every pad to the battery -ve pad (GND), and then from the battery +ve pad to every ground pad if you prefer. The board is single sided, so you might like to test it upside down so you can see the tracks.

Install all resistors, including the variable resistor, or external pot as you prefer, first taking great care to read each resistor’s colour bands, (good way to learn what they mean) or to measure them on an Ohm-meter, (Great way to become lazy.) For reading them later, always install resistors, and any other component with colour bands so the bands read from left to right, or top to bottom. It will make fault finding easier, if you need to fix anything.

Next install the capacitors making sure you have the polarity of electrolytic capacitors correct, and the correct values in the correct places.

Install the diodes and the IR photo-transistor (labelled as D2), but NOT the IC yet. We will add that last. Now to test that the circuit is correct and the correct components are in the correct placements. We have included a NodeTest.pdf in the digital resources for you to compare with your pcb. Each node is identified and one pad of that node used to test the voltage on that node.

The IR-LED and IR-PD should be placed so the LED is straight, and therefore the IR light faces up through the top of the LED, and the photo-transistor flat side should be facing the LED from the side, and looking over it. i.e. stick them straight in, polarity correct, so the sensitive window of the photo-transistor looks across the top of the LED.

Connect the battery and look at the LED through your Digital Camera, or phone camera, and you should see the LED glow. Make sure you are looking from above the LED and the room light level is not too bright. If necessary, find a shadow.

Disconnect the battery, and add the IC. You can socket it if you like but you should never need to ever replace the IC.

Testing the Heart Pulse Monitor

The IR-LED and the IR-PD have a different active face. The LED radiates upwards through the domed end, and the IR-PD is sensitive to infrared light through the flat side, so two possible methods of sensing the blood can be used, Transmissive where the light shines through flesh, and reflective where infrared is reflected off the blood.

Either method can be managed with the Heart Pulse Monitor, and some discussion remains over which approach is best. It seems that both work with the same circuit.

For a reflective test the LED and IR-PD are soldered so the detector faces across the top of the LED. Without the finger there, the infrared light should pass the IR-PD without triggering it. When the finger is present, some light will pass through the finger and some will be reflected back into the IR-PD causing the Base Emitter junction to conduct when the light energy ejects electrons from atoms.

These electrons not only pass through the BE junction to ground, but in the process, allow electrons from the collector to pass which is the basis of the amplification. When the pulse of blood reduces the amount of reflected infrared, the BE current flow is reduced and the CE current is also reduced, by a greater proportion, due to amplification.

In the transmissive use, a finger cuff is used, either fabricated from a large peg, or paper clip, or 3D printed using the file on the DIYODE web page. If it looks like a Minion, and you print it yellow, and draw two eyes on it, well, your call.

Place the LED diode facing inwards, and place the IR-PD with the flat face facing the finger, and wire both using flexible hookup wire to the PCB. Make sure that both are in place and if you prefer, hot glue them in place. Now your Minion looks more like a Borg!

You are ready to test the circuit. Turn it on, or simply connect a battery, and place a finger on the LED-PD set, or in the finger cuff, and adjust the trim-pot for a nice rhythmic pulse.

If you push too hard, you squeeze blood out of the finger and it won’t work, and similarly if you are not in close contact the infrared won’t be detected or daylight may flood the sensor. Using the 3D printable finger-cup and installing the sensor/LED set on flyleads will definitely improve accuracy.


Thanks to Mike Hansell for doing what I think is the hard work, coding the project. We worked on a simple plan. Rather than trying to count heartbeats over a minute, which would mean having a display once every minute, we set about counting the period of each heart pulse in milliseconds.

There are 60 x 1000 milliseconds in a minute, so dividing 60,000 by the time of each beat results in a number of beats per minute.

We have provided the complete sketch heartrate_display_v1.ino in the digital resources. Download the file, load it into Arduino IDE, and compile the sketch onto your UNO.


As we are using an LCD display for the output of the Heart Beat Monitor we need to include LiquidCrystal.h which is built-in so we don’t need to import anything. In the sketch we have:

#include <LiquidCrystal.h>
const int rs = 12, en = 11, d4 = 6, d5 = 5, 
  d6 = 4, d7 = 3; // allow pin 2 to be
// used for heartbeat interrupt
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
  // Create a variable of type LCD. 
  // allows us to display text on LCD.

This defines the Arduino pins that will be used to communicate with the LCD display and the 2nd line creates an object that we can use to control the LCD display.

// Initialise serial comms at FAST rate
  while (!Serial) { ; }
// Wait for Serial to become ready

The setup() is pretty standard. Using the serial methods for debug or general communication is very helpful so it is setup first. While testing code to give reasonably accurate timing we found that printing debug info at higher baud rates (115200 vs 9600) chewed less CPU time. It is documented that from the Arduino’s point of view, printing via the serial device is just a matter of passing the data to a buffer. Control of actually transmitting that data is then handled automatically. Your experience may differ.

  pinMode(PinPulseInput, INPUT);
  attachInterrupt(digitalPinToInterrupt(PinPulseInput), GotABeat, RISING)
// Use interrupts to ensure we will 
// see heartbeat signal from HBM

As we are monitoring the HBM via an interrupt, we set that up.

screen working
  lcd.begin(16, 2);
// Set up the LCD’s number of columns and rows

Tell the LCD device that it has 16 character on each of 2 lines. As we may not yet have any heartbeat pulses to process, we show that we have ‘Started...’.

The loop() code:

// This won’t stop heartbeats being processed 
// but ensures that the display will be 
// updated twice per second
  if (BPM < 20) Serial.println("BPM < 20. It appears you are dead."); 
// If BPM is 0 then no heartbeart detected. 
// Please we are joking with you!
// Show BPM on LCD
  BPM = 0;

As the sketch is driven by the heartbeat pulses, it will do nothing (delay(500)) until a heartbeat signal is received. This loop() does not process the heartbeat signals but it does update the display twice each second to show what is going on (lost signal etc).


This is the “heart” (sorry, poor pun intended) of the sketch. It is an Interrupt Service Routine (ISR). Use of interrupts and their associated ISR’s is discussed below. When a heartbeat signal is detected, regardless of the sketch’s delay(500) in the loop(), this code will be executed. It is time critical that the signal is processed immediately to ensure accurate BPM readings.

  int CurrentMillis, PulseTime;
  CurrentMillis = millis();
  PulseTime = CurrentMillis - PreviousMillis;         
// Time between pulses in ms
  Serial.print("PulseTime = ");
  Serial.println(" ms");

Each time through this function (IE each heartbeat), we use the millis() to determine how long the sketch has been running, then deduct the previous millis time from the current millis value. This then is the time in milliseconds since the previous heartbeat, if any.

  BPM = 60000 / PulseTime;
// BPM = no. of ms in a minute / time between beats in ms
  PreviousMillis = CurrentMillis;
// Keep a copy for next heartbeat
  Serial.print("GotABeat() - current bpm = ");
  // Average last 5 BPMs
  PreviousBPMs[0] = PreviousBPMs[1];
  PreviousBPMs[1] = PreviousBPMs[2];
  PreviousBPMs[2] = PreviousBPMs[3];
  PreviousBPMs[3] = PreviousBPMs[4];
  PreviousBPMs[4] = BPM;
  Average = (PreviousBPMs[0] + PreviousBPMs[1] + PreviousBPMs[2] + PreviousBPMs[3] + PreviousBPMs[4]) / 5; // Average the last 5 BPM values

We then compute the BPM by dividing the number of milliseconds in 1 minute (60000) by the time between pulses. Again we use Serial.print() to show us that the sketch is functioning properly. As an extra we compute the average of the last 5 BPM readings.

  tone(9, 500, 50); 
// Heartbeat tone - piezo on pin9, 
// frequency 500Hz for 50ms

We can make a squawk at each heartbeat via a piezo buzzer if it’s connected. The current BPM and the average is updated on the LCD.


This code comments describe what is going on here pretty well, however, driving the LCD can do with some discussion.


The prototype of this project uses a 2 x 16 LCD. Most of these are rather similar and are based around the Hitachi HD44780 (or a compatible) chipset, which will all use the same library to run.

A library of functions (LiquidCrystal) is used to provide easy manipulation of these panel LCDs. This library provides amongst other functions, cursor positioning, display clearing, display blanking and printing text on the display. It is necessary to put the cursor at the position (line and column) where the text is to appear. Attempting to display more than 16 characters per line will simply truncate the text, unless you enable autoscroll() which isn’t necessary for this project.

Note: It is important to understand that the 1st line of the LCD is line 0. The 2nd line is line 1. You can see this here:

lcd.setCursor(0, 1);


Interrupts and their associated Interrupt Service Routine (ISR) are commonly used where timing or capturing random events is required. The Arduino has a function, attachInterrupt() through which we can define which input pin will generate the interrupt, the name of the function to call, to service the interrupt, and a condition that will trigger the interrupt.

(PinPulseInput), GotABeat, RISING);

The syntax is digitalPinToInterrupt(pin, ISRname, condition). The conditions are: LOW to trigger the interrupt whenever the pin is low, CHANGE to trigger the interrupt whenever the pin changes value, RISING to trigger when the pin goes from low to high and FALLING for when the pin goes from high to low.


This isn't about to replace a hospital grade, or even many pharmacist-available heart-rate detectors. However sometimes it's fun to experiment with this sort of thing. It also demonstrates the use of light blocking pulses, which have all sorts of other applications too.

All in all, there are some good techniques in both the Heart Pulse Detector, and in the code to use the data you have collected. Your challenge is to come up with ways to personalise this project.

The project has potential as the source for a science bio project, training aid, health science experiments, sports data logger, perhaps a heartbeat controlled appliance – fan?

Perhaps you have some "dirty" pulse data you can use this project for, that has nothing to do with heart rates at all!