WorldFamousElectronics / PulseSensorPlayground

A PulseSensor library (for Arduino) that collects our most popular projects in one place.
https://PulseSensor.com
MIT License
200 stars 97 forks source link

Cannot get correct BPM while keep getting data from firebase #80

Closed goldenhenry closed 5 years ago

goldenhenry commented 5 years ago

I am doing an IOT health alert project. However, I met a problem when keep checking the firebase data. The BPM seems become incorrect or even not detected, keep having zero. Is there any way I can modify the code to get better BPM?

#include <WiFi.h>
#include <IOXhop_FirebaseESP32.h>
#include<Wire.h>

// Set these to run example.
#define FIREBASE_HOST "xxx"
#define FIREBASE_AUTH "xxx"
#define WIFI_SSID "xxx"
#define WIFI_PASSWORD "xxx"
#define onboardLED 2

#define PROCESSING_VISUALIZER 1
#define SERIAL_PLOTTER  2

//  Variables
#define ESP32
int pulsePin = 34;                 // Pulse Sensor purple wire connected to analog pin 34 , ADC6
// 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 int outputType = SERIAL_PLOTTER;
volatile int newbpmstatus = 0, oldbpmstatus = 0, bpmalertcd = 0;
int bpmalertflg = 0;
int sentfb = 0;

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 = 530;                // used to find instant moment of heart beat, seeded
volatile int amp = 0;                   // 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

hw_timer_t * timer = NULL;
volatile SemaphoreHandle_t timerSemaphore;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

hw_timer_t * timer1 = NULL;
volatile SemaphoreHandle_t timerSemaphore1;
portMUX_TYPE timerMux1 = portMUX_INITIALIZER_UNLOCKED;

//MPU6050----------------------------------------------------------------------------------------------------------------------------------------------------------
const int MPU_addr = 0x68; // I2C address of the MPU-6050
int16_t AcX, AcY, AcZ, Tmp, GyX, GyY, GyZ;
float ax = 0, ay = 0, az = 0, gx = 0, gy = 0, gz = 0;

//int data[STORE_SIZE][5]; //array for saving past data
//byte currentIndex=0; //stores current data array index (0-255)
boolean fall = false; //stores if a fall has occurred
boolean trigger1 = false; //stores if first trigger (lower threshold) has occurred
boolean trigger2 = false; //stores if second trigger (upper threshold) has occurred
boolean trigger3 = false; //stores if third trigger (orientation change) has occurred

byte trigger1count = 0; //stores the counts past since trigger 1 was set true
byte trigger2count = 0; //stores the counts past since trigger 2 was set true
byte trigger3count = 0; //stores the counts past since trigger 3 was set true
int angleChange = 0;
int sendAM2fb = 0;
volatile int fbcheckoperation = 0;

//------------------------------------------------------------------------------------------------------------------------------------------------------------------
void IRAM_ATTR onTimer() {
  portENTER_CRITICAL_ISR(&timerMux);
  getPulse();
  portEXIT_CRITICAL_ISR(&timerMux);
  xSemaphoreGiveFromISR(timerSemaphore, NULL);
}

void IRAM_ATTR onTimer1() {
  portENTER_CRITICAL_ISR(&timerMux1);
  fbcheckoperation = 1;
  portEXIT_CRITICAL_ISR(&timerMux1);
  xSemaphoreGiveFromISR(timerSemaphore1, NULL);
}

void getPulse()
{
  // read the Pulse Sensor, bits of ESP32 ADC ch is 4 times larger
  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
      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
    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 = 530;                          // 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
    QS = false;
    BPM = 0;
    IBI = 600;                  // 600ms per beat = 100 Beats Per Minute (BPM)
    Pulse = false;
    amp = 100;                  // beat amplitude 1/10 of input range.

  }
}

void checkoperation()
{
  if (Firebase.getFloat("operation") == 1)
  {
    Firebase.setFloat("bpm", BPM);
    // handle error
    if (Firebase.failed()) {
      Serial.print("setting /number failed:");
      Serial.println(Firebase.error());
      return;
    }
    Firebase.setFloat("operation", 0 );
  }
}

void interruptSetup() { // CHECK OUT THE Timer_Interrupt_Notes TAB FOR MORE ON INTERRUPTS
  timerSemaphore = xSemaphoreCreateBinary();
  timer = timerBegin(0, 80, true);    // Use 1st timer of 4 (counted from zero).Set 80 divider for prescaler (see ESP32 Technical Reference Manual for more info).
  timerAttachInterrupt(timer, &onTimer, true);// Attach onTimer function to our timer.
  timerAlarmWrite(timer, 2000, true);// Set alarm to call onTimer function every second (value in microseconds). Repeat the alarm (third parameter)
  timerAlarmEnable(timer);// Start an alarm

  timerSemaphore1 = xSemaphoreCreateBinary();
  timer1 = timerBegin(0, 80, true);    // Use 1st timer of 4 (counted from zero).Set 80 divider for prescaler (see ESP32 Technical Reference Manual for more info).
  timerAttachInterrupt(timer1, &onTimer1, true);// Attach onTimer function to our timer.
  timerAlarmWrite(timer1, 1000000, true);// Set alarm to call onTimer function every second (value in microseconds). Repeat the alarm (third parameter)
  timerAlarmEnable(timer1);// Start an alarm
}

void setup() {
  Wire.begin();
  Wire.beginTransmission(MPU_addr);
  Wire.write(0x6B);  // PWR_MGMT_1 register
  Wire.write(0);     // set to zero (wakes up the MPU-6050)
  Wire.endTransmission(true);
  Serial.begin(115200);             // we agree to talk fast!
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.print("connecting");
  while (WiFi.status() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }
  Serial.println();
  Serial.print("connected: ");
  Serial.println(WiFi.localIP());

  Firebase.begin(FIREBASE_HOST, FIREBASE_AUTH);
  interruptSetup();                 // sets up to read Pulse Sensor signal every 2mS
  pinMode(onboardLED, OUTPUT);
  digitalWrite(onboardLED, HIGH);
}

//  Where the Magic Happens
void loop() {
  if (fbcheckoperation == 1)
  {
    checkoperation();
    fbcheckoperation=0;
  }

  Signal = analogRead(pulsePin) / 4;
  if (BPM != 0 && BPM >= 60 && BPM <= 100)
  {
    newbpmstatus = 1;
  }
  else
  {
    newbpmstatus = 0;
  }
  if (newbpmstatus == oldbpmstatus)
  {
    if (newbpmstatus == 0)
    {
      bpmalertcd++;
      Serial.print("bpmalertcd: ");
      Serial.println(bpmalertcd);
    }
    else
    {
      bpmalertcd = 0;
    }
  }
  else
  {
    bpmalertcd = 0;
  }
  Serial.print(BPM);
  Serial.print(",");
  Serial.print(IBI);
  Serial.print(",");
  Serial.println(Signal);

  if (QS == true) {    // A Heartbeat Was Found

    // BPM and IBI have been Determined
    // Quantified Self "QS" true when arduino finds a heartbeat
    QS = false;                      // reset the Quantified Self flag for next time
  }
  if (bpmalertcd >= 2000 )
  {
    if (Firebase.getFloat("apr") == 0)
    {
      Firebase.setFloat("apr", 1);
      bpmalertcd = 0;
    }
    else
    {
      bpmalertcd = 0;
    }
  }
  oldbpmstatus = newbpmstatus;
  delay(20);                             //  take a break
  mpu_read();
  //2050, 77, 1947 are values for calibration of accelerometer
  // values may be different for you
  ax = (AcX - 2050) / 16384.00;
  ay = (AcY - 77) / 16384.00;
  az = (AcZ - 1947) / 16384.00;

  //270, 351, 136 for gyroscope
  gx = (GyX + 270) / 131.07;
  gy = (GyY - 351) / 131.07;
  gz = (GyZ + 136) / 131.07;

  // calculating Amplitute vactor for 3 axis
  float Raw_AM = pow(pow(ax, 2) + pow(ay, 2) + pow(az, 2), 0.5);
  int AM = Raw_AM * 10;  // as values are within 0 to 1, I multiplied
  // it by for using if else conditions

  Serial.println(AM);
  //Serial.println(PM);
  //delay(500);

  if (trigger3 == true) {
    trigger3count++;
    //Serial.println(trigger3count);
    if (trigger3count >= 10) {
      angleChange = pow(pow(gx, 2) + pow(gy, 2) + pow(gz, 2), 0.5);
      //delay(10);
      Serial.println(angleChange);
      if ((angleChange >= 0) && (angleChange <= 10)) { //if orientation changes remains between 0-10 degrees
        fall = true; trigger3 = false; trigger3count = 0;
        Serial.println(angleChange);
      }
      else { //user regained normal orientation
        trigger3 = false; trigger3count = 0;
        Serial.println("TRIGGER 3 DEACTIVATED");
      }
    }
  }
  if (fall == true) { //in event of a fall detection
    Serial.println("FALL DETECTED");
    digitalWrite(onboardLED, LOW);
    delay(20);
    digitalWrite(onboardLED, HIGH);
    fall = false;
    // exit(1);
  }
  if (trigger2count >= 6) { //allow 0.5s for orientation change
    trigger2 = false; trigger2count = 0;
    Serial.println("TRIGGER 2 DECACTIVATED");
  }
  if (trigger1count >= 6) { //allow 0.5s for AM to break upper threshold
    trigger1 = false; trigger1count = 0;
    Serial.println("TRIGGER 1 DECACTIVATED");
  }
  if (trigger2 == true) {
    trigger2count++;
    //angleChange=acos(((double)x*(double)bx+(double)y*(double)by+(double)z*(double)bz)/(double)AM/(double)BM);
    angleChange = pow(pow(gx, 2) + pow(gy, 2) + pow(gz, 2), 0.5); Serial.println(angleChange);
    if (angleChange >= 30 && angleChange <= 400) { //if orientation changes by between 80-100 degrees
      trigger3 = true; trigger2 = false; trigger2count = 0;
      Serial.println(angleChange);
      Serial.println("TRIGGER 3 ACTIVATED");
    }
  }
  if (trigger1 == true) {
    trigger1count++;
    if (AM >= 12) { //if AM breaks upper threshold (3g)
      trigger2 = true;
      Serial.println("TRIGGER 2 ACTIVATED");
      trigger1 = false; trigger1count = 0;
    }
  }
  if (AM <= 2 && trigger2 == false) { //if AM breaks lower threshold (0.4g)
    trigger1 = true;
    Serial.println("TRIGGER 1 ACTIVATED");
  }
  //It appears that delay is needed in order not to clog the port
  sendAM2fb++;
  if (sendAM2fb >= 1000)
  {
    Firebase.setFloat("am", AM);
    sendAM2fb = 0;
  }
  //  delay(100);
  //
}

void mpu_read() {
  Wire.beginTransmission(MPU_addr);
  Wire.write(0x3B);  // starting with register 0x3B (ACCEL_XOUT_H)
  Wire.endTransmission(false);
  Wire.requestFrom(MPU_addr, 14, true); // request a total of 14 registers
  AcX = Wire.read() << 8 | Wire.read(); // 0x3B (ACCEL_XOUT_H) & 0x3C (ACCEL_XOUT_L)
  AcY = Wire.read() << 8 | Wire.read(); // 0x3D (ACCEL_YOUT_H) & 0x3E (ACCEL_YOUT_L)
  AcZ = Wire.read() << 8 | Wire.read(); // 0x3F (ACCEL_ZOUT_H) & 0x40 (ACCEL_ZOUT_L)
  Tmp = Wire.read() << 8 | Wire.read(); // 0x41 (TEMP_OUT_H) & 0x42 (TEMP_OUT_L)
  GyX = Wire.read() << 8 | Wire.read(); // 0x43 (GYRO_XOUT_H) & 0x44 (GYRO_XOUT_L)
  GyY = Wire.read() << 8 | Wire.read(); // 0x45 (GYRO_YOUT_H) & 0x46 (GYRO_YOUT_L)
  GyZ = Wire.read() << 8 | Wire.read(); // 0x47 (GYRO_ZOUT_H) & 0x48 (GYRO_ZOUT_L)

}
biomurph commented 5 years ago

@goldenhenry First, please don't post your SSID and PASSWORD on this page.

Second, it looks like you are using some timer interrupts on the ESP32! Super cool! Can you point me to where you are working from to make that happen? I can help you work out the timer interrupts for Pulse Sensor so that we can improve the PulseSensor Playground for everyone.

It looks like you are using a different code base than the one that this repo holds, FYI.

You are using the onTimer to call getPulse() every 2mS, which is how we do it here at Pulse Sensor. However, when you go into the getPulse() you are NOT taking a sample of the Pulse Sensor. Instead, you are sampling the Pulse Sensor signal in the loop() function, and that seems to be happening once every 20mS. Far too slow for the getPulse routine to make any sense out of the signal. Try putting the analogRead(pulsePin) at the top of the getPulse() and see if that helps?

goldenhenry commented 5 years ago

@biomurph Thanks for you reply.

I actually used a coniferconifer/ESP32_pulsesensor and modify it. This coding is working when it is just using pulse sensor and mpu6050 gyro+accelerometer. However, when I add in wifi related coding the esp keep rebooting. At last, I found out the interrupt function of esp32 cannot include "analogread()" which will cause this error:

Guru Meditation Error: Core  1 panic'ed (Cache disabled but cached memory region accessed)
Register dump:
PC      : 0x400812d1  PS      : 0x00060034  A0      : 0x800810af  A1      : 0x3ffc0b70  
A2      : 0x00000023  A3      : 0x000000c0  A4      : 0x8008244d  A5      : 0x3ffd0960  
A6      : 0x00000000  A7      : 0x00000001  A8      : 0x3f40226c  A9      : 0x000000c0  
A10     : 0xbad00bad  A11     : 0x00000018  A12     : 0x80082a7e  A13     : 0x3ffd0940  
A14     : 0x00000001  A15     : 0x3ffc3afc  SAR     : 0x00000014  EXCCAUSE: 0x00000007  
EXCVADDR: 0x00000000  LBEG    : 0x4000c2e0  LEND    : 0x4000c2f6  LCOUNT  : 0x00000000  

Backtrace: 0x400812d1:0x3ffc0b70 0x400810ac:0x3ffc0b90 0x40081225:0x3ffc0bb0 0x40080ec1:0x3ffc0bd0 0x4008163d:0x3ffc0bf0 0x40081b19:0x3ffc0c10 0x4000bfed:0x00000000
Rebooting...
ets Jun  8 2016 00:22:57 
rst:0x3 (SW_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0018,len:4
load:0x3fff001c,len:956
load:0x40078000,len:0
load:0x40078000,len:13076
entry 0x40078a58

Hence, no matter what I did, if I need to "analogread()" during the interrupt routine, this error will occur. My own stupid solution will be keep sampling the signal at the loop, but processing the signal at the interrupt service routine. So, this prevents the error.

I found out that the firebase.get is lagging me. I took it out, then I can get quite consistent pulserate.

As you say, I do this

Signal = analogRead(pulsePin) / 4;

in my loop. Then I will process it at interrupt service routine every 2ms, but using the signal get maybe last few ms. So I not sure, by getting old signal (get from few ms ago), will it affected my result badly?

biomurph commented 5 years ago

@goldenhenry Interesting I will say again that you are using an old code base for Pulse Sensor. Go to the PulseSensor Playground repo under World Famous Electronics. There you will find library examples that don't use interrupts. They use a software timer instead which will make your work easier.

biomurph commented 5 years ago

Oh nevermind about the code base comment 😂

Pulsesensor playground does have tools to check your timing, so you can get a sense of your sample jitter. The tools are in the PulseSensorTimingStatistics. That can help you get a baseline of how much jitter you get with your setup

goldenhenry commented 5 years ago

@biomurph Software timer? Are you saying Free RTOS? I just checked the PulseSensorTimingStatistics but I dont think I understand how it works. Is it I just call the function inside the .cpp?

biomurph commented 5 years ago

Oh, no I don't mean RTOS. By software timer, I mean that when the _Alternative code us run, it doesn't use the hardware timer. Instead, it compares the current program time in micros with the previous sample time, and if it's time (or past time) to sample (once every 2mS) then a sample is taken. Simple software timing function. The fact that is uses measured time could cause some jitter in the actual sample timing, since it could be some microseconds later than the actual 2mS.

We don't really have a TimingStatistics example. That's something that I should work out...

@bneedhamia if you can, would you help out with some code that implements functions from the timing statistics portion of the library? I will be able to stab at it in a couple of days.

goldenhenry commented 5 years ago

I see. I know what you're saying now. I did test that code. It is working when there is less task in the loop. My tasks consider too much. Hence, I got too far inaccurate reading like 200++bpm consistently.

goldenhenry commented 5 years ago

late#update

I was managed to get the BPM reading as accurate as using UNO timer interrupt. In ESP32, I have to call the whole interrupt function externally by using

void IRAM_ATTR getpulse()

This function name will able to read the analog every time the timer interrupt get triggered without error I mentioned above.

I have a new problem which I should not ask here, but I still want to try to get answer here. I searched through INTERNET but I cannot find any datasheet of pulsesensor, is there any keyword needed other than "pulsesensor datasheet"?

biomurph commented 5 years ago

@goldenhenry that is awesome! Can you share your working sketch that targets the ESP32 using hardware interrupts? Would love to roll that into the library or set up example code (attributing to you, of course).

The datasheet for the Pulse Sensor can be found here https://github.com/WorldFamousElectronics/PulseSensorAmped_Hardware/blob/master/Pulse%20Sensor%20Data%20Sheet.pdf

goldenhenry commented 5 years ago
int pulsePin = 34;                 // Pulse Sensor purple wire connected to analog pin 34 , ADC6

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 Arduino finds a beat.

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 = 530;                // used to find instant moment of heart beat, seeded
volatile int amp = 0;                   // 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

hw_timer_t * timertocheckpulse = NULL; //declare timer global variable
portMUX_TYPE timertocheckpulseMux = portMUX_INITIALIZER_UNLOCKED;

void IRAM_ATTR getPulse()                   //must called using IRAM_ATTR in ESP32 since it need to be called in ISR
{
  Signal = analogRead(pulsePin) / 4;             // read the Pulse Sensor, bits of ESP32 ADC ch is 4 times larger
  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
      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
    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 = 530;                          // 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
    QS = false;
    BPM = 0;
    IBI = 600;                  // 600ms per beat = 100 Beats Per Minute (BPM)
    Pulse = false;
    amp = 100;                  // beat amplitude 1/10 of input range.

  }
}

NOTES:
//this is the Interrupt Service Routine 
//the interrupt handling routine should have the IRAM_ATTR attribute, in order for the compiler to place the code in IRAM. 
//Also, interrupt handling routines should only call functions also placed in IRAM,
void IRAM_ATTR onTimertocheckpulse() {
  portENTER_CRITICAL_ISR(&timertocheckpulseMux);
  getPulse();
  portEXIT_CRITICAL_ISR(&timertocheckpulseMux);
}

void interruptSetup() { // CHECK OUT THE Timer_Interrupt_Notes TAB FOR MORE ON INTERRUPTS (i referred https://techtutorialsx.com/2017/10/07/esp32-arduino-timer-interrupts/)
  timertocheckpulse = timerBegin(0, 80, true);    
  //1st input: Use 1st timer of 4 (counted from zero).
  //2nd input: Set 80 divider for prescaler (most of the ESP32 running 80MHz as base frequency, prescaler of 80 make it every tick with 1microseconds, easy to determine the time later)

  timerAttachInterrupt(timertocheckpulse, &onTimertocheckpulse, true);
  //1st input: timer global variable
  //2nd input: Attach onTimer function to correspond timer.
  //3rd input: interrupt to be generated at edge(true) or level(false)

  timerAlarmWrite(timertocheckpulse, 2000, true);
  //1st input: timer to be started
  //2nd input: as every tick is 1 microsecond, use 2000 to get timer total tick of 2miliseconds which is needed to check the signal evey 2ms
  //3rd input: true(repeat the alarm automatically);false will make this timer one-time timer

  timerAlarmEnable(timertocheckpulse);// Start the timer
}

void setup() {
  Serial.begin(115200);             // we agree to talk fast!
  }

void loop() {
  Serial.print(BPM);
  Serial.print(",");
  Serial.print(IBI);
  Serial.print(",");
  Serial.println(Signal);

  if (QS == true) {    // A Heartbeat Was Found

    // BPM and IBI have been Determined
    // Quantified Self "QS" true when arduino finds a heartbeat
    QS = false;                      // reset the Quantified Self flag for next time
  }
  delay(20);                             //  take a break

}

@biomurph Thanks for the datasheet! It helps! I wonder how it hides from Google.

This is my pulse sensor code target for ESP32.

I actually found this ESP32 Arduino IDE version for pulsesensor Amped when I started to code using Arduino IDE. As I mentioned, it could be run before I added Wi-Fi related codes. Hence, I modified it so it can interact with more codes such as running Wi-Fi or other sensors by referencing to ESP32 Arduino: Timer interrupts. The difference is calling

getpulse();

out of the ISR, and the function called should be declared as

void IRAM_ATTR getpulse()
{}

In this project, I didnt display the blinking LED or visualizer but only using Serial Plotter. However, it could be done by just adding the related code into part where the Pulse sensor Playground inserted. Hope these help.

biomurph commented 5 years ago

@goldenhenry This is awesome. I will roll it into our PusleSensor Playground Library.

goldenhenry commented 5 years ago

@biomurph
I'm sorry. I have to tag you again. https://github.com/WorldFamousElectronics/PulseSensorAmped_Hardware/blob/master/PulseSensorAmpd_1.sch

The schematic above cannot be opened by using Eagle. It says "line 1, column 1: Start tag expected."

I wonder can I get the schematic from you?

biomurph commented 5 years ago

@goldenhenry the schematics were designed in Design Spark, not Eagle. There is a PDF of the schematic linked to from here https://pulsesensor.com/pages/open-hardware