Building a Basic Heart Rate Monitor

Theo Chemel

Piezo

Arduino

OLED Display

HR Monitor

In this starter project, we will build a simple heart rate monitor that displays the user's current heart rate and beeps whenever a pulse is detected.


BOM

We will use several components from your MakerKit, including:

  1. The Arduino Uno R3
  2. The 128x64 Monochrome OLED Display
  3. The Heart Rate / Blood Oxygen Sensor Module
  4. The Piezo Buzzer
  5. The Breadboard
  6. The Dupont connector cables

Make sure you have all of these supplies organized and ready to use before continuing. Also, you will need to have installed the Arduino IDE - see the Introduction to Arduino resource.


Wiring

First, connect all of the components using your breadboard and Dupont connector cables as shown in the schematic above. The Piezo buzzer can be very loud, so you may want to place something on top of it to muffle the sound. Using properly colored connector cables can help organize your circuit and prevent mistakes. Double check that the red 5V cables and black GND cables are connected to the right places, to avoid damaging any components. Once everything is properly connected, you can load the following code onto your Arduino with the Arduino IDE.



Installing Libraries

This project requires some software libraries to interface with the OLED screen and the heart rate monitor. In the Arduino IDE, click on Tools and then on Manage Libraries. Search for

  1. Adafruit SSD1306
  2. Adafruit BusIO
  3. SparkFun MAX3010x Pulse and Proximity Sensor Library

and install both of them.


Code


The following code computes a rolling average of the last 4 heart rate values, and displays this average on the OLED screen over a heart icon. It also beeps the piezo buzzer each time a pulse is detected.


#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "MAX30105.h"
#include "heartRate.h"


// These options define the size of the OLED screen.
// They are measured in pixels.
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64


// This is the pin that the Piezo buzzer is connected to.
#define PIEZO_PIN 8


MAX30105 hrSensor;
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);


// This number defines the size of the heart-rate buffer used to calculate the rolling average.
const byte RATE_SIZE = 4;


byte rates[RATE_SIZE];
byte rateSpot = 0;
long lastBeat = 0;


float beatsPerMinute;
int beatAvg;


// These variables define the height, width, and contents of the heart image that we will display on the OLED screen.
#define HEART_WIDTH 60
#define HEART_HEIGHT 60
const unsigned char heart [] PROGMEM = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x01, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xc0, 0x00, 
  0x00, 0x1f, 0xfc, 0x00, 0x3f, 0xff, 0xf0, 0x00, 0x00, 0x7f, 0xff, 0x80, 0x7f, 0xff, 0xf8, 0x00, 
  0x00, 0xff, 0xff, 0xc0, 0xff, 0xff, 0xfc, 0x00, 0x03, 0xff, 0xff, 0xf1, 0xff, 0xff, 0xfe, 0x00, 
  0x07, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xff, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 
  0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 
  0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 
  0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 
  0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 
  0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 
  0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 
  0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 
  0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 
  0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 
  0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 
  0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 
  0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 
  0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 
  0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 
  0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 
  0x00, 0x0f, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 
  0x00, 0x03, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 
  0x00, 0x00, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xe0, 0x00, 0x00, 
  0x00, 0x00, 0x3f, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x00, 
  0x00, 0x00, 0x0f, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xfe, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x07, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xf8, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x01, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xe0, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x7f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};


// Code in the setup function runs once.
void setup() {
  Serial.begin(9600);
  Serial.println("Heart Rate Monitor");


  // This code connects to the heart rate sensor.
  if (!hrSensor.begin(Wire, I2C_SPEED_FAST))
  {
    Serial.println("Heart rate sensor not found.");
    while (1);
  }


  // This code connects to the OLED display.
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C, 0)) {
    Serial.println("Display not found.");
    while (1);
  }


  // This code configures the heart rate sensor.
  // Try changing the setPulseAmplitudeRed and setPulseAmplitudeGreen values.
  // Do you notice anything different on the heart rate sensor?
  hrSensor.setup();
  hrSensor.setPulseAmplitudeRed(0x0A);
  hrSensor.setPulseAmplitudeGreen(0);


  // The display.display() function updates the state of the screen.
  display.display();
  delay(2000);


  // The display.clearDisplay() function clears the contents of the screen.
  display.clearDisplay();


  // This function call draws the heart image in the center of the screen.
  display.drawBitmap(
    (display.width() - HEART_WIDTH) / 2,
    (display.height() - HEART_HEIGHT) / 2,
    heart, HEART_WIDTH, HEART_HEIGHT, SSD1306_WHITE);


   // Nothing changes on the screen until the display.display() function is called.
   // This call will send the changes from the clearDisplay
   // and drawBitmap functions to the display.
  display.display();
}


// Code in the loop function will run over and over again forever.
void loop() {
    long irValue = hrSensor.getIR();


    // This line checks to see if the heart rate monitor has detected a beat
    if (checkForBeat(irValue) == true)
    {
      // The delta variable stores the time between this heartbeat
      // and the last heartbeat, in milliseconds.
      long delta = millis() - lastBeat;
      lastBeat = millis();


      // This line calculates the current BPM, based on the time
      // between this heartbeat and the last one.
      beatsPerMinute = 60 / (delta / 1000.0);


      // This line checks to see if the heart rate makes sense
      // and should be treated as a valid reading
      if (beatsPerMinute < 255 && beatsPerMinute > 20)
      {
        // These lines adds the last reading to the
        // buffer of recent heart rate values.
        // The size of the buffer is defined by
        // the RATE_SIZE value.
        rates[rateSpot++] = (byte)beatsPerMinute;
        rateSpot %= RATE_SIZE;


        // These lines calculate the average heart rate
        // based on the buffer of recent readings.
        beatAvg = 0;
        for (byte x = 0 ; x < RATE_SIZE ; x++)
          beatAvg += rates[x];
        beatAvg /= RATE_SIZE;
      }


      // These lines play a 200hz tone
      // on the Piezo buzzer for 5ms,
      // to indicate a pulse.
      tone(PIEZO_PIN, 200);
      delay(5);
      noTone(PIEZO_PIN);


      // This code draws the heart image to the OLED screen
      // and writes the current heart rate over it.
      display.clearDisplay();


      display.drawBitmap(
        (display.width() - HEART_WIDTH) / 2,
        (display.height() - HEART_HEIGHT) / 2,
        heart, HEART_WIDTH, HEART_HEIGHT, SSD1306_WHITE);


      // Try changing the textSize value and the setCursor position.
      // What changes on the screen?
      display.setTextSize(3);
      display.setTextColor(SSD1306_BLACK);
      display.setCursor((display.width() - 30) / 2, (display.height() - 24) / 2);
      display.print(beatAvg);
      display.display();
    }
}

Room for Improvement

  1. Try changing the size of the heart rate buffer. What are the advantages and disadvantages of a larger buffer size, in terms of accuracy?
  2. Try displaying a special warning symbol when the user does not have their finger on the heart rate monitor.
  3. Try using the materials in your MakerKit to comfortably attach the heart rate monitor to your finger.