Ways to limit the sources of noise
Where does the noise come from in the Modern Device Pulse / PSO2 sensor? I’ll make a list of some of the places I think noise comes from. Some of these will be supported by experiments to get rid of the noise and others will remain more speculative. The SI1143 chip in the PSO2 sensor uses a photodiode and an amplifier, to amplify the voltage for sampling by an A to D converter.
Sampling on the power line frequency
In my experience, anytime you use an analog amplifier at medium to high gain, in a an indoor environment that contains power lines, the possibility of power-line hum being induced onto your signal has a good probability. This is true even if the devices are powered by batteries – such as a laptop. The field is so pervasive that any nearby amplifier will capacitively couple with it at some level.
One classic way to minimize this power-line noise is to sample on the power-line frequency. This insures that any sixty cycle signal impressed upon on the desired signal is averaged out. It is simple and easy but comes with one very large drawback, it is slow. A power line cycle in the US is 16.666 ms which in terms of a computer’s speed, is basically forever (266,666 Arduino clock cycles for example). Luckily for us, in terms of a human heartbeat, 60 hz is still fairly fast and a human heartbeat sampled at 60 hz or even 30 hz still looks great.
To use power-line sampling with the pulse sensor, just un-comment the line
“#define POWERLINE_SAMPLING // samples on an integral of a power line period [eg 1/60 sec]”
The time for the sampling is defined farther down the file.
When we compile and upload the Arduino sketch (don’t forget to comment out the extra Serial.print’s at the end), and then move to the Processing sketch, we can see what a nice clean trace the powerline sampling provides.
However, there is a downside. Because the maximum sampling rate is now 16ms or longer, the signal has been sort of “quantized” at the rate. So if you examine the numbers, you will see several 975’s and several 966 – 967’s in the dataset. It is unlikely that the heartbeat was that closely coordinated to the same numbers (it’s analog after all), it just happened to get quantized to the same numbers.
So why is this quantized effect important. If you are just interested in a digitized version of your heartbeat, it’s not very important, and the powerline sampling option is a good one. If however you wish to experiment with heartrate variability, then quantizing at 16ms is too slow for some of the data you probably wish to capture.
If we lose the slower sampling by commenting out the “POWERLINE_SAMPLING” line, and set the sensor back the sensor back to a setting of 5 samples, compiling, uploading, and sending the data to Processing, the plot looks like this.
Still a very clear trace but now the data is lot more “continuous”. It’s a bit hard to tell these things from one graph though, you can still see two 916s for data points, we’ll chalk that up to a fluke. If we now drop the samples to 2, you can see a lot more noise in the data.
There are some disturbingly large spikes in the data. You can see one just about in the middle of the page and one in the next heartbeat. The datasheet warns about taking samples that are not synchronized with the internal write process, which they say, may lead to “hybrid” readings – in other words, a low byte from one sample and a high byte from a different sample.
After banging my head against the problem for too long, and getting out the oscilloscope. I did indeed come to the conclusion that what I was looking at were hybrid readings. So off to coding I went and cooked up a scheme that read the sensor, based on an interrupt that was set to fire once a reading had completed. The coding effort proved slightly time consuming, like all coding, but when I was done, I was rewarded with a nice clean trace from all the LED readings. The image below happens to be an average of all three LEDs but I did check all the channels for glitches to avoid getting bit later.
There is no averaging at all in this image, (except for combining 3 LEDs). Each datapoint only represents one reading, so the results look very nice. As always there are a few caveats that should be mentioned. It’s not really fast, meaning that the throughput on data points to Processing is only around 80 hz. I tried increasing the sensor’s read rate, by lowering the parameter in the following line
pulse.setReg(PulsePlug::MEAS_RATE, 0x88); // see datasheet 0x84 default
This should have, in theory, sped up the sketch. It had little effect though, and if I increased it high enough, started introducing the “hybrid-read-glitch” errors again. This probably means that the rest of my sketch (and perhaps the bit-banged I2C library) is too slow to benefit from speeding up the reads on the sensor. In any case I now have a clean read of the sensor (although a bit pokey) that I can use for pulse and other things – such as proximity sensing. Who wants to go to a lot of work cleaning up data when it can be cleaned up much better in hardware from the beginning?
Another way of limiting noise is to maximize the signal at the sensor (running it close to saturation). This insures that our signal-to-noise ratio is maximized. This is done by using high brightness LEDs and controlling them with the LED current controls built into the chip.
One thing on the TO-DO list should probably be an automatic routine for adjusting LED currents into range. Certainly for a pulse sensor that could accommodate a range of finger sizes and skin colors this would be required.
I coded some easy LED controls into the top of the interrupt-driven sketch, so it should be easy to tweak the LED values. You also may need to tweak these values if anything changes, such as modding the LEDs on the boards. The sensor is still undergoing tweaks too, so you may need to tweak LED brightnesses with newer versions of the sensor.
/* LED currents listed in the datasheet
Value Current in mA
const int LED1_brightness = 7; // LED brightnesses from 0 to 15 are valid
const int LED2_brightness = 2;
const int LED3_brightness = 1;
So we’ve got a nice smooth waveform at this point with not too much noise showing. For users who wish to use just the human (or animal – much early testing of PSO2 sensors was done on dogs) heartbeat in a project or an artwork you’re pretty much done at this point.
There is lots I could talk about in my Processing Sketch, but I think I’ll let people dig into it on their own. There is a fair amount of experimental and klugey fixes in the code, that I have a feeling will change in the near future.
One of the fun and easy parts about working with heart rate signals is that they are very slow moving signals – especially compared with radio frequency, audio or even power line signals. Another intriguing aspect of heartbeat signals (and biological signals in general) is that they never exactly repeat, with each signal being distinctive but unique. In the heartbeat signal at least, this is thought to come from contributions from the sympathetic and parasympathetic nervous systems.
Heart Rate Variability (HRV)
As a really brief introduction to heart-rate variability perhaps this video is a good start. The beginning is the important part – the later part of the video is just setting up the research equipment the researchers are using.
Academic Video on Heart Rate Variability
A good itnroduction to using HRV for assessing fitness and athletic training may be found here.
The Processing sketch that I have been using to generate the waveforms above also has heart rate variability built in. For the math geeks, I’m sorry but it doesn’t do any fancy math. It just displays heart rate variability. I’d love to have a better tool, so please point me to anything that looks like a decent starting point for analyzing HRV. I’ll post any efforts from users that make interesting progress, or improve upon my sketch. There’s lots of room for improvements.
The yellow lines at the bottom are a bar chart of HRV. Durring most of the bottom screen capture (the two screens are not syncronous) I was breathing deeply. Toward the right side you can see where I dozed off after a day of coding. Some of the out-of-place lines are motion artifacts too (unfortunately). As I woke up at the far right you can see respiration return to (my attentive) normal, fairly deep breathing.
The interrupt-based sketch sketch has been added to the latest library revisions at Github.
The sensor is here in case you want one..
next – A stab at O2 sensing