WorldFamousElectronics / PulseSensorPlayground

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

Can't acquire .sawStartOfBeat() #145

Closed olsonap closed 2 years ago

olsonap commented 3 years ago

So this problem is a bit complicated, and it's not on your end but I'm hoping you can help.

I'm working on a project for an ESP32 using the ESP-IDF. The Arduino library is active in the project, so I should be able to use everything as normal. I created the pulsesensor aspect in Arduino first to ensure it worked, but I'm having trouble after transferring it into my project.

This file, pulseSensorArduino.cpp, is where I'm having issues. You can see the rest of my project here but I'll try to explain the interactions. https://github.com/olsonap/JAMLabs

`#include "Arduino.h"

define USE_ARDUINO_INTERRUPTS false // Set-up low-level interrupts for most acurate BPM math.

include // Includes the PulseSensorPlayground Library.

include "main.h"

extern "C" void getHRSensorValues(); extern "C" void run(); extern "C" void app_main2();

// Variables const int OUTPUT_TYPE = SERIAL_PLOTTER;

const int PulseWire = 4; // PulseSensor PURPLE WIRE connected to ANALOG PIN 0 const int PulseWire2 = 32; const int PULSE_SENSOR_COUNT = 2; int Threshold = 550; // Determine which Signal to "count as a beat" and which to ignore. // Use the "Gettting Started Project" to fine-tune Threshold Value beyond default setting. // Otherwise leave the default "550" value.

byte samplesUntilReport; const byte SAMPLES_PER_SERIAL_SAMPLE = 10;

PulseSensorPlayground pulseSensor(PULSE_SENSOR_COUNT); // Creates an instance of the PulseSensorPlayground object called "pulseSensor"

unsigned long lastBeatSampleNumber[PULSE_SENSOR_COUNT]; int PTT; int PTTdisplay; int BPMdisplay;

void setup() {
Serial.begin(115200); printf("SASQUATCH CHECK"); pulseSensor.setOutputType(OUTPUT_TYPE); pulseSensor.analogInput(PulseWire, 0); pulseSensor.analogInput(PulseWire2, 1);
pulseSensor.setThreshold(Threshold);
}

// Initializes app_main2 extern "C" void getHRSensorValues(){ printf("This is hit once as a mediator to app_main2"); app_main2(); return; }

// PulseSensor Engine void run() { //app_main();

//while (true){
    int PULSE_SENSOR_COUNT = 2;
    int myBPM = pulseSensor.getBeatsPerMinute(0);  // Calls function on our pulseSensor object that returns BPM as an "int".
                                            // "myBPM" hold this BPM value now. 
    int myBPM2 = pulseSensor.getBeatsPerMinute(1);

    //printf("%i", myBPM);
    if (pulseSensor.sawNewSample()) {
        if (--samplesUntilReport == (byte) 0) {
            samplesUntilReport = SAMPLES_PER_SERIAL_SAMPLE;

            pulseSensor.outputSample();
            //if (pulseSensor.sawStartOfBeat()) {            // Constantly test to see if "a beat happened". 
            // Serial.println("♥  A HeartBeat Happened ! "); // If test is "true", print a message "a heartbeat happened".
            // Serial.print("BPM: ");                        // Print phrase "BPM: " 
            // Serial.println(myBPM);                        // Print the value inside of myBPM. 
            // Serial.print("BPM2: ");
            // Serial.println(myBPM2);
            //}
            for (int i = 0; i < PULSE_SENSOR_COUNT; ++i) {
                printf("I can get here");
                if (pulseSensor.sawStartOfBeat(i)) {
                    printf("I can't get here");
                    pulseSensor.outputBeat(i);
                    lastBeatSampleNumber[i] = pulseSensor.getLastBeatTime(i);
                    if(i == 1){
                        PTT = lastBeatSampleNumber[1] - lastBeatSampleNumber[0];
                    }
                    // If both sensors match
                    if (((myBPM - myBPM2) < 5) && ((myBPM - myBPM2) > -5)){
                        BPMdisplay = int((myBPM + myBPM2) / 2);
                        if ((PTT > 500) && (PTT < 1000)){
                            PTTdisplay = PTT;
                        }
                    }
                    // If both sensors don't match
                    else if ((myBPM > 30) && (myBPM < 210)){
                        BPMdisplay = myBPM;
                    }
                    else if (myBPM >= 210){
                        BPMdisplay = 210;
                    }
                    else if (myBPM <= 0){
                        BPMdisplay = 0;
                    }
                    // Find length of numbers for bpm and ptt
                    unsigned int lenBPM = 0;
                    unsigned int lenPTT = 0;
                    int n = BPMdisplay;
                    int p = PTTdisplay;
                    do {
                        ++lenBPM; 
                        n /= 10;
                    } while (n);
                    do {
                        ++lenPTT;
                        p /= 10;
                    } while (p);
                    // The "val*" variables are declared in main.cpp and accessed using main.h
                    if ((lenBPM <= 3) && (BPMdisplay > 0)){
                        *p1 = BPMdisplay / 100 % 10;
                        *p2 = BPMdisplay / 10 % 10;
                        *p3 = BPMdisplay % 10;
                    }
                    if ((lenPTT <= 4) && (PTTdisplay > 0)){
                        *p4 = PTTdisplay / 1000 % 10;
                        *p5 = PTTdisplay / 100 % 10;
                        *p6 = PTTdisplay / 10 % 10;
                        *p7 = PTTdisplay % 10;
                    printf("Never gotten here");
                    }
                }
            }
        }
    }
//}
return;

} // App_main2 initializes Arduino library void app_main2() { initArduino(); printf("This initializes once as desired"); // Trying to get it to run //run(); return; } `

My main.cpp file is initializing the coexistence of BT Classic audio and a BLE Gatts server. The Gatts server is meant to send the HR and PTT data, which is the purpose of the pointers (p1 - p7) in the code above. There are a couple printfs in the code that show where I'm currently stuck. I can get to the for loop that counts through the number of sensors, but I can't get passed sawStartOfBeat(i).

Also, a few lines up below int my BPM = pulseSensor.getBeatsPerMinute(1); I was printing myBPM and getting 0. There's a while loop in my main.cpp that runs this function rapidly, so I'm pretty sure the .sawNewSample() is getting called enough.

See anything glaring?

olsonap commented 3 years ago

For clarity, the above code was formatted weird and I'm not great with markup yet. The run() function is inside an infinite while loop in main.cpp. The while loop shown in the code snippet is (obviously) commented out.

olsonap commented 3 years ago

Turns out that was a baud rate issue due to multiple baud rate settings.

HOWEVER, now I'm getting BPM that's about two times too high and if it comes down, it tends to creep up. I think I'm dealing with a timing issue, but I'm not sure how to resolve. I'm using vTaskDelay(10 / portTICK_RATE_MS); at the moment and BPM is pretty stable at 200 on the earlobe.

biomurph commented 3 years ago

@olsonap if your feeling stable with a BPM of 200, you might be in cardiac arrest....

There are known issues when you just dive into the ESP32 land with our library. It is doable, but it's not easy. Please look at some old issues? There seems to be a way to integrate the timer on the ESP32 to get more accurate sample rate that our algorithm can work with. It's likely that the high (inaccurate) BPM that you're getting is a result of that.

Tell me more about your hardware. I have an ESP32 here, but it usually helps to know exactly what you're using.

Also SUPER IMPORTANT! send me a pic of your Pulse Sensor and hardware setup. Sounds crazy, but it can help enormously.

olsonap commented 3 years ago

Lol, what I meant was the BPM is holding (stable) at about 200 BPM despite my very normal, 2-digit heartrate.

I'm using a tinyPICO from UnexpectedMaker (UM), which uses an ESP32-PICO-D4. I'm also using UM's I2S audio shield. The bulk of the code is coming from ESP-IDF examples, including a2dp-gatts-coex and gatts-server. I'm using two pulsesensor.com sensors (I'll attach an image shortly) and I've seen how many fakes are out there but I think I've got real ones based on the issues section on this page. I'm using a lipoly battery from adafruit although it's been plugged into 5V during the development process. My sensors are connected to 3v3, ground, pin 4 (sensor 0) and pin 32 (sensor 1).

I think I'm still dealing with a baud rate issue. When I force part of the IDF down to 9600, I get one good HR from just one of the sensors. When I allow the system to use 115200, both sensors work poorly at inflated bpms. If this is what's going on, it's an IDF issue and I think it'll work better when I'm no longer using the serial monitor.

Just thinking out loud.

olsonap commented 3 years ago

16370333802455830095739451871605 16370333973735495991268043136108 16370334323284318211824430461232 16370334685919088258177926187751

olsonap commented 3 years ago

Hardware's not much to look at I'm afraid.

biomurph commented 3 years ago

I can verify that you're using a legit PulseSensor, and not some cheap knockoff.

have you tried just running the pulse sensor code on your platform without any other stuff? The ESP32 can be tricky because it is a microcontroller and a radio, so it will get 'hijacked' by the radio and loose it's sense of time. Other things: I'm not sure what you're doing with the 'run' and 'main' loops. What's your dev environment? There may be real issues using our existing library given that we require accurate timing to get good IBI and BPM values. Again, check the issues past for mention of ESP or other wifi modules.

olsonap commented 3 years ago

I got it working well in Arduino using the serial monitor and a sketch very closely resembling the run() function. Of course, I had to use your alternative method with interrupts off, but that was always the plan anyway. The values were all very reasonable and didn't float around at all after a couple seconds. That's when I started moving over to the IDE, but I had to make a lot of adjustments to my epressif coexistence code in the process. I'm using the ESP-IDF in Visual Studio Basic, and I'm using "Arduino as a component" to keep your library functional. There's a little bit of C and C++ mixing involved too.

olsonap commented 3 years ago

I'll look into past issues tomorrow. I'm optimistic though, it feels like I'm close

biomurph commented 3 years ago

Yeah, it sounds like a kludge.

can you get the serial port running and look at the signal? That is always my go-to. If I can see a signal that is legit, then I at least think that I can find the artifacts I'm looking for in it...

Also, I think there is really some fundamental timing that's not being addressed.

olsonap commented 3 years ago

PXL_20211116_042907424 It's working great 👍

biomurph commented 3 years ago

Yes, that looks great.

I think you're right. the issue you're having is with the ESP variant and not with the hardware. Interesting that you can get good data when you are at a lower baud rate... It's hard to tell from your code being formatted weirdly in this thread, but try to see if there are areas where you are spending too much time doing stuff.

When not using the hardware timer, the BPM Alternative sketch is 'manually' gathering the beats based on a software timer set to 500Hz (sample every 2mS). When you call the sawNewSample() function, that's when it checks to see if it's time to get the next sample. I would think that a higher baud rate would make the duration of your time 'doing stuff' greatly reduced.

Also, we are relying on the ability to do software timing in microseconds. This can be problematic in the ESP32 https://esp32developer.com/programming-in-c-c/delays/delays That's just one reference, but I don't think the ESP32 can do sub millisecond timing without grabbing a hardware timer. If you don't need to constantly report the BPM, you can gather data during dedicated time, and then report it?

olsonap commented 2 years ago

Hello again. So how many times should I be calling analogRead(PulseWire) per second to be successful?

Also, I need to calculate PIR. Any tips on how to get the time values of these points? image

I just need to get the time value of peaks and lows.

biomurph commented 2 years ago

@olsonap In our PulseSensor Playground library, we are targeting a 500Hz sample rate. That has been determined to be optimal for accurate BPM and IBI (Interbeat Interval) values. If you're not using the PulseSensor Playground library, you will need to time your sample reading to be as accurate as you need. The lower the frequency of sample reading, then the lower resolution of BPM and IBI.

As I mentioned, the ESP hardware has some issues when using the millis() and micros() timing functions in Arduino. There is an old issue thread on this repo that has some code that a user wrote to grab a hardware timer on th ESP that you can find and use, I'm sure.

As for your PIR requirement, you should know that the Pulse Sensor hardware does not deliver a "raw" PPG signal. We condition the signal with hardware filtering and amplification to deliver a large, clean, easy to use pulse signal. If you are looking for the raw PPG to find time-domain artifacts and morphology, you may need to get a different sensor.

olsonap commented 2 years ago

Understood. It looks like it was running at a very consistent 202Hz. I managed to find what I believe is a reliable timer in the ESP-IDF and have sped it up to as much as 1650Hz by adjusting baud rate and trimming some fat. I'll have the sensors in front of me later to test.

The PIR I'm after is a dimensionless ratio, so I don't necessarily care about time, just the distance between peaks.

biomurph commented 2 years ago

@olsonap Oh, great! Glad to hear that you have captured a consistent sample rate. Please note that in my research and understanding, you don't need to go much over 500Hz. You will need time between sample taking to run algorithms and do other things, likely?

Also, I understand the PIR, and it is derived from the raw PPG waveform. Our Pulse Sensor hardware does filtering and amplification on the raw waveform, so you will likely get values for PIR that are not 'real', meaning that they are not based on actual photodiode current.

olsonap commented 2 years ago

Oh okay. That's fine for my purposes I'd say. I was thinking of altering PulseSensor.cpp a bit to find the sampleCount of two consecutive troughs (calling their sampleCounts systole1 and systole2) and then the next peak (diastole). (diastole-systole1)/(systole2-systole1) would be my pseudo-PIR.

biomurph commented 2 years ago

@olsonap sounds like a plan!

If your feel resolved, please close this issue. Let us know if you have any other questions or project ideas.

olsonap commented 2 years ago

Thanks for your help!