Introduction: Talking Arduino Heart Rate Monitor

My partner and I wanted to make heart rate monitor that does more than simply measure a user's heart rate. Our heart rate monitor talks! Each button gives a verbal description of its functionality and makes the measurements visible on the screen. This monitor will save the last four readings, display them, average them, and also offer some inspirational quotes!

Step 1: What You Need

  • Arduino Uno
  • Pulse Sensor
  • Adafruit Wave Shield (Must be soldered by user. This is what makes the device speak!)
  • Adafruit RBG LCD Shield
  • Standard speakers
  • Custom Shield (helpful for mounting multiple shields to the Arduino)
  • USB Cable
  • SD card (8GB is a LOT of space... Even 2GB will do. Make sure to purchase the real SD brand. Fakes are out there.)

Step 2: Connecting Components to the Custom Shield

Custom shield

The custom shield was built as a lab exercise for our class. This shield is mounted on top of the Arduino and gives you access to unused pins (or pins that can be shared) on the Arduino--enabling your to mount multiple shields or devices at once. Adafruit also sells a shield that should work just fine as a substitute.

The custom shield was used for the following:

  • Power the Pulse Sensor. This will require 3 pins: 5V, ground, and analog pin 2.
  • Power the RGB LCD shield. This will require 4 pins: 5V, ground, and analog pins 4 and 5.
  • Provide space to mount the Wave Shield.

Pulse Sensor

The sensor requires some set up depending on how you choose to use it. We insulated the back of the pulse sensor with hot glue. Here is a video that guides you through the set-up (video). All of the wires on it go the the screw terminal on the custom shield. The screw terminal gives access to 4 pins on the Arduino: 5V, ground, analog 2, and a digital pin (which we will ignore). The red wire on the Pulse Sensor goes to 5V, the black wire to ground, and the purple wire goes to analog 2.

RGB LCD Shield

The shield itself requires no set-up. You will need 4 wires that connect to the shield's 5V, ground, analog 4 and analog 5. We highly recommend soldering the 4 wires directly onto the LCD shield to stabilize the device if placing in a container later. These wires will connect to the Arduino's respective pins, which for us were accessed by the custom shield.

Wave Shield

Mount the wave shield on top of the custom shield and now we're ready to code!

Step 3: Using the Wave Shield

  1. Download the WaveHC Library from Adafruit.
  2. Format the SD card
    • The wave shield library only reads files in FAT format (both FAT16 and FAT32).
    • Some SD cards come formatted with FAT (mine did) and one way to test this is by running an example code that checks for the format of the card.
    • A program that formats SD cards
    • You will need an SD card reader to transfer files from your computer to the SD card. Most laptops come with SD slots (Macbook Pros do!). Else you will need to purchase one.
  3. Add sound files to the SD card
    • The files must be .wav files
    • We decided to use AT&T's Text-to-Speech website to generate voice files. These files already come formatted in .wav.
    • Suggestion: You can play with voice using Audacity. (Lower the pitch and add some echo for a Darth Vader voice!)
  4. Connect the Speaker to the audio jack. You can also solder the speakers to the audio output on the shield.

Step 4: Coding

This is really the heart of this project (heh). By step 3, you have the physical heart rate monitor, but now you decide what you will do with the device. The RGB LCD Shield comes with 5 programmable buttons. For our project, we decided on the following:

  • UP: Takes a reading of your heart rate.
  • DOWN: Displays the average of the last 4 heart rate readings.
  • LEFT: Displays the last 4 readings.
  • RIGHT: Gives some motivation.
  • SELECT: Explains each button.

There are three parts to the coding:

  1. Pulse Sensor data
  2. Wave Shield audio output
  3. RGB LCD Shield display & button presses

Now, lets go over the actual code. We basically meshed freely available code together from the RGB LCD shield, Wave Shield, and Pulse Sensor websites. We will also attach the two files you will need in .txt format. Please note that you will need a file provided by the Pulse Sensor website called Interrupt.ino (do not alter this file in any way). This file runs in the background and does the hard work that goes into calculating your BPM from the Pulse Sensor's readings. Note that this file must be in the same directory as the one that we are showing below for the code to work. It's also important to remember the audio file names in order to call the specific files. You will notice playcomplete( "file_name.wav") contains a specific file name. Once you upload the following file (with the interrupt file in the same directory) you are good to go!

<p>// project: interactive heart rate monitor with audio</p><p>// LCD shield libraries
#include <Wire.h><wire.h>
#include <Adafruit_</wire.h>MCP23017.h></p><p><wire.h><adafruit_mcp23017.h>#include <<adafruit_rgblcdshield.h></adafruit_rgblcdshield.h></adafruit_mcp23017.h></wire.h>Adafruit_RGBLCDShield.h></p><p>#include <Average.h><average.h></average.h></p><p>// waveshield libraries
#include <FatReader.h></p><p><fatreader.h>#include <SdReader.h><sdreader.h>
#include <avr/pgmspace.h><avr pgmspace.h="">
#include "WaveUtil.h"
#include "WaveHC.h"</avr></sdreader.h></fatreader.h></p><p>// objects for audio
SdReader card;    // This object holds the information for the card
FatVolume vol;    // This holds the information for the partition on the card
FatReader root;   // This holds the information for the filesystem on the card
FatReader f;      // This holds the information for the file we're play
WaveHC wave;      // This is the only wave (audio) object, since we will only play one at a time</p><p>#define DEBOUNCE 100  // button debouncer, may not be needed</p><p>//==============================================================================
// adapted from Adafruit Waveshield website
// methods for debugging
void sdErrorCheck(void) // checks the SD card
{
  if (!card.errorCode()) return;
  putstring("\n\rSD I/O error: ");
  Serial.print(card.errorCode(), HEX);
  putstring(", ");
  Serial.println(card.errorData(), HEX);
  while(1);
}</p><p>// adapted from Adafruit rgb lcd shield website
// The rgb lcd shield uses the I2C SCL and SDA pins. On classic Arduinos
// this is Analog 4 and 5 so you can't use those for analogRead() anymore
// However, you can connect other I2C sensors to the I2C bus and share
// the I2C bus.
Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield();</p><p>// These #defines make it easy to set the backlight color
#define RED 0x1
#define YELLOW 0x3
#define GREEN 0x2
#define TEAL 0x6
#define BLUE 0x4
#define VIOLET 0x5
#define WHITE 0x7</p><p>//================================================================================
// PULSE SENSOR CODE: adapted from Pulse Sensor website for this project
/*</p><p> >> Pulse Sensor Amped 1.2 <<
 This code is for Pulse Sensor Amped by Joel Murphy and Yury Gitman
 <a href="http://www.pulsesensor.com">  www.pulsesensor.com  </a> 
 >>> Pulse Sensor purple wire goes to Analog Pin 0 <<<
 Pulse Sensor sample aquisition and processing happens in the background via Timer 2 interrupt. 2mS sample rate.
 PWM on pins 3 and 11 will not work when using this code, because we are using Timer 2!
 The following variables are automatically updated:
 Signal :    int that holds the analog signal data straight from the sensor. updated every 2mS.
 IBI  :      int that holds the time interval between beats. 2mS resolution.
 BPM  :      int that holds the heart rate value, derived every beat, from averaging previous 10 IBI values.
 QS  :       boolean that is made true whenever Pulse is found and BPM is updated. User must reset.
 Pulse :     boolean that is true when a heartbeat is sensed then false in time with pin13 LED going out.
 */</p><p>//  VARIABLES
int pulsePin = 2;                 // Pulse Sensor purple wire connected to analog pin 2 (rgb lcd shield)
int blinkPin = 6;                 // pin to blink led at each beat</p><p>int fadeRate = 0;</p><p>int heartvals[4];
int h = 0;</p><p>// these variables are volatile because they are used during the interrupt service routine!
volatile int BPM;                   // used to hold the pulse rate
volatile int Signal;                // holds the incoming raw data
volatile int IBI = 600;             // holds the time between beats, must be seeded! 
volatile boolean Pulse = false;     // true when pulse wave is high, false when it's low
volatile boolean QS = false;        // becomes true when Arduoino finds a beat.</p><p>//===============================================================================</p><p>void setup() {</p><p>  pinMode( blinkPin, OUTPUT ); // pin that will blink to your heartbeat!</p><p>  // Set the output pins for the DAC control. This pins are defined in the library
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);</p><p>  // enable pull-up resistors on switch pins (analog inputs)
  digitalWrite(14, HIGH);
  digitalWrite(15, HIGH);
  digitalWrite(16, HIGH);
  digitalWrite(17, HIGH);
  digitalWrite(18, HIGH);
  digitalWrite(19, HIGH);</p><p>  Serial.begin( 9600 );</p><p>  // debugging methods from waveshield website
  //  if (!card.init(true)) { //play with 4 MHz spi if 8MHz isn't working for you
  if (!card.init()) {         //play with 8 MHz spi (default faster!)  
    putstring_nl("Card init. failed!");  // Something went wrong, lets print out why
    sdErrorCheck();
    while(1);                            // then 'halt' - do nothing!
  }</p><p>  // enable optimize read - some cards may timeout. Disable if you're having problems
  card.partialBlockRead(true);</p><p>  // Now we will look for a FAT partition!
  uint8_t part;
  for (part = 0; part < 5; part++) {     // we have up to 5 slots to look in
    if (vol.init(card, part)) 
      break;                             // we found one, lets bail
  }</p><p>  if (part == 5) {                       // if we ended up not finding one  :(
    putstring_nl("No valid FAT partition!");
    sdErrorCheck();      // Something went wrong, lets print out why
    while(1);                            // then 'halt' - do nothing!
  }</p><p>  // Lets tell the user about what we found
  putstring("Using partition ");
  Serial.print(part, DEC);
  putstring(", type is FAT");
  Serial.println(vol.fatType(),DEC);     // FAT16 or FAT32?</p><p>  // Try to open the root directory
  if (!root.openRoot(vol)) {
    putstring_nl("Can't open root dir!"); // Something went wrong,
    while(1);                             // then 'halt' - do nothing!
  }</p><p>  // secondary file with code for the Pulse Sensor
  interruptSetup();                 // sets up to read Pulse Sensor signal every 2mS 
  // UN-COMMENT THE NEXT LINE IF YOU ARE POWERING The Pulse Sensor AT LOW VOLTAGE, 
  // AND APPLY THAT VOLTAGE TO THE A-REF PIN
  //analogReference(EXTERNAL);   </p><p>  // set up for LCD
  lcd.begin( 16, 2 );
  lcd.setBacklight( RED );</p><p>  lcd.setCursor( 0, 0 );
  lcd.print( "Welcome to your" );
  lcd.setCursor( 0, 1 );
  lcd.print( "Arduino HRM!" );
  playcomplete("intro.wav");</p><p>  //  lcd.clear();</p><p>} // END OF VOID SETUP</p><p>uint8_t i=0;</p><p>//===============================================================================</p><p>void loop() { 
  uint8_t buttons = lcd.readButtons();
  if ( buttons ) {
    lcd.clear();
    lcd.setCursor( 0, 0 );</p><p>    if ( buttons & BUTTON_UP ) {
      heartRate();
    }
    if ( buttons & BUTTON_LEFT ) {
      playcomplete( "readings.wav" );
      lcd.setCursor( 0, 0 );
      lcd.print( "Last 4 readings: " );
      lcd.setCursor( 0, 1 );
      int i;
      for ( i=0; i<4; i++ ) {
        lcd.println( heartvals[i] );
      }
    }
    if ( buttons & BUTTON_RIGHT ) {
      int randNumber = 0;
      randNumber = random( 1, 10 );
      if ( randNumber == 1 ) {
        playcomplete("1.wav");
      }
      if ( randNumber == 2 ) {
        playcomplete("2.wav");
      }
      if ( randNumber == 3 ) {
        playcomplete("3.wav");
      }
      if ( randNumber == 4 ) {
        playcomplete("4.wav");
      }
      if ( randNumber == 5 ) {
        playcomplete("5.wav");
      }
      if ( randNumber == 6 ) {
        playcomplete("6.wav");
      }
      if ( randNumber == 7 ) {
        playcomplete("7.wav");
      }
      if ( randNumber == 8 ) {
        playcomplete("8.wav");
      }
      if ( randNumber == 9 ) {
        playcomplete("9.wav");
      }
      if ( randNumber == 10 ) {
        playcomplete("10.wav");
      }
    }
    if ( buttons & BUTTON_DOWN ) {
      averageH();
    }
    if ( buttons & BUTTON_SELECT ) {
      lcd.print( "Select" );
      playcomplete( "select.wav" );
    }
  }
} // END OF VOID LOOP</p><p>//===============================================================================</p><p>// methods needed for playing .wav files
// adapted from waveshield website</p><p>// Plays a full file from beginning to end with no pause.
void playcomplete(char *name) {
  // call our helper to find and play this name
  playfile(name);
  while (wave.isplaying) {
    // do nothing while its playing
  }
  // now its done playing
}</p><p>void playfile(char *name) {
  // see if the wave object is currently doing something
  if (wave.isplaying) {// already playing something, so stop it!
    wave.stop(); // stop it
  }
  // look in the root directory and open the file
  if (!f.open(root, name)) {
    putstring("Couldn't open file "); 
    Serial.print(name); 
    return;
  }
  // OK read the file and turn it into a wave object
  if (!wave.create(f)) {
    putstring_nl("Not a valid WAV"); 
    return;
  }</p><p>  // ok time to play! start playback
  wave.play();
}
//===============================================================================</p><p>// a method called to read and display BPM on lcd on UP button press
void heartRate() {
  int x = 10;</p><p>  while ( x != 0 ) {
    if ( QS == true ){                 // Quantified Self flag is true when arduino finds a heartbeat
      fadeRate = 255;                  // Set 'fadeRate' Variable to 255 to fade LED with pulse
      lcd.setCursor( 0, 0 );
      lcd.print( "Heart rate: " );
      lcd.setCursor( 12, 0 );
      lcd.println( BPM );
      lcd.setCursor( 0, 1 );
      lcd.print( "You are alive!" );
      delay( 500 );
      QS = false;                      // reset the Quantified Self flag for next time    
      x--;
    } 
  }
  heartvals[h] =  BPM ;
  if ( h < 4 ) {
    h++;
  } 
  else if ( h == 4 ) {
    h = 0;
    heartvals[h] =  BPM ;
  }</p><p>  int i;
  for ( i=0; i<4; i++ ) {
    Serial.println( heartvals[i]);
  }
  playcomplete( "finished.wav" ); 
}</p><p>// method to calculate the average of the 4 values in heartvals
void averageH() {
  int average = 0;
  if ( heartvals[3] == 0 ) {
    lcd.setCursor( 4, 0 );
    lcd.print( "Error..." );
    lcd.setCursor( 0, 1 );
    lcd.print( "4 values needed" );
  } 
  else {
    playcomplete( "average.wav" );
    average = mean( heartvals, 4 );
    lcd.setCursor( 0, 0 );
    lcd.print( "Average: " );
    lcd.setCursor( 9, 0 );
    lcd.print( average );
  }</p><p>}</p><p>//===============================================================================</p>

Step 5: The Final Product...It's Alive!

In the following video, we press reset on the Wave Shield and take a quick reading for you. Note that all of the audio comes from our premade .wav files (you can make it say whatever you want!).

Good luck!