Summary
The number of contractions of the heart is expressed in beats per minute (BPM). There are several ways to measure BPM. The oldest method is manually, with a finger sensing pulses at the wrist and a chronometer for the timing.
Today’s smart watches and other gear use the principle of photophletysmography (PPT): blood absorbs most light shining through the skin into the underlying tissue while a fraction of the light will return. Light absorption, especially that of green light, depends on the amount of blood inside the tissue. Under normal conditions each contraction of the heart forces a wave of blood into the capillaries of the skin that, in turn, causes extra absorption of light during that period. The regularity in the fluctuation of this absorption can therefore be considered as representing the heart beat. An electronic heart pulse sensor uses a green led that continuously emits light into the skin and a photosensor that detects the returning, nonabsorbed light and (thus) its fluctuations. Because the amount of returning light and the fluctuations in the absorbance are minute, electronics is present on the sensor’s print board to do preprocessing. The entire assembly has the size of a 10 Eurocent coin (Figure 1).
Figure 1: Heart pulse sensor and its wiring to an Arduino Nano.
Output of the sensor consists of a fluctuating voltage: an analogous signal. Wiring to the Arduino is for that matter very simple: power (best is to use the 3.3V pin on the Arduino), GND and a signal wire to one of the analogous pins A0 through A5 on the Arduino. Most common is to connect the signal wire of the sensor with pin A0. However, because the TFT display we are planning to use needs pin A0 we further use pin A5 as signal pin for the pulse sensor.
Basic electronics required:
Arduino Nano, heart pulse sensor, Dupont type wires, led.
Very basic: sensor plus Arduino
An assembly consisting of a sensor and an Arduino microcontroller board, as shown in Figure 1 is the most simple and economic way to measure a pulse. Visual feedback is derived from the information displayed by Serial Monitor. Furthermore the led flashes every time when teh Arduino detects a pulse signal. The sketch, ‘basic_heart_pulse” uses the millis() timer function to count pulses during a 10-second period. It tries to distinguish a true ‘pulse’ by applying a threshold value (550) for the analogous signal. Any signal strength higher than that (arbitrary) threshold value is recognized as a ‘pulse’. Although this is an utterly primitive way to count pulses, it works.
In my experience the best place to position the sensor is on or close to a fold in the skin of a finger on the palmar side of the hand. The ear lobe might do as well. Anyway, you need to search for the best spot to position the sensor. Don’t apply too much pressure. Getting a stable output of the sensor is half the work.
sketch
// basic_heart_pulse
// works with one heart pulse rate sensor
// Floris Wouterlood May 2018
//
// detects suprathreshold pulses for 10 seconds, then multiplies with 6
// data from sensor to pin A5
// led at pin 10 flashes when a suprathreshold pulse is registered
// sketch for Arduino Uno / Arduino Nano
// public domain
// variables
int sensorPin = A5; // select the input pin for the potentiometer
float sensorValue = 0; // variable to store the value coming from the sensor
int count = 9;
unsigned long starttime = 0;
int heartrate = 0;
boolean counted = false;
void setup (void) {
pinMode (10, OUTPUT); // for the signaling led
Serial.begin (9600); // start Serial Communication
}
void loop (){
starttime = millis();
while (millis()550 && counted == false) // counted boolean is extra check
{
count++;
Serial.print (“count = “);
Serial.println (count);
digitalWrite (10, HIGH);
delay (50);
digitalWrite (10, LOW);
counted = true;
}
else if (sensorValue < 550)
{
counted = false;
digitalWrite (10, LOW);
}
}
heartrate = count*6; // multiply by six to get pulse per minute
Serial.println ();
Serial.print (“BPM = “);
Serial.println (heartrate); // report heart rate in Serial Monitor;
Serial.println ();
count = 0;
}
Figure 2. The Arduino test bench counting BPM
Need for additional electronics
Visual feedback in any form is necessary during pulse sensing because successful measurement of the BPM is highly dependent on the position of the sensor on the right spot. Having a visual clue in this search phase can be very helpful. In the basic design the flashing led attached to pin 10 provides this clue. While the basic design uses the Arduino’s user interface Serial Monitor for output, a more attractive design shows pulses and BPM on a display. In this example we use an Arduino workbench equipped with a 3.5’ 320×480 color TFT display with ILI9341 controller
The wiring and construction of this workbench has been described in detail in a previous article, ‘10 – Arduino analog test bench‘. A small adaptation of this original workbench was made by the addition of a female pin header connected to the 3.3V pin of the Nano. The wiring diagram of this adapted workbench is shown in figure 3.
Because the TFT display requires 16 of the available pins of the Arduino, only a few are available for the real work. These are pins A5, D11 through 13 and the TX and RX pins. A5 is used here to connect the data wire of the heart pulse sensor (figure 2).
Working with a TFT display makes the sketch more complicated. On the other hand the led in the basic design that lights up with every registered pulse can be replaced with a more atttactive visual clue on screen, here a yellow dot that appears every time a pulse is recognized.
Figure 3. Wiring of the Arduino test bench
For the TFT display I wrote two sketches: one named ‘type-1 sketch’ that is based on the basic sketch above, counting pulses for 10 seconds and then displaying the BPM by multiplying with 6. Because the BPM displayed in this way is always a multiple of 6, I wrote another sketch (‘type-2 sketch’) that counts 10 pulses while keeping track of the time passed after the first pulse has been recognized. After 10 counts the scipt calculates the BPM based on passed time between the first and the tenth pulse. This provides a better estimate of the BPM than the 10-seconds method (type-1 sketch). With an average person’s heart rate of 60-70 beats per minute a type-2 measurement takes about 6 seconds before the display provides a BPM estimate. First, a good spot on the finger or ear lobe has to be found where to position the sensor for getting a reliable signal. The yellow dot on the TFT display that appears and disappears synchronously with the pulse is a very useful assistant in this search phase.
Advanced, ‘type-2 sketch’
// TFT-3-5-heart-pulse-type_2
//
// Floris Wouterlood May 2018
//
// this sketch monitors one heart pulse meter
// stores millis () at first registered pulse
// and millis () at the 10th registered pulse
// then calculates and displays heart rate from time difference
// analogous signal via pin A5
// sketch for Arduino Uno / Arduino Nano
// display on a 3.5 inch 480×320 pix TFT display_with 0x9341 controller
// uses mcufriend_kbv library by David Prentice
// public domain
#include <TFT_FastPin.h>
#include <TFT_ILI9341.h>
#include <SPI.h>
#include <Adafruit_GFX.h> // hardware-specific library
#include <MCUFRIEND_kbv.h> // hardware-specific library
// ========== setup the TFT =======================================
MCUFRIEND_kbv tft;
#define LCD_CS A3 // Chip Select goes to pin A3
#define LCD_CD A2 // Command/Data goes to pin A2
#define LCD_WR A1 // LCD Write goes to pin A1
#define LCD_RD A0 // LCD Read goes to pin A0
#define LCD_RESET A4 // can alternately connected to Arduino’s reset pin
#define BLACK 0x0000 // define human names to some 16-bit colors
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define MAGENTA 0xF81F
#define YELLOW 0xFFE0
#define WHITE 0xFFFF
#define ORANGE 0xFBE0
#define GREY 0x5AEB
// ======== variables =============================================
int sensorPin = A5; // select the input pin for the potentiometer
float sensorValue = 0; // variable to store the value coming from the sensor
int Threshold = 550; // higher than this value the signal is considered a true pulse
byte veryfirstone = 0;
int Signal; // holds the incoming raw data. Signal value can range from 0-1024
int pulse = 0;
unsigned long starttime = 0;
unsigned long endtime = 0;
int heartrate = 0;
int cursorx = 0; // cursor positioning x coordinate
int cursory = 0; // cursor positioning y coordinate
void setup() {
Serial.begin (9600); // open serial communication
// ========== identify the TFTs controller =============================
uint16_t ID;
ID = tft.readID(); // valid for Uno shields
Serial.begin(9600);
Serial.println (“============================”);
Serial.println (“heart pulse sensor on pin A5”);
Serial.println (“============================”);
Serial.println (” “);
Serial.println (” “);
Serial.println (“starting 3.5 inch TFT”);
Serial.print (“controller ID = 0x”);
Serial.println (” “);
Serial.println (ID,HEX);
tft.begin (ID);
tft.setRotation(3); // 1 = landscape – 0 = portrait
// =========== initialization message TFT screen =======================
tft.fillScreen (BLUE);
tft.setCursor (10, 10);
tft.setTextColor (WHITE); tft.setTextSize (1);
tft.println (“initializing. . . . . . .”);
delay (500);
tft.fillScreen (BLUE);
// ======== draw rectangles magenta borders for messages and blip ======
tft.drawRect ( 30,195,265,70,MAGENTA); // for the pulse indicator
tft.drawRect ( 30,50,265,70,MAGENTA); // version and messages indicator
tft.drawRect (310,50,140,215,MAGENTA); // for the heart rate counter
tft.drawLine (30, 45,450, 45,MAGENTA); // decorative line
tft.drawLine (30,280,450,280,MAGENTA); // decorative line
tft.drawLine (30,282,450,282,MAGENTA); // decorative line
cursorx = 55; cursory = 20; // text “heart rate meter”
tft.setCursor (cursorx,cursory);
tft.setTextSize (2);
tft.setTextColor (YELLOW,BLUE);
tft.prin t(“heart rate meter”);
cursorx = 55; cursory = 60; // text “type 2 sketch”
tft.setCursor (cursorx,cursory);
tft.setTextSize (2);
tft.setTextColor (GREEN,BLUE);
tft.print (“type 2 sketch”);
cursorx = 55; cursory = 90; // text “times 10 pulses for BPM calculation”
tft.setCursor (cursorx,cursory);
tft.setTextSize (1);
tft.setTextColor (YELLOW,BLUE);
tft.print (“times 10 pulses for BPM calculation”);
cursorx = 55; cursory = 100; // text “needs a steady, regular pulse”
tft.setCursor (cursorx,cursory);
tft.setTextSize (1);
tft.setTextColor (YELLOW,BLUE);
tft.print (“needs a steady, regular pulse”);
cursorx = 55; cursory = 222; // text “pulse indicator”
tft.setCursor (cursorx,cursory);
tft.setTextSize (2);
tft.setTextColor (YELLOW,BLUE);
tft.print(“pulse: “);
cursorx = 340; cursory = 15; // text “BPM”
tft.setCursor (cursorx,cursory);
tft.setTextSize (3);
tft.setTextColor (YELLOW,BLUE);
tft.print (” BPM”);
cursorx = 55; cursory = 300; // text “FGW – May 2018”
tft.setCursor (cursorx,cursory);
tft.setTextSize (1);
tft.setTextColor (YELLOW,BLUE);
tft.print(“FGW – April 2018”);
Serial.print (“Heart pulse”);
Serial.println (“===========”);
}
void loop() {
pulse = 0;
subroutine_00 (); // detect very first real pulse to set startime
while ((pulse >0) && (pulse<11))
{
subroutine_01(); // detect signal
subroutine_02(); // time the tenth pulse and then calculate heartrate
// debugger(); // uncomment for debugging – void in subroutines
}
veryfirstone = 0;
delay (50); // stabilizing delay
}
// ============ subroutines ===========================
void subroutine_00() // detect signal and set countedFlag byte
{
while (pulse Threshold)
{
pulse = pulse+1;
veryfirstone = 1;
starttime = millis();
subroutine_03();
}
}
}
void subroutine_01() // detect signal and set countedFlag byte
{
Signal = analogRead(sensorPin); // read PulseSensor value and assign to Signal paramater
veryfirstone = 0;
if (Signal > Threshold)
{
pulse = pulse+1;
subroutine_03 ();
}
else if ((Signal < Threshold) or (Signal = Threshold))
{
}
}
void subroutine_02() // calculate heartrate
{
if (pulse == 10)
{
endtime = millis ();
heartrate = (600000/(endtime-starttime));
subroutine_04 ();
delay (50); // stabilizing delay
}
else
{
heartrate = 0;
}
}
void subroutine_03 () // yellow dot visual pulse display
{
cursorx = 200; cursory = 230;
tft.fillCircle (cursorx, cursory, 25,YELLOW);
delay (200);
cursorx = 174; cursory = 203;
tft.fillRect(cursorx, cursory, 55, 58, BLUE);
}
void subroutine_04 ()
{
cursorx = 315; cursory = 120; // indicates on TFT whether there is a pulse detected
tft.fillRect (cursorx, cursory, 125, 75, BLUE);
cursorx = 350; cursory = 150; // indicates on TFT whether there is a pulse detected
tft.setCursor (cursorx,cursory);
tft.setTextSize (5);
tft.setTextColor (YELLOW,BLUE);
tft.print (heartrate);
Serial.print (“heartrate = ===============”);
Serial.println (heartrate);
}
void debugger() // for debugging purposes – uncomment call in void loop
{
Serial.print (“signal strength = “);
Serial.println (Signal);
Serial.print (“pulses = “);
Serial.println (pulse);
Serial.print (“veryfirstone = “);
Serial.println (veryfirstone);
Serial.print (“starttime = “);
Serial.println (starttime);
Serial.print (“endtime = “);
Serial.println (endtime);
Serial.print (“endtime – starttime = “);
Serial.println (endtime-starttime);
Serial.print (“heartrate = “);
Serial.println (heartrate);
Serial.println ();
Serial.println ();
}
Using the sensor with Processsing
If Processing is installed on your computer (https://processing.org) you have powerful tools to produce on your computer monitor graphical output of the pulse sensor. The Processing user interface is similar to the Arduino’s in terms of structure: there is a setup function followed by draw functions. The main advantage is that Processing communicates fast with the Arduino through serial communication, and that the computer’s graphical power is used to produce graphical output. Your computer screen then acts as heart pulse meter terminal screen!
Figure 4. Screen capture of a Processing heart pulse sensing session with the Nano and the pulse sensor.
scripts
The package “arduino_heart_pulse_meter.zip” contains the following sketches:
basic-heart-pulse.ino
TFT-3-5-heart-pulse-type-1.ino
TFT-3-5-heart-pulse-type-2.ino
and for the Processing environment:
arduinopart-pulse-meter-sketch.ino
processingpart-pulse-meter-sketch.pde