Problems with PulseSensor Libraries on Photon 2

I use the PulseSensor in my class, and I'm in the process of migrating from Argon to Photon 2.

I have been using the PulseSensorAmped library, but that requires nrfx_timer.h which I gather is not designed for the Photon 2.

Is there a way to adapt this library to work with the Photon 2, or is there a way to make any library work with the Photon 2?

BTW There is another library PulseSensor_Spark which seems more complex to use, but it requires SparkIntervalTimer which the Photon 2 migration guide says is not compatible.

It's feasible, with two possible levels of difficulty.

As you noted, PulseSensor_Spark requires SparkIntervalTimer which does not work on RTL872x (P2, Photon 2, M-SoM). However, it only uses it to periodically sample the ADC from an ISR, at 2 millisecond intervals (2000 uS).

This isn't a great use of a hardware timer, because it also disables interrupts while sampling and processing the ADC, causing the system to run for long periods with interrupts disabled, which will result in poor performance.

The easiest option that might work is put the code in a worker thread instead of a hardware interrupt handler. The thread scheduler runs at a 1 millisecond cadence, so this will probably work. Just take the pulseISR() code and remove the calls to interrupts() and noInterrupts() and put it in a thread with a delay(2) at the bottom of the worker thread loop.

The best but most complicated option is to use the DMA feature of the ADC so the samples are continuously read by the MCU and stored in a buffer, which eliminates the blocking and jitter. However I don't think anyone has implemented ADC DMA on Particle RTL872x. I think it's possible, but it may be difficult.

@rob7, a while back I had written a software timer version of PulseSensor. The only "issue" with it is that it disables interrupts during sampling which is not ideal for DeviceOS, however short that time is. The noInterrupts()/interrupts() code is a vestige of the previous interrupt-driven code where you did not want to be interrupted again while processing the current interrupt.

With the code running in a sotware timer, there are no overlapping interrupts. There are, however, other DeviceOS interrupts that can impact but I suspect not enough to be considered a problem given the 2 millisecond sampling interval. The only thing in the code needing "protection" may be the analogRead() which could, if necessary, be protected with a SINGLE_THREAD_BLOCK() since software timers run in their own thread, or with ATOMIC_BLOCK() which may be extreme. Perhaps @rickkas7 has some suggestions.

1 Like

Thanks @peekay123 and @rickkas7 . I started with @peekay123 's project and adapt @rickkas7 's recommendation to use a worker thread on a 2ms delay.

I needed to use this library for my students so I wanted to abstract some of the details so I re-created the PulseSensor class from previous libraries. The code seems to provide semi-accurate heart rate but it does fluctuate a bit and sometimes stays high (around 240). I'm not sure if that's my code or just the sensor.

I'd really appreciate any feedback since admittedly I'm not an expert in working with threads in DeviceOS. The worker thread is threadFunction in PulseSensor.cpp.

Another element I wasn't sure about was the QS variable in the PulseSensor class, which is used to note if a beat was detected. In @peekay123 's code, it is set to false after a beat is displayed on the LED so I tried to mimic that by setting it to false after the BPM is read in getBPM.