Creating an alert with a buzzer for a heart rate monitor arduino

Hi all,

I need some help. I have the code for an arduino with a pulse sensor. The BPM reads on an LCD 16x2 display and even has an LED that blinks with it. I just can not find any code to make it beep with the buzzer when it reaches a BPM of 55 or below. Thanks!
Here's the code.

#include <LiquidCrystal.h>
#include <PulseSensorPlayground.h>
//Viral Science www.viralsciencecreativity.com
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

int pulsePin = A0;                 // Pulse Sensor purple wire connected to analog pin A0
int blinkPin = 13;                // pin to blink led at each beat

// Volatile Variables, used in the interrupt service routine!
volatile int BPM;                   // int that holds raw Analog in 0. updated every 2mS
volatile int Signal;                // holds the incoming raw data
volatile int IBI = 600;             // int that holds the time interval between beats! Must be seeded!
volatile boolean Pulse = false;     // "True" when User's live heartbeat is detected. "False" when not a "live beat".
volatile boolean QS = false;        // becomes true when Arduoino finds a beat.

static boolean serialVisual = true;   // Set to 'false' by Default.  Re-set to 'true' to see Arduino Serial Monitor ASCII Visual Pulse
volatile int rate[10];                      // array to hold last ten IBI values
volatile unsigned long sampleCounter = 0;          // used to determine pulse timing
volatile unsigned long lastBeatTime = 0;           // used to find IBI
volatile int P = 512;                      // used to find peak in pulse wave, seeded
volatile int T = 512;                     // used to find trough in pulse wave, seeded
volatile int thresh = 525;                // used to find instant moment of heart beat, seeded
volatile int amp = 100;                   // used to hold amplitude of pulse waveform, seeded
volatile boolean firstBeat = true;        // used to seed rate array so we startup with reasonable BPM
volatile boolean secondBeat = false;      // used to seed rate array so we startup with reasonable BPM
void setup()
{
  pinMode(blinkPin, OUTPUT);        // pin that will blink to your heartbeat!
  Serial.begin(115200);             // we agree to talk fast!
  interruptSetup();                 // sets up to read Pulse Sensor signal every 2mS
  // IF YOU ARE POWERING The Pulse Sensor AT VOLTAGE LESS THAN THE BOARD VOLTAGE,
  // UN-COMMENT THE NEXT LINE AND APPLY THAT VOLTAGE TO THE A-REF PIN
  //   analogReference(EXTERNAL);
  lcd.begin(16, 2);
  lcd.clear();
}
//  Where the Magic Happens
void loop()
{
  serialOutput();
  if (QS == true) // A Heartbeat Was Found
  {
    // BPM and IBI have been Determined
    // Quantified Self "QS" true when arduino finds a heartbeat
    serialOutputWhenBeatHappens(); // A Beat Happened, Output that to serial.
    QS = false; // reset the Quantified Self flag for next time
  }
  delay(20); //  take a break
}

void interruptSetup()
{
  // Initializes Timer2 to throw an interrupt every 2mS.
  TCCR2A = 0x02;     // DISABLE PWM ON DIGITAL PINS 3 AND 11, AND GO INTO CTC MODE
  TCCR2B = 0x06;     // DON'T FORCE COMPARE, 256 PRESCALER
  OCR2A = 0X7C;      // SET THE TOP OF THE COUNT TO 124 FOR 500Hz SAMPLE RATE
  TIMSK2 = 0x02;     // ENABLE INTERRUPT ON MATCH BETWEEN TIMER2 AND OCR2A
  sei();             // MAKE SURE GLOBAL INTERRUPTS ARE ENABLED
}

void serialOutput()
{ // Decide How To Output Serial.
  if (serialVisual == true)
  {
    arduinoSerialMonitorVisual('-', Signal);   // goes to function that makes Serial Monitor Visualizer
  }
  else
  {
    sendDataToSerial('S', Signal);     // goes to sendDataToSerial function
  }
}

void serialOutputWhenBeatHappens()
{
  if (serialVisual == true) //  Code to Make the Serial Monitor Visualizer Work
  {
    Serial.print(" Heart-Beat Found ");  //ASCII Art Madness
    Serial.print("BPM: ");
    Serial.println(BPM);
    lcd.print("Heart-Beat Found ");
    lcd.setCursor(1, 1);
    lcd.print("BPM: ");
    lcd.setCursor(5, 1);
    lcd.print(BPM);
    delay(300);
    lcd.clear();
  }
  else
  {
    sendDataToSerial('B', BPM);  // send heart rate with a 'B' prefix
    sendDataToSerial('Q', IBI);  // send time between beats with a 'Q' prefix
  }
}
void arduinoSerialMonitorVisual(char symbol, int data )
{
  const int sensorMin = 0;      // sensor minimum, discovered through experiment
  const int sensorMax = 1024;    // sensor maximum, discovered through experiment
  int sensorReading = data; // map the sensor range to a range of 12 options:
  int range = map(sensorReading, sensorMin, sensorMax, 0, 11);
  // do something different depending on the
  // range value:
}
void sendDataToSerial(char symbol, int data )
{
  Serial.print(symbol);
  Serial.println(data);
}
ISR(TIMER2_COMPA_vect) //triggered when Timer2 counts to 124
{
  cli();                                      // disable interrupts while we do this
  Signal = analogRead(pulsePin);              // read the Pulse Sensor
  sampleCounter += 2;                         // keep track of the time in mS with this variable
  int N = sampleCounter - lastBeatTime;       // monitor the time since the last beat to avoid noise
  //  find the peak and trough of the pulse wave
  if (Signal < thresh && N > (IBI / 5) * 3) // avoid dichrotic noise by waiting 3/5 of last IBI
  {
    if (Signal < T) // T is the trough
    {
      T = Signal; // keep track of lowest point in pulse wave
    }
  }
  if (Signal > thresh && Signal > P)
  { // thresh condition helps avoid noise
    P = Signal;                             // P is the peak
  }                                        // keep track of highest point in pulse wave
  // signal surges up in value every time there is a pulse
  if (N > 250)
  { // avoid high frequency noise
    if ( (Signal > thresh) && (Pulse == false) && (N > (IBI / 5) * 3) )
    {
      Pulse = true;                               // set the Pulse flag when we think there is a pulse
      digitalWrite(blinkPin, HIGH);               // turn on pin 13 LED
      IBI = sampleCounter - lastBeatTime;         // measure time between beats in mS
      lastBeatTime = sampleCounter;               // keep track of time for next pulse
      if (secondBeat)
      { // if this is the second beat, if secondBeat == TRUE
        secondBeat = false;                  // clear secondBeat flag
        for (int i = 0; i <= 9; i++) // seed the running total to get a realisitic BPM at startup
        {
          rate[i] = IBI;
        }
      }
      if (firstBeat) // if it's the first time we found a beat, if firstBeat == TRUE
      {
        firstBeat = false;                   // clear firstBeat flag
        secondBeat = true;                   // set the second beat flag
        sei();                               // enable interrupts again
        return;                              // IBI value is unreliable so discard it
      }
      // keep a running total of the last 10 IBI values
      word runningTotal = 0;                  // clear the runningTotal variable
      for (int i = 0; i <= 8; i++)
      { // shift data in the rate array
        rate[i] = rate[i + 1];                // and drop the oldest IBI value
        runningTotal += rate[i];              // add up the 9 oldest IBI values
      }
      rate[9] = IBI;                          // add the latest IBI to the rate array
      runningTotal += rate[9];                // add the latest IBI to runningTotal
      runningTotal /= 10;                     // average the last 10 IBI values
      BPM = 60000 / runningTotal;             // how many beats can fit into a minute? that's BPM!
      QS = true;                              
      // QS FLAG IS NOT CLEARED INSIDE THIS ISR
    }}

  if (Signal < thresh && Pulse == true)
  { // when the values are going down, the beat is over
    digitalWrite(blinkPin, LOW);          
    Pulse = false;                         
    amp = P - T;                          
    thresh = amp / 2 + T;                  
    P = thresh;                            
    T = thresh;  }
  if (N > 2500)
  { // if 2.5 seconds go by without a beat
    thresh = 512;                          
    P = 512;                               
    T = 512;                               
    lastBeatTime = sampleCounter;          
    firstBeat = true;                      
    secondBeat = false;                    
  }
  sei();                                   
}// end isr

To put your code in a code box, use the </> icon in the far left of the post tool bar and paste your code between the two bracket sets that appear.

To go back and put your code in a code box, in the bottom right of your post, select "more" and click modify. When the modify post opens, high light your code and click the </> in the far left of the post tool bar. This will put you code in code brackets. Then save the changes.

Thanks!

(deleted)

Let's start with the basics. What type of buzzer do you have ? If you connect its pins to 5V and GND what noise, if any, does it make ?

It's a piezo buzzer (a small one). I have it running on this code:

 tone(9, 2000, 300);
           delay(300);

It works fine. Everything seems to work fine. I am really struggling to get the buzzer to go off when BPM reaches a certain point but can't figure out how to code that conditional statement. I think threshold might work but I found this code online and not sure which variables to use to make the buzzer go off when that condition is met.

Thanks!

You have a variable named BPM, presumably Beats Per Minute

if (BPM < lowThreshold)
{
  //do something like sounding a buzzer
}

Thanks for that. Do you think I need to now define lowThreshold? Not exactly sure what to use now since I don't want to mess up the code. Sorry. I am a teacher working on this with my students and I'm a bit over my head here.

Do you think I need to now define lowThreshold?

You either need to declare a variable named lowThrehold and assign it a value or use an explicit value in the test. It is considered better to declare a variable with a sensible name so that its purpose and meaning is obvious and even better, to declare it as a constant value

For your purposes, if you declare it at the start of the program, outside of any function, so that it is available globally throughout the program that would be easiest for you. Where you put the test and buzzer sounding is up to you but the most obvious place would seem to be soon after calculating BPM but be aware that using the tone() function will cause the program to pause whilst the buzzer sounds as will the use of the delay() function

is there another way to set a high frequency without using the tone function so as not to pause the code? I tried digitalWrite(buzzerPin, HIGH) but that just turns it on.

My issue is determining the value for the lowThreshold. I do not know much about heart rate and how it's determined and also I can not figure it out from the code.

is there another way to set a high frequency without using the tone function so as not to pause the code?

My apologies, tone() is not a blocking function so will not pause the code. However, delay() will if you use it

bessa12:
My issue is determining the value for the lowThreshold. I do not know much about heart rate and how it's determined and also I can not figure it out from the code.

You will not be able to figure it out from the code as it is not part of it, but a Google search should help. What is the normal range of human heartbeats per minute ?

Heart beat normal range is 70 to 100 bpm I believe. However, this code is run using heart waves and not beats per minute. I will search it up as per your suggestion.
Thanks

However, this code is run using heart waves and not beats per minute

I have no idea what that means and what is BPM if not Beats Per Minute ?

Sorry. I have that but whenever I put that code in I always get an error. Any advice on what I should be writing?

Here is the part of the code I believe I am going to enter this in.

ISR(TIMER2_COMPA_vect) //triggered when Timer2 counts to 124
{
  cli();                                      // disable interrupts while we do this
  Signal = analogRead(pulsePin);              // read the Pulse Sensor
  sampleCounter += 2;                         // keep track of the time in mS with this variable
  int N = sampleCounter - lastBeatTime;       // monitor the time since the last beat to avoid noise
  //  find the peak and trough of the pulse wave
  if (Signal < thresh && N > (IBI / 5) * 3) // avoid dichrotic noise by waiting 3/5 of last IBI
  {
    if (Signal < T) // T is the trough
    {
      T = Signal;                    // keep track of lowest point in pulse wave
    }
  }
  

  if (Signal > thresh && Signal > P)
  { // thresh condition helps avoid noise
    P = Signal;                             // P is the peak
  }                                        // keep track of highest point in pulse wave

  //  NOW IT'S TIME TO LOOK FOR THE HEART BEAT
  // signal surges up in value every time there is a pulse
  if (N > 250)
  { // avoid high frequency noise
    if ( (Signal > thresh) && (Pulse == false) && (N > (IBI / 5) * 3) )
    {
      Pulse = true;                               // set the Pulse flag when we think there is a pulse
      digitalWrite(blinkPin, HIGH);               // turn on pin 13 LED
      IBI = sampleCounter - lastBeatTime;         // measure time between beats in mS
      lastBeatTime = sampleCounter;               // keep track of time for next pulse

      if (secondBeat)
      { // if this is the second beat, if secondBeat == TRUE
        secondBeat = false;                  // clear secondBeat flag
        for (int i = 0; i <= 9; i++) // seed the running total to get a realisitic BPM at startup
        {
          rate[i] = IBI;
        }
      }


      if (firstBeat) // if it's the first time we found a beat, if firstBeat == TRUE
      {
        firstBeat = false;                   // clear firstBeat flag
        secondBeat = true;                   // set the second beat flag
        sei();                               // enable interrupts again
        return;                              // IBI value is unreliable so discard it
      }
      // keep a running total of the last 10 IBI values
      word runningTotal = 0;                  // clear the runningTotal variable

      for (int i = 0; i <= 8; i++)
      { // shift data in the rate array
        rate[i] = rate[i + 1];                // and drop the oldest IBI value
        runningTotal += rate[i];              // add up the 9 oldest IBI values
      }

      rate[9] = IBI;                          // add the latest IBI to the rate array
      runningTotal += rate[9];                // add the latest IBI to runningTotal
      runningTotal /= 10;                     // average the last 10 IBI values
      BPM = 60000 / runningTotal;             // how many beats can fit into a minute? that's BPM!
      QS = true;                              // set Quantified Self flag
      // QS FLAG IS NOT CLEARED INSIDE THIS ISR
    }
  }

  if (Signal < thresh && Pulse == true)
  { // when the values are going down, the beat is over
    digitalWrite(blinkPin, LOW);           // turn off pin 13 LED
    Pulse = false;                         // reset the Pulse flag so we can do it again
    amp = P - T;                           // get amplitude of the pulse wave
    thresh = amp / 2 + T;                  // set thresh at 50% of the amplitude
    P = thresh;                            // reset these for next time
    T = thresh;
  }

  if (N > 2500)
  { // if 2.5 seconds go by without a beat
    thresh = 512;                          // set thresh default
    P = 512;                               // set P default
    T = 512;                               // set T default
    lastBeatTime = sampleCounter;          // bring the lastBeatTime up to date
    firstBeat = true;                      // set these to avoid noise
    secondBeat = false;                    // when we get the heartbeat back
  }



  sei();                                   // enable interrupts when youre done!
}// end isr

I have created variables for lowThreshold and anything else I can think of, but my code is never correct. I feel like I'm missing something. Please help.

Here is the part of the code I believe I am going to enter this in.

That is entirely the wrong place. Do not put the test and tone in the ISR

As a start, put it immediately after you print BPM to the LCD. It is not the best place but it will do for now

I put this code here. This makes sense?

It is not setting the buzzer off though. Any suggestions?

void loop()
{
  serialOutput();

  if (QS == true) // A Heartbeat Was Found
  {
    // BPM and IBI have been Determined
    // Quantified Self "QS" true when arduino finds a heartbeat
    serialOutputWhenBeatHappens(); // A Beat Happened, Output that to serial.
    QS = false; // reset the Quantified Self flag for next time
  }

  delay(20); //  take a break

if (BPM <= 55)
{ 
   digitalWrite(buzzerPin, HIGH);
}

}

That looks like the wrong place, although without you complete revised sketch it is difficult to say, but is certainly not where I suggested. If the buzzer is a passive one requiring the use of the tone() function then digitalWrite() is not the correct function to use, is it

Just to be sure which type of buzzer you have, what happens if you put 5 volts across the buzzer contacts ? Does it just go click or does it produce a note ?

I didn't think I could use tone() since the pulsesensor site said using this would disable the tone command.

When I tested the buzzer on it's own with the tone() function and analogWrite() function, it creates a note. digitalWrite() did nothing.

Here is my full code. I took out that clip.

#include <LiquidCrystal.h>
#include <PulseSensorPlayground.h>

//Viral Science
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

int pulsePin = A0;                 // Pulse Sensor purple wire connected to analog pin A0
int blinkPin = 13;                // pin to blink led at each beat
int buzzerPin = 9;                //pin to have buzzer sound alarm

// Volatile Variables, used in the interrupt service routine!
volatile int BPM;                   // int that holds raw Analog in 0. updated every 2mS
volatile int Signal;                // holds the incoming raw data
volatile int IBI = 600;             // int that holds the time interval between beats! Must be seeded!
volatile boolean Pulse = false;     // "True" when User's live heartbeat is detected. "False" when not a "live beat".
volatile boolean QS = false;        // becomes true when Arduoino finds a beat.

static boolean serialVisual = true;   // Set to 'false' by Default.  Re-set to 'true' to see Arduino Serial Monitor ASCII Visual Pulse

volatile int rate[10];                      // array to hold last ten IBI values
volatile unsigned long sampleCounter = 0;          // used to determine pulse timing
volatile unsigned long lastBeatTime = 0;           // used to find IBI
volatile int P = 512;                      // used to find peak in pulse wave, seeded
volatile int T = 512;                     // used to find trough in pulse wave, seeded
volatile int thresh = 525;                // used to find instant moment of heart beat, seeded
volatile int amp = 100;                   // used to hold amplitude of pulse waveform, seeded
volatile boolean firstBeat = true;        // used to seed rate array so we startup with reasonable BPM
volatile boolean secondBeat = false;      // used to seed rate array so we startup with reasonable BPM
volatile int lowThreshold = 450;          //used to determine BPM below 55 

void setup()
{
  pinMode(blinkPin, OUTPUT);        //will blink to your heartbeat!
  Serial.begin(115200);             // 
  interruptSetup();                 // sets up to read Pulse Sensor signal every 2mS
  // IF YOU ARE POWERING The Pulse Sensor AT VOLTAGE LESS THAN THE BOARD VOLTAGE,
  // UN-COMMENT THE NEXT LINE AND APPLY THAT VOLTAGE TO THE A-REF PIN
  //   analogReference(EXTERNAL);
  lcd.begin(16, 2);
  lcd.clear();
  pinMode(buzzerPin, OUTPUT);
  pinMode(pulsePin, INPUT);
  
}


//  Where the Magic Happens
void loop()
{
  serialOutput();

  if (QS == true) // A Heartbeat Was Found
  {
    // BPM and IBI have been Determined
    // Quantified Self "QS" true when arduino finds a heartbeat
    serialOutputWhenBeatHappens(); // A Beat Happened, Output that to serial.
    QS = false; // reset the Quantified Self flag for next time
  }
  delay(20); //  
}

void interruptSetup()
{
  // Initializes Timer2 to throw an interrupt every 2mS.
  TCCR2A = 0x02;     // DISABLE PWM ON DIGITAL PINS 3 AND 11, AND GO INTO CTC MODE
  TCCR2B = 0x06;     // DON'T FORCE COMPARE, 256 PRESCALER
  OCR2A = 0X7C;      // SET THE TOP OF THE COUNT TO 124 FOR 500Hz SAMPLE RATE
  TIMSK2 = 0x02;     // ENABLE INTERRUPT ON MATCH BETWEEN TIMER2 AND OCR2A
  sei();             // MAKE SURE GLOBAL INTERRUPTS ARE ENABLED
}

void serialOutput()
{ // Decide How To Output Serial.
  if (serialVisual == true)
  {
    arduinoSerialMonitorVisual('-', Signal);   // goes to function that makes Serial Monitor Visualizer
  }
  else
  {
    sendDataToSerial('S', Signal);     // goes to sendDataToSerial function
  }
}

void serialOutputWhenBeatHappens()
{
  if (serialVisual == true) //  Code to Make the Serial Monitor Visualizer Work
  {
    Serial.print(" ♥ Heart-Beat Found ");  //ASCII Art
    Serial.print("BPM: ");
    Serial.println(BPM);
    lcd.print("Heart-Beat Found ");
    lcd.setCursor(1, 1);
    lcd.print("BPM: ");
    lcd.setCursor(5, 1);
    lcd.print(BPM);
    delay(1000);
    lcd.clear();
  }
  else
  {
    sendDataToSerial('B', BPM);  // send heart rate with a 'B' prefix
    sendDataToSerial('Q', IBI);  // send time between beats with a 'Q' prefix
  }
}
void arduinoSerialMonitorVisual(char symbol, int data )
{
  const int sensorMin = 0;      // sensor minimum, discovered through experiment
  const int sensorMax = 1024;    // sensor maximum, discovered through experiment
  int sensorReading = data; // map the sensor range to a range of 12 options:
  int range = map(sensorReading, sensorMin, sensorMax, 0, 11);
  // do something different depending on the
  // range value:
}
void sendDataToSerial(char symbol, int data )
{
  Serial.print(symbol);
  Serial.println(data);
}
ISR(TIMER2_COMPA_vect) //triggered when Timer2 counts to 124
{
  cli();                                      // disable interrupts while we do this
  Signal = analogRead(pulsePin);              // read the Pulse Sensor
  sampleCounter += 2;                         // keep track of the time in mS with this variable
  int N = sampleCounter - lastBeatTime;       // monitor the time since the last beat to avoid noise
  //  find the peak and trough of the pulse wave
  if (Signal < thresh && N > (IBI / 5) * 3) // no dichrotic noise by waiting 3/5 of last IBI
  {
    if (Signal < T) // T is the trough
    {
      T = Signal;                    // keep track of lowest point in pulse wave
    }
  }
  if (Signal > thresh && Signal > P)
  { // thresh condition helps avoid noise
    P = Signal;                             // P is the peak
  }                                        // keep track of highest point in pulse wave
  //  NOW IT'S TIME TO LOOK FOR THE HEART BEAT
  // signal surges up in value every time there is a pulse
  if (N > 250)
  { // avoid high frequency noise
    if ( (Signal > thresh) && (Pulse == false) && (N > (IBI / 5) * 3) )
    {
      Pulse = true;                               // set the Pulse flag when we think there is a pulse
      digitalWrite(blinkPin, HIGH);               // turn on pin 13
      IBI = sampleCounter - lastBeatTime;         // measure time between beats in mS
      lastBeatTime = sampleCounter;               // keep track of time for next pulse
      if (secondBeat)
      { // if this is the second beat, if secondBeat == TRUE
        secondBeat = false;                  // clear secondBeat flag
        for (int i = 0; i <= 9; i++) // seed the running total to get a realisitic BPM at startup
        {
          rate[i] = IBI;
        }
      }
      if (firstBeat) // if it's the first time we found a beat, if firstBeat == TRUE
      {
        firstBeat = false;       // clear firstBeat flag
        secondBeat = true;          // set the second beat flag
        sei();                     // enable interrupts again
        return;                 // IBI value is unreliable so discard it
      }
      // keep a running total of the last 10 IBI values
      word runningTotal = 0;        // clear the runningTotal variable
      for (int i = 0; i <= 8; i++)
      { // shift data in the rate array
        rate[i] = rate[i + 1];      // and drop the oldest IBI value
        runningTotal += rate[i]      // add up the 9 oldest IBI values
      }
      rate[9] = IBI;            // add the latest IBI to the rate array
      runningTotal += rate[9];     // add the latest IBI to runningTotal
      runningTotal /= 10;          // average the last 10 IBI values
      BPM = 60000 / runningTotal;      // how many beats can fit into a minute? that's BPM!
      QS = true;                 // set Quantified Self flag
      // QS FLAG IS NOT CLEARED INSIDE THIS ISR
    }
  }
  if (Signal < thresh && Pulse == true)
  { // when the values are going down, the beat is over
    digitalWrite(blinkPin, LOW);    // turn off pin 13
    Pulse = false;                // reset the Pulse flag so we can do it again
    amp = P - T;                 // get amplitude of the pulse wave
    thresh = amp / 2 + T;           // set thresh at 50% of the amplitude
    P = thresh;            // reset these for next time
    T = thresh;
  }
  if (N > 2500)
  { // if 2.5 seconds go by without a beat
    thresh = 512;            // set thresh
    P = 512;                  // set P
    T = 512;                 // set T
    lastBeatTime = sampleCounter;  // bring the lastBeatTime up to date
    firstBeat = true;                      
    secondBeat = false;       // when we get the heartbeat back
  sei();                              // enable interrupts
}// end isr