Closed goldenhenry closed 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?
@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?
@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.
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
@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?
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.
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.
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"?
@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
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.
@goldenhenry This is awesome. I will roll it into our PusleSensor Playground Library.
@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?
@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
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?