Calculating heartbeat frequency using PPG method - Help needed!

Now the LED flashes correctly - with the heartbeat, but there is nothing printed. I've also tried going back, but still nothing is printed. Here's the code now:

int LED = 13; //LED to blink when pulsing
int din = 2; // digital pin 2 to read 0V or 5V
int check; // checks current status of digital pin 2
int prevcheck;// previous check status
int pulsecount = 0; //number of pulses (highs) seen on the digital pin
int BPM = 0; //beats perminute = counter value * 10
unsigned long firstpulse =0; //time of first pulse
unsigned long timecount = 0;//cumulative time of total pulses
unsigned long latestpulse=0;
unsigned long interval = 6000; //setting 6 seconds as max read time

void setup()

{
  pinMode(LED, OUTPUT);
  pinMode(din,INPUT);
  Serial.begin(9600);
}

void loop()

{
  check = digitalRead(din);
  
  //if (check != prevcheck ) // comparing current state to previous state
  {
    if (check == LOW)
    {
      digitalWrite(LED, LOW);
      //prevcheck == LOW;
    }
    
    if (check == HIGH)
    {
      digitalWrite(LED, HIGH);
      //prevcheck == HIGH; //storing current state of input pin
      check == LOW; //reset the check

      if (pulsecount == 0 ) // if this is the first count
      
      {
        firstpulse = millis(); // recording time of first pulse
        pulsecount = pulsecount +1; //increment the counter
      }
      
      if (pulsecount >=1 )
      {
        pulsecount = pulsecount + 1; // increment pulse counter when detected
        //timecount = millis() + firstpulse + timecount; //sumation of all pulse periods
        latestpulse = millis(); // recording latest pulse time
      }
      

    }
  }
        
        if (latestpulse - firstpulse > interval) //checking if 6 seconds has elapsed
      {
        BPM = pulsecount;
        Serial.println(BPM); //display the number of counts over 6 seconds
        pulsecount = 0; // counter restarted
        firstpulse = 0; // restart for time of new first-pulse read 
        latestpulse = 0; // restart for new time readings
        //delay(100);
      }
      prevcheck = check;
    }

Thanks

ok. try this:

void loop()
{
check = digitalRead(din);
if(check != prevcheck)
  {
  if (check == LOW)
    {
    digitalWrite(LED, LOW);
    }

  if (check == HIGH)
    {
    digitalWrite(LED, HIGH);

    if (pulsecount == 0 ) // if this is the first count
      {
      firstpulse = millis(); // recording time of first pulse
      pulsecount = pulsecount +1; //increment the counter
      }

    if (pulsecount >=1 )
      {
      pulsecount = pulsecount + 1; // increment pulse counter when detected
      latestpulse = millis(); // recording latest pulse time
      }
    }
  }

if (latestpulse - firstpulse > interval) //checking if 6 seconds has elapsed
  {
  BPM = pulsecount;
  Serial.println(BPM); //display the number of counts over 6 seconds
  pulsecount = 0; // counter restarted
  firstpulse = 0; // restart for time of new first-pulse read 
  latestpulse = 0; // restart for new time readings
  }
prevcheck = check;
}

Thanks Wildbill, closest so far!

Though the concept works, there seems to be extra reads, not sure where from, making the BPM a bit unrealistic:

9
16
20
20
26
9
11
7
12
16
14
13
14
18

how did you get

if(check != prevcheck)

to run with it? Did i have extra brackets?

Thanks

Most likely need to debounce. This is a usage where delay is an acceptable solution. Throw in a delay(20) or so after the digitalread.

Did i have extra brackets?

Yes. I assume you left them in from when you had the check against prevcheck.

Excellent work Wildbill! here are some of the read values (with 50ms delay):

12
8
8
9
9
8
9
8
9
9
8
8
10
8
10

6

I will try and confirm the pulses with some scope measurements.

Do you think this is the most simple and reliable way of recording BPM?

Thanks again!

You're welcome. However:

Do you think this is the most simple and reliable way of recording BPM?

You took a turn there out of my area of expertise :wink:

:slight_smile: @ Wildbill, sorry i mean't with the arduino. Is this the best way to determine pulse counts? Could arrays / for loops be used? I'm considering using case statements. The BPM is not always consistent so far.

Thanks

The code looks fine to me - I don't see any advantage to using different constructs. The area I'd look at is how well your sensor is working - there seems to be quite wide variation in what it reads - or perhaps you got excited to see it finally working :wink: You might be able to mask that with a longer sensing period, but it looks like you need to compare what you're getting with a manual check of your pulse.

Hi, Yes i've been very excited but up to 300 BPM readings would be concerning XD

I was able to take some scope measurements - setup:
2V per box, 1 second per box, graph width of 10s - with this i was measuring a square wave (5V peak) and on average 7 pulses.

But the arduino kept displaying values from 100 upwards on an attached LCD - i increased the time interval to 10s. Here's the current code:

int LED = 13; //LED to blink when pulsing
int din = 2; // digital pin 2 to read 0V or 5V
int check; // checks current status of digital pin 2
int prevcheck;// previous check status
int pulsecount = 0; //number of pulses (highs) seen on the digital pin
int BPM = 0; //beats perminute = counter value * 10
unsigned long firstpulse =0; //time of first pulse
unsigned long timecount = 0;//cumulative time of total pulses
unsigned long latestpulse=0;
unsigned long interval = 10000; //setting 6 seconds as max read time
#include <LiquidCrystal.h> //include LCD

LiquidCrystal lcd(12, 11, 6, 5, 4, 3);

void setup()

{
  pinMode(LED, OUTPUT);
  pinMode(din,INPUT);
  lcd.begin(16, 2);// set up the LCD's number of columns and rows:
  lcd.print("AV. HEART RATE:");// Print a message to the LCD.
  Serial.begin(9600);
}

void loop()
{
check = digitalRead(din);
delay(100);
if(check != prevcheck)
  {
  if (check == LOW)
    {
    digitalWrite(LED, LOW);
    }

  if (check == HIGH)
    {
    digitalWrite(LED, HIGH);

    if (pulsecount == 0 ) // if this is the first count
      {
      firstpulse = millis(); // recording time of first pulse
      pulsecount = pulsecount +1; //increment the counter
      }

    if (pulsecount >=1 )
      {
      pulsecount = pulsecount + 1; // increment pulse counter when detected
      latestpulse = millis(); // recording latest pulse time
      }
    }
  }

//-----DISPLAY------///

if (latestpulse - firstpulse > interval) //checking if 6 seconds has elapsed
  {
  BPM = pulsecount*10;
  Serial.println(BPM); //display the number of counts over 6 seconds
  pulsecount = 0; // counter restarted
  firstpulse = 0; // restart for time of new first-pulse read 
  latestpulse = 0; // restart for new time readings
  lcd.setCursor(0, 1); //setting LCD cursor to 1st column, second row
  lcd.print(BPM); // print the HR on the LCD as well
  lcd.setCursor(5,1); // setting cursor in 5th column, row 2
  lcd.print("BPM"); // printing text next to BPM
  }
prevcheck = check;
}

Thanks

The LCD code is dodgy. You may be leaving digits on the screen from prior readings. Either clear the LCD or print spaces after the number. Frankly, I'd concentrate on the results from the serial port for now until you're convinced that the sensing is working.

I think you're trying to take too many steps at the same time.

Start by writing your sketch so that it detects each pulse, using the logic demonstrated in the state change example.

Then add some code that detects when the elapsed time reaches six seconds. I suggest this timeout should be independent of the pulse detection/timing logic since you presumably don't want it to hang if no pulse is detected.

Once that is working, leave that logic alone and just focus on the code that is run each time a pulse is detected. It needs to determine whether this is the first pulse or not and either save the first pulse time, or save the last pulse time and increment the pulse count. You could use the non-zero first pulse time to determine whether this is the first pulse or not.

Once that's working, add some code that executes after the six second timeout has elapsed that does your arithmetic to calculate the bps.

At each step, make sure the code you're adding / changing does what you want before you move on to add the next bit of functionality. You're only looking at about a dozen lines of code and none of it is complicated, but you need to develop it methodically or you will just end up going round in circles.

Woops! Thanks for the replies guys

I had :

unsigned long interval = 10000; // pulse reading time
BPM = pulsecount*10; // multiply by (6) to get BPMinute

When changed:

unsigned long interval = 10000; // pulse reading time
BPM = pulsecount*6; // multiply by (6) to get BPMinute

Here are some of my readings:

84
90
84
102
90
90
78
90

Thanks

Thank you!! The code seems to work now. Will test again in a few hours. :slight_smile: - the LCD display matches the values on the serial monitor.

My next step is to use bluetooth to send and receive the signal - using the rn-42 modules. Will a new post be needed or continue with this?

Current code:

int LED = 13; //LED to blink when pulsing
int din = 2; // digital pin 2 to read 0V or 5V
int check; // checks current status of digital pin 2
int prevcheck;// previous check status
int pulsecount = 0; //number of pulses (highs) seen on the digital pin
int BPM = 0; //beats perminute = counter value * 10
unsigned long firstpulse =0; //time of first pulse
unsigned long timecount = 0;//cumulative time of total pulses
unsigned long latestpulse=0;
unsigned long interval = 10000; //setting 10 seconds as max read time
#include <LiquidCrystal.h> //include LCD

LiquidCrystal lcd(12, 11, 6, 5, 4, 3);

void setup()

{
  pinMode(LED, OUTPUT);
  pinMode(din,INPUT);
  lcd.begin(16, 2);// set up the LCD's number of columns and rows:
  lcd.print("AV. HEART RATE:");// Print a message to the LCD.
  Serial.begin(9600);
}

void loop()
{
check = digitalRead(din);
delay(100);
if(check != prevcheck)
  {
  if (check == LOW)
    {
    digitalWrite(LED, LOW);
    }

  if (check == HIGH)
    {
    digitalWrite(LED, HIGH);

    if (pulsecount == 0 ) // if this is the first count
      {
      firstpulse = millis(); // recording time of first pulse
      pulsecount = pulsecount +1; //increment the counter
      }

    if (pulsecount >=1 )
      {
      pulsecount = pulsecount + 1; // increment pulse counter when detected
      latestpulse = millis(); // recording latest pulse time
      }
    }
  }

//-----DISPLAY------///

if (latestpulse - firstpulse > interval) //checking if 6 seconds has elapsed
  {
  BPM = pulsecount*6;
  Serial.println(BPM); //display the number of counts over 6 seconds
  pulsecount = 0; // counter restarted
  firstpulse = 0; // restart for time of new first-pulse read 
  latestpulse = 0; // restart for new time readings
  lcd.setCursor(0, 1); //setting LCD cursor to 1st column, second row
  lcd.print(BPM); // print the HR on the LCD as well
  lcd.setCursor(5,1); // setting cursor in 5th column, row 2
  lcd.print("BPM"); // printing text next to BPM
  
 }
prevcheck = check;
}

Thanks

Actually there is an issue with the LCD display :roll_eyes:

When serial monitor is opened and my pulse goes above 99 - e.g 102, the LCD holds the 3rd bit until board is reset or serial monitor reopened.

Hence large reads of e.g 962 on the LCD.

How can i deal with this?

Thanks

I suggest that rather than just printing BPM directly with 'lcd.print(BPM)', you format it to a string and then print the string. This gives you control over padding and alignment of the number.

char buff[5]; // make it long enough for your longest string, plus a space for the null terminator
snprintf(buff, sizeof(buff), "%3d", BPM);
lcd.print(buff);

Thanks for the reply PeterH - it seems to have fixed that issue.

I plugged in the Arduiono today - same setup as yesterday - and the LED is now out of sync with the heratbeat, giving sporadic pulses.

Here are some of my readings from the serial monitor:

102
156
162
162
138
132
108
72
78
90
162
162

Going back to yesterdays code has't changed anything. Is there a timing issue in the loop? I don't understand why it should suddenly stop working after a few hours

Thanks

Sounds like you have a wiring issue.

wildbill:
Sounds like you have a wiring issue.

Yes. I don't know what sort of sensor you're using but I guess it will need to be very sensitive and could be upset by tiny electrical issues.

Do you still have that delay() call at the top of your loop() function? I can't see the point of that, and it would reduce the accuracy of your timing quite substantially since you only get to poll the input ten times a second. Depending on the nature of the sensor that might even cause you to miss beats.

Hey, thanks.

The delay in the loop slows the execution a bit, without it the read is too large and the LED flickers at times. - But it does seem like i'm missing // adding pulses

With regards to the wiring - i moved each wire connecting the arduino and the LCD and it measures more realistic values. my current setup:

Sensor circuit:
IR LED next to OPT101 - transimpedance amp with photodiode. Signal passes through (LP filter + Active HP filter)*2. Fed into arduino

Display:
Standard Hitachi LCD

The sensor circuit is on a veroboard, the LCD on a breadboard. All of this is powered by the arduino's 5 Volts.

Is this overloading the arduino?

Thanks

Any suggestions would be much appreciated

Thanks