初始化提交
This commit is contained in:
@@ -0,0 +1,317 @@
|
||||
/*
|
||||
A central Playground object to manage a set of PulseSensors.
|
||||
See https://www.pulsesensor.com to get started.
|
||||
|
||||
Copyright World Famous Electronics LLC - see LICENSE
|
||||
Contributors:
|
||||
Joel Murphy, https://pulsesensor.com
|
||||
Yury Gitman, https://pulsesensor.com
|
||||
Bradford Needham, @bneedhamia, https://bluepapertech.com
|
||||
|
||||
Licensed under the MIT License, a copy of which
|
||||
should have been included with this software.
|
||||
|
||||
This software is not intended for medical use.
|
||||
*/
|
||||
#include <PulseSensorPlayground.h>
|
||||
|
||||
// Define the "this" pointer for the ISR
|
||||
PulseSensorPlayground *PulseSensorPlayground::OurThis;
|
||||
|
||||
|
||||
PulseSensorPlayground::PulseSensorPlayground(int numberOfSensors) {
|
||||
// Save a static pointer to our playground so the ISR can read it.
|
||||
OurThis = this;
|
||||
|
||||
// Dynamically create the array to minimize ram usage.
|
||||
SensorCount = (byte) numberOfSensors;
|
||||
Sensors = new PulseSensor[SensorCount];
|
||||
|
||||
#if PULSE_SENSOR_TIMING_ANALYSIS
|
||||
// We want sample timing analysis, so we construct it.
|
||||
pTiming = new PulseSensorTimingStatistics(MICROS_PER_READ, 500 * 30L);
|
||||
#endif // PULSE_SENSOR_TIMING_ANALYSIS
|
||||
}
|
||||
|
||||
boolean PulseSensorPlayground::PulseSensorPlayground::begin() {
|
||||
|
||||
for (int i = 0; i < SensorCount; ++i) {
|
||||
Sensors[i].initializeLEDs();
|
||||
}
|
||||
|
||||
// Note the time, for non-interrupt sampling and for timing statistics.
|
||||
NextSampleMicros = micros() + MICROS_PER_READ;
|
||||
|
||||
SawNewSample = false;
|
||||
Paused = false;
|
||||
|
||||
#if PULSE_SENSOR_MEMORY_USAGE
|
||||
// Report the RAM usage and hang.
|
||||
printMemoryUsage();
|
||||
for (;;);
|
||||
#endif // PULSE_SENSOR_MEMORY_USAGE
|
||||
|
||||
// Lastly, set up and turn on the interrupts.
|
||||
|
||||
if (UsingInterrupts) {
|
||||
if (!PulseSensorPlaygroundSetupInterrupt()) {
|
||||
Stream *pOut = SerialOutput.getSerial();
|
||||
if (pOut) {
|
||||
pOut->print(F("Interrupts not supported on this platform\n"));
|
||||
}
|
||||
// The user requested interrupts, but they aren't supported. Say so.
|
||||
Paused = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PulseSensorPlayground::analogInput(int inputPin, int sensorIndex) {
|
||||
if (sensorIndex != constrain(sensorIndex, 0, SensorCount)) {
|
||||
return; // out of range.
|
||||
}
|
||||
Sensors[sensorIndex].analogInput(inputPin);
|
||||
}
|
||||
|
||||
void PulseSensorPlayground::blinkOnPulse(int blinkPin, int sensorIndex) {
|
||||
if (sensorIndex != constrain(sensorIndex, 0, SensorCount)) {
|
||||
return; // out of range.
|
||||
}
|
||||
Sensors[sensorIndex].blinkOnPulse(blinkPin);
|
||||
}
|
||||
|
||||
void PulseSensorPlayground::fadeOnPulse(int fadePin, int sensorIndex) {
|
||||
if (sensorIndex != constrain(sensorIndex, 0, SensorCount)) {
|
||||
return; // out of range.
|
||||
}
|
||||
Sensors[sensorIndex].fadeOnPulse(fadePin);
|
||||
}
|
||||
|
||||
boolean PulseSensorPlayground::sawNewSample() {
|
||||
/*
|
||||
If using interrupts, this function reads and clears the
|
||||
'saw a sample' flag that is set by the ISR.
|
||||
|
||||
When not using interrupts, this function sees whether it's time
|
||||
to sample and, if so, reads the sample and processes it.
|
||||
|
||||
First, check to see if the sketch has paused the Pulse Sensor sampling
|
||||
*/
|
||||
|
||||
if (UsingInterrupts) {
|
||||
// Disable interrupts to avoid a race with the ISR.
|
||||
DISABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
boolean sawOne = SawNewSample;
|
||||
SawNewSample = false;
|
||||
ENABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
|
||||
return sawOne;
|
||||
}else{
|
||||
if(Paused){
|
||||
SawNewSample = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Time the sample as close as you can when not using interrupts
|
||||
|
||||
unsigned long nowMicros = micros();
|
||||
if ((long) (NextSampleMicros - nowMicros) > 0L) {
|
||||
return false; // not time yet.
|
||||
}
|
||||
NextSampleMicros = nowMicros + MICROS_PER_READ;
|
||||
|
||||
#if PULSE_SENSOR_TIMING_ANALYSIS
|
||||
if (pTiming->recordSampleTime() <= 0) {
|
||||
pTiming->outputStatistics(SerialOutput.getSerial());
|
||||
for (;;); // Hang because we've disturbed the timing.
|
||||
}
|
||||
#endif // PULSE_SENSOR_TIMING_ANALYSIS
|
||||
|
||||
// Act as if the ISR was called.
|
||||
onSampleTime();
|
||||
|
||||
SawNewSample = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PulseSensorPlayground::onSampleTime() {
|
||||
// Typically called from the ISR.
|
||||
|
||||
/*
|
||||
Read the voltage from each PulseSensor.
|
||||
We do this separately from processing the voltages
|
||||
to minimize jitter in acquiring the signal.
|
||||
*/
|
||||
for (int i = 0; i < SensorCount; ++i) {
|
||||
Sensors[i].readNextSample();
|
||||
}
|
||||
|
||||
// Process those voltages.
|
||||
for (int i = 0; i < SensorCount; ++i) {
|
||||
Sensors[i].processLatestSample();
|
||||
Sensors[i].updateLEDs();
|
||||
}
|
||||
|
||||
// Set the flag that says we've read a sample since the Sketch checked.
|
||||
SawNewSample = true;
|
||||
}
|
||||
|
||||
int PulseSensorPlayground::getLatestSample(int sensorIndex) {
|
||||
if (sensorIndex != constrain(sensorIndex, 0, SensorCount)) {
|
||||
return -1; // out of range.
|
||||
}
|
||||
return Sensors[sensorIndex].getLatestSample();
|
||||
}
|
||||
|
||||
int PulseSensorPlayground::getBeatsPerMinute(int sensorIndex) {
|
||||
if (sensorIndex != constrain(sensorIndex, 0, SensorCount)) {
|
||||
return -1; // out of range.
|
||||
}
|
||||
return Sensors[sensorIndex].getBeatsPerMinute();
|
||||
}
|
||||
|
||||
int PulseSensorPlayground::getInterBeatIntervalMs(int sensorIndex) {
|
||||
if (sensorIndex != constrain(sensorIndex, 0, SensorCount)) {
|
||||
return -1; // out of range.
|
||||
}
|
||||
return Sensors[sensorIndex].getInterBeatIntervalMs();
|
||||
}
|
||||
|
||||
boolean PulseSensorPlayground::sawStartOfBeat(int sensorIndex) {
|
||||
if (sensorIndex != constrain(sensorIndex, 0, SensorCount)) {
|
||||
return false; // out of range.
|
||||
}
|
||||
return Sensors[sensorIndex].sawStartOfBeat();
|
||||
}
|
||||
|
||||
boolean PulseSensorPlayground::isInsideBeat(int sensorIndex) {
|
||||
if (sensorIndex != constrain(sensorIndex, 0, SensorCount)) {
|
||||
return false; // out of range.
|
||||
}
|
||||
return Sensors[sensorIndex].isInsideBeat();
|
||||
}
|
||||
|
||||
void PulseSensorPlayground::setSerial(Stream &output) {
|
||||
SerialOutput.setSerial(output);
|
||||
}
|
||||
|
||||
void PulseSensorPlayground::setOutputType(byte outputType) {
|
||||
SerialOutput.setOutputType(outputType);
|
||||
}
|
||||
|
||||
void PulseSensorPlayground::setThreshold(int threshold, int sensorIndex) {
|
||||
if (sensorIndex != constrain(sensorIndex, 0, SensorCount)) {
|
||||
return; // out of range.
|
||||
}
|
||||
Sensors[sensorIndex].setThreshold(threshold);
|
||||
}
|
||||
|
||||
void PulseSensorPlayground::outputSample() {
|
||||
SerialOutput.outputSample(Sensors, SensorCount);
|
||||
}
|
||||
|
||||
void PulseSensorPlayground::outputBeat(int sensorIndex) {
|
||||
SerialOutput.outputBeat(Sensors, SensorCount, sensorIndex);
|
||||
}
|
||||
|
||||
void PulseSensorPlayground::outputToSerial(char s, int d) {
|
||||
SerialOutput.outputToSerial(s,d);
|
||||
}
|
||||
|
||||
int PulseSensorPlayground::getPulseAmplitude(int sensorIndex) {
|
||||
if (sensorIndex != constrain(sensorIndex, 0, SensorCount)) {
|
||||
return -1; // out of range.
|
||||
}
|
||||
return Sensors[sensorIndex].getPulseAmplitude();
|
||||
}
|
||||
|
||||
unsigned long PulseSensorPlayground::getLastBeatTime(int sensorIndex) {
|
||||
if (sensorIndex != constrain(sensorIndex, 0, SensorCount)) {
|
||||
return -1; // out of range.
|
||||
}
|
||||
return Sensors[sensorIndex].getLastBeatTime();
|
||||
}
|
||||
|
||||
boolean PulseSensorPlayground::isPaused() {
|
||||
return Paused;
|
||||
}
|
||||
|
||||
boolean PulseSensorPlayground::pause() {
|
||||
if (UsingInterrupts) {
|
||||
if (!PulseSensorPlaygroundDisableInterrupt()) {
|
||||
Stream *pOut = SerialOutput.getSerial();
|
||||
if (pOut) {
|
||||
pOut->print(F("Could not pause Pulse Sensor\n"));
|
||||
}
|
||||
return false;
|
||||
}else{
|
||||
// DOING THIS HERE BECAUSE IT COULD GET CHOMPED IF WE DO IN resume BELOW
|
||||
for(int i=0; i<SensorCount; i++){
|
||||
Sensors[i].resetVariables();
|
||||
}
|
||||
Paused = true;
|
||||
return true;
|
||||
}
|
||||
}else{
|
||||
// do something here?
|
||||
Paused = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
boolean PulseSensorPlayground::resume() {
|
||||
if (UsingInterrupts) {
|
||||
if (!PulseSensorPlaygroundEnableInterrupt()) {
|
||||
Stream *pOut = SerialOutput.getSerial();
|
||||
if (pOut) {
|
||||
pOut->print(F("Could not resume Pulse Sensor\n"));
|
||||
}
|
||||
return false;
|
||||
}else{
|
||||
Paused = false;
|
||||
return true;
|
||||
}
|
||||
}else{
|
||||
// do something here?
|
||||
Paused = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#if PULSE_SENSOR_MEMORY_USAGE
|
||||
void PulseSensorPlayground::printMemoryUsage() {
|
||||
char stack = 1;
|
||||
extern char *__data_start;
|
||||
extern char *__data_end;
|
||||
extern char *__bss_start;
|
||||
extern char *__bss_end;
|
||||
extern char *__heap_start;
|
||||
extern char *__heap_end;
|
||||
|
||||
int data_size = (int)&__data_end - (int)&__data_start;
|
||||
int bss_size = (int)&__bss_end - (int)&__data_end;
|
||||
int heap_end = (int)&stack - (int)&__malloc_margin;
|
||||
int heap_size = heap_end - (int)&__bss_end;
|
||||
int stack_size = RAMEND - (int)&stack + 1;
|
||||
int available = (RAMEND - (int)&__data_start + 1);
|
||||
available -= data_size + bss_size + heap_size + stack_size;
|
||||
|
||||
Stream *pOut = SerialOutput.getSerial();
|
||||
if (pOut) {
|
||||
pOut->print(F("data "));
|
||||
pOut->println(data_size);
|
||||
pOut->print(F("bss "));
|
||||
pOut->println(bss_size);
|
||||
pOut->print(F("heap "));
|
||||
pOut->println(heap_size);
|
||||
pOut->print(F("stack "));
|
||||
pOut->println(stack_size);
|
||||
pOut->print(F("total "));
|
||||
pOut->println(data_size + bss_size + heap_size + stack_size);
|
||||
}
|
||||
}
|
||||
#endif // PULSE_SENSOR_MEMORY_USAGE
|
||||
@@ -0,0 +1,441 @@
|
||||
/*
|
||||
A central Playground object to manage a set of PulseSensors.
|
||||
See https://www.pulsesensor.com to get started.
|
||||
|
||||
Copyright World Famous Electronics LLC - see LICENSE
|
||||
Contributors:
|
||||
Joel Murphy, https://pulsesensor.com
|
||||
Yury Gitman, https://pulsesensor.com
|
||||
Bradford Needham, @bneedhamia, https://bluepapertech.com
|
||||
|
||||
Licensed under the MIT License, a copy of which
|
||||
should have been included with this software.
|
||||
|
||||
This software is not intended for medical use.
|
||||
*/
|
||||
|
||||
/*
|
||||
NOTE: Every Sketch that uses the PulseSensor Playground
|
||||
must define the variable USE_ARDUINO_INTERRUPTS *before* including
|
||||
PulseSensorPlayground.h. If you don't, you will get a compiler error
|
||||
about "undefined reference to `PulseSensorPlayground::UsingInterrupts".
|
||||
|
||||
In particular, if your Sketch wants the Playground to use interrupts
|
||||
to read and process PulseSensor data, your Sketch must contain the
|
||||
following two lines, in order:
|
||||
#define USE_ARDUINO_INTERRUPTS true
|
||||
#include <PulseSensorPlayground.h>
|
||||
|
||||
If, instead, your Sketch does not use interrupts to read PulseSensor
|
||||
data, your Sketch must instead contain the
|
||||
following two lines, in order:
|
||||
#define USE_ARDUINO_INTERRUPTS false
|
||||
#include <PulseSensorPlayground.h>
|
||||
|
||||
See utility/interrupts.h for details.
|
||||
|
||||
Internal, developer note: in the Playground code, don't use
|
||||
USE_ARDUINO_INTERRUPTS as a variable; instead, refer to
|
||||
PulseSensorPlayground::UsingInterrupts, which is a static variable
|
||||
that reflects what the Sketch defined USE_ARDUINO_INTERRUPTS to.
|
||||
Because USE_ARDUINO_INTERRUPTS is defined *only* in the user's Sketch,
|
||||
it doesn't exist when the various Playground modules are compiled.
|
||||
|
||||
See further notes in interrupts.h
|
||||
*/
|
||||
|
||||
|
||||
#ifndef PULSE_SENSOR_PLAYGROUND_H
|
||||
#define PULSE_SENSOR_PLAYGROUND_H
|
||||
|
||||
/*
|
||||
If you wish to perform timing statistics on your non-interrupt Sketch:
|
||||
|
||||
Uncomment the line below: #define PULSE_SENSOR_TIMING_ANALYSIS true
|
||||
Compile and download your Sketch.
|
||||
Start the Arduino IDE Serial Monitor.
|
||||
Wait about 30 seconds. The Sketch should then print 3 numbers and hang.
|
||||
The three numbers are:
|
||||
Minimum variation (microseconds) from the 2 millisecond sample time.
|
||||
Average variation in that number.
|
||||
Maximum variation in that number.
|
||||
For example and output of -4 0 18 says that samples were made between
|
||||
4 microseconds short of 2 milliseconds, and 18 microseconds longer,
|
||||
with an average sample time right at 2 milliseconds (0 microseconds offset).
|
||||
|
||||
If the average number is larger than, say, 50 microseconds, your Sketch
|
||||
is taking too much time per loop(), causing inaccuracies in the
|
||||
measured signal, heart rate, and inter-beat interval.
|
||||
|
||||
You should aim for an average offset of under 50 microseconds.
|
||||
|
||||
NOTES:
|
||||
|
||||
1) This is an approximate measure, because interrupts can occur that
|
||||
the timing statistics cannot measure.
|
||||
|
||||
2) These statistics compile only for non-interrupt Sketches. If your
|
||||
Sketch uses Interrupts to sample the PulseSensor signal, enabling
|
||||
this timing analysis will have no effect and will print nothing.
|
||||
|
||||
3) Because timing analysis results are printed on Serial, you cannot
|
||||
use the Arduino IDE Serial Plotter or the Processing Visualizer to
|
||||
examine output when timing analysis is enabled.
|
||||
|
||||
4) If the average is a negative number, your assumed Arduino clock
|
||||
speed may be incorrect. For example, if you compiled for an 8MHz clock
|
||||
and your Arduino actually runs at 16MHz, you would likely see an
|
||||
average offset of something like -1000.
|
||||
|
||||
*/
|
||||
#define PULSE_SENSOR_TIMING_ANALYSIS false
|
||||
//#define PULSE_SENSOR_TIMING_ANALYSIS true
|
||||
|
||||
/*
|
||||
If you wish to print the amount of memory used before your Sketch
|
||||
starts:
|
||||
|
||||
Uncomment the line below: #define PULSE_SENSOR_MEMORY_USAGE true
|
||||
Compile and download your Sketch.
|
||||
Start the Arduino IDE Serial Monitor
|
||||
Your Sketch will start normally, then print memory usage, then hang.
|
||||
|
||||
The memory usage consists of five numbers:
|
||||
data = bytes of global, uninitialized data storage (int x;)
|
||||
bss = bytes of global, initialized data storage (int x = 5;)
|
||||
heap = bytes of dynamically allocated memory (new Stream;)
|
||||
stack = bytes of local variables (those defined within a function)
|
||||
total = the total of data, bss, heap, and stack memory used.
|
||||
|
||||
The amount of flash memory used is printed by the Arduino IDE
|
||||
when compilation finishes, with a printout such as:
|
||||
Sketch uses 5036 bytes (15%) of program storage space.
|
||||
|
||||
NOTE: you must call pulse.setSerial(Serial) in your Sketch's setup().
|
||||
*/
|
||||
#define PULSE_SENSOR_MEMORY_USAGE false
|
||||
// #define PULSE_SENSOR_MEMORY_USAGE true
|
||||
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "utility/PulseSensor.h"
|
||||
#include "utility/PulseSensorSerialOutput.h"
|
||||
#include "utility/PulseSensorTimingStatistics.h"
|
||||
|
||||
class PulseSensorPlayground {
|
||||
public:
|
||||
/*
|
||||
The number of microseconds per sample of data from the PulseSensor.
|
||||
1 millisecond is 1,000 microseconds.
|
||||
|
||||
Refer to this value as PulseSensorPlayground::MICROS_PER_READ
|
||||
*/
|
||||
static const unsigned long MICROS_PER_READ = (2 * 1000L); // usecs per sample.
|
||||
|
||||
//---------- PulseSensor Manager functions
|
||||
|
||||
/*
|
||||
Construct the one PulseSensor Playground manager,
|
||||
that manages the given number of PulseSensors.
|
||||
Your Sketch should declare either PulseSensorPlayground() for one sensor
|
||||
or PulseSensorPlayground(n) for n PulseSensors.
|
||||
|
||||
For example:
|
||||
PulseSensorPlayground pulse();
|
||||
or
|
||||
PulseSensorPlayground pulse(2); // for 2 PulseSensors.
|
||||
*/
|
||||
PulseSensorPlayground(int numberOfSensors = 1);
|
||||
|
||||
/*
|
||||
Start reading and processing data from the PulseSensors.
|
||||
|
||||
Your Sketch should make all necessary PulseSensor configuration calls
|
||||
before calling begin().
|
||||
|
||||
If the Sketch defined USE_ARDUINO_INTERRUPTS as true, this function
|
||||
sets up and turns on interrupts for the PulseSensor.
|
||||
|
||||
If instead the Sketch defined USE_ARDUINO_INTERRUPTS as false,
|
||||
it initializes what's necessary for the Sketch to process
|
||||
PulsSensor signals. See sawNewSample(), below.
|
||||
|
||||
Returns true if successful, false if unsuccessful.
|
||||
Returns false if PulseSensorPlayground doesn't yet support
|
||||
interrupts on this Arduino platform and the user's Sketch
|
||||
did a #define USE_ARDUINO_INTERRUPTS true.
|
||||
|
||||
If begin() returns false, you can either use a different
|
||||
type of Arduino platform, or you can change your Sketch's
|
||||
definition of USE_ARDUINO_INTERRUPTS to false:
|
||||
#define USE_ARDUINO_INTERRUPTS false
|
||||
*/
|
||||
boolean begin();
|
||||
|
||||
/*
|
||||
Returns true if a new sample has been read from each PulseSensor.
|
||||
You'll likely want to add this call to your Sketch's loop()
|
||||
only if you either 1) want to do something with each sample of the
|
||||
PulseSensor signals, or 2) your Sketch doesn't use interrupts
|
||||
to read from the PulseSensors.
|
||||
|
||||
NOTE: If your Sketch defined USE_ARDUINO_INTERRUPTS as false,
|
||||
you must call pulse.sawNewSample() frequently (at least
|
||||
once every 2 milliseconds) to assure that PulseSensor signals
|
||||
are read accurately.
|
||||
A typical loop() that doesn't use interrupts will contain:
|
||||
if (pulse.sawNewSample()) {
|
||||
int latest = pulse.getLatestSample();
|
||||
...do whatever you want with the sample read from the PulseSensor.
|
||||
}
|
||||
*/
|
||||
boolean sawNewSample();
|
||||
|
||||
//---------- Per-PulseSensor functions
|
||||
|
||||
/*
|
||||
By default, the Playground assumes the PulseSensor is connected to A0.
|
||||
If your PulseSensor is connected to a different analog input pin,
|
||||
call pulse.analogInput(pin) or pulse.analogInput(pin, sensorIndex).
|
||||
|
||||
inputPin = the analog input this PulseSensor is connected to.
|
||||
sensorIndex = optional, index (0..numberOfSensors - 1)
|
||||
of the PulseSensor to configure.
|
||||
*/
|
||||
void analogInput(int inputPin, int sensorIndex = 0);
|
||||
|
||||
/*
|
||||
By default, the Playground doesn't blink LEDs automatically.
|
||||
|
||||
If you wish the Playground to automatically blink an LED
|
||||
during each detected pulse,
|
||||
call pulse.blinkOnPulse(blinkPin) or
|
||||
pulse.blinkOnPulse(blinkPin, sensorIndex).
|
||||
|
||||
blinkPin = the pin to blink on each pulse, which you've connected
|
||||
to an LED and 220 ohm resistor, or the built in LED pin
|
||||
on your Arduino (for example, pin 13 on Arduino Uno).
|
||||
sensorIndex = optional, index (0..numberOfSensors - 1)
|
||||
of the PulseSensor to configure.
|
||||
*/
|
||||
void blinkOnPulse(int blinkPin, int sensorIndex = 0);
|
||||
|
||||
/*
|
||||
By default, the Playground doesn't blink LEDs automatically.
|
||||
|
||||
If you wish the Playground to automatically blink a fading LED
|
||||
during each detected pulse,
|
||||
call fadeOnPulse(fadePin) or fadeOnPulse(fadePin, sensorIndex).
|
||||
|
||||
NOTE: the fade pin must be a PWM (Pulse-Width Modulation) pin.
|
||||
|
||||
fadePin = the PWM pin to blink and fade on each pulse,
|
||||
which is connected to an LED and a current-limit resistor.
|
||||
sensorIndex = optional, index (0..numberOfSensors - 1)
|
||||
of the PulseSensor to configure.
|
||||
*/
|
||||
void fadeOnPulse(int fadePin, int sensorIndex = 0);
|
||||
|
||||
/*
|
||||
(Internal to library - do not call from a Sketch)
|
||||
Perform all the processing necessary when it's time to
|
||||
read from all the PulseSensors and process their signals.
|
||||
*/
|
||||
void onSampleTime();
|
||||
|
||||
/*
|
||||
Returns the most recently read analog value from the given PulseSensor
|
||||
(range: 0..1023).
|
||||
|
||||
sensorIndex = optional, index (0..numberOfSensors - 1)
|
||||
of the PulseSensor of interest.
|
||||
*/
|
||||
int getLatestSample(int sensorIndex = 0);
|
||||
|
||||
/*
|
||||
Returns the latest beats-per-minute measure for the given PulseSensor.
|
||||
|
||||
The internal beats-per-minute measure is updated per-PulseSensor,
|
||||
when a beat is detected from that PulseSensor.
|
||||
|
||||
sensorIndex = optional, index (0..numberOfSensors - 1)
|
||||
of the PulseSensor of interest.
|
||||
*/
|
||||
int getBeatsPerMinute(int sensorIndex = 0);
|
||||
|
||||
/*
|
||||
Returns the latest IBI (inter-beat interval, in milliseconds) measure
|
||||
for the given PulseSensor.
|
||||
|
||||
The internal IBI measure is updated per-PulseSensor,
|
||||
when a beat is detected from that PulseSensor.
|
||||
|
||||
sensorIndex = optional, index (0..numberOfSensors - 1)
|
||||
of the PulseSensor of interest.
|
||||
*/
|
||||
int getInterBeatIntervalMs(int sensorIndex = 0);
|
||||
|
||||
/*
|
||||
Returns true if a new heartbeat (pulse) has been detected
|
||||
from the given PulseSensor since the last call to sawStartOfBeat()
|
||||
on this PulseSensor.
|
||||
|
||||
Typical use in loop():
|
||||
if (pulse.sawStartOfBeat()) {
|
||||
...do what you want to do per-heartbeat.
|
||||
}
|
||||
|
||||
sensorIndex = optional, index (0..numberOfSensors - 1)
|
||||
of the PulseSensor of interest.
|
||||
*/
|
||||
boolean sawStartOfBeat(int sensorIndex = 0);
|
||||
|
||||
/*
|
||||
Returns true if the given PulseSensor signal is currently
|
||||
inside a heartbeat. That is, returns true if the signal is above
|
||||
the automatically-set threshold of a beat, false otherwise.
|
||||
|
||||
Typical use in loop():
|
||||
if (pulse.isInsideBeat()) {
|
||||
...do what you want while in the beat.
|
||||
} else {
|
||||
...do what you want while between beats.
|
||||
}
|
||||
|
||||
sensorIndex = optional, index (0..numberOfSensors - 1)
|
||||
of the PulseSensor of interest.
|
||||
*/
|
||||
boolean isInsideBeat(int sensorIndex = 0);
|
||||
|
||||
//---------- Serial Output functions
|
||||
|
||||
/*
|
||||
By default, the Playround doesn't output serial data automatically.
|
||||
|
||||
If you want to output serial pulse data, call pulse.setSerial(Serial),
|
||||
pulse.setSerial(Serial1), or whatever Serial stream you like.
|
||||
|
||||
output = the Stream to write data to. Serial, Serial1, Serial2,
|
||||
etc., and a SoftwareSerial are valid parameters to pass.
|
||||
*/
|
||||
void setSerial(Stream &output);
|
||||
|
||||
/*
|
||||
By default, Playground output is in SERIAL_PLOTTER format.
|
||||
|
||||
If you want output in a different format, call this function once
|
||||
sometime before calling pulse.begin().
|
||||
|
||||
Remember to call pulse.setSerial() if you want serial output.
|
||||
|
||||
outputType = SERIAL_PLOTTER to output to the Arduino Serial Plotter,
|
||||
PROCESSSING_VISUALIZER to output to the Processing Sketch
|
||||
that draws the PulseSensor output.
|
||||
*/
|
||||
void setOutputType(byte outputType);
|
||||
|
||||
/*
|
||||
By default, the threshold value is 530.
|
||||
threshold is used to find the heartbeat
|
||||
adjust this value up in the setup function to avoid noise.
|
||||
*/
|
||||
void setThreshold(int threshold, int sensorIndex = 0);
|
||||
|
||||
/*
|
||||
Output the current signal information for each PulseSensor,
|
||||
in the previously-set outputType.
|
||||
|
||||
If your Sketch wants to plot samples, it should call this function
|
||||
every so often.
|
||||
*/
|
||||
void outputSample();
|
||||
|
||||
/*
|
||||
Serial print data with prefix.
|
||||
Used exclusively with the Pulse Sensor Processing sketch.
|
||||
*/
|
||||
void outputToSerial(char symbol, int data);
|
||||
|
||||
/*
|
||||
Returns the current amplitude of the pulse waveform.
|
||||
*/
|
||||
int getPulseAmplitude(int sensorIndex = 0);
|
||||
|
||||
/*
|
||||
Returns the sample number when the last beat was found. 2mS resolution.
|
||||
*/
|
||||
unsigned long getLastBeatTime(int sensorIndex = 0);
|
||||
|
||||
/*
|
||||
Output the current per-beat information for each PulseSensor,
|
||||
in the previously-set outputType.
|
||||
|
||||
If your Sketch wants to plot beat information, it should call this
|
||||
function every time a beat is detected.
|
||||
|
||||
Typical use:
|
||||
if (pulse.sawStartOfBeat()) {
|
||||
pulse.outputBeat();
|
||||
}
|
||||
*/
|
||||
void outputBeat(int sensorIndex = 0);
|
||||
|
||||
// check to see if the library is sampling (on interrupt OR in software)
|
||||
boolean isPaused();
|
||||
|
||||
// option to pause Pulse Sensor sampling in order to do other stuff
|
||||
// this function will only tell the timer to stop interrupting
|
||||
// does not return PWM or other fuctionality to effected pins
|
||||
boolean pause();
|
||||
|
||||
// restart sampling the Pulse Sensor after a pause
|
||||
boolean resume();
|
||||
|
||||
|
||||
// (internal to the library) "this" pointer for the ISR.
|
||||
static PulseSensorPlayground *OurThis;
|
||||
|
||||
private:
|
||||
|
||||
/*
|
||||
Configure and enable interrupts to read samples.
|
||||
Call only if PulseSensorPlayground::UsingInterrupts is true.
|
||||
|
||||
This function is defined (vs. declared here) in interrupts.h
|
||||
*/
|
||||
// void setupInterrupt();
|
||||
// boolean disableInterrupt();
|
||||
// boolean enableInterrupt();
|
||||
|
||||
#if PULSE_SENSOR_MEMORY_USAGE
|
||||
/*
|
||||
Print our RAM usage. See PULSE_SENSOR_MEMORY_USAGE
|
||||
*/
|
||||
void printMemoryUsage();
|
||||
#endif // PULSE_SENSOR_MEMORY_USAGE
|
||||
|
||||
/*
|
||||
If true, the Sketch wants to use interrupts to read the PulseSensor(s).
|
||||
|
||||
This variable is defined (vs. declared here) in interrupts.h
|
||||
*/
|
||||
static boolean UsingInterrupts;
|
||||
boolean Paused;
|
||||
byte SensorCount; // number of PulseSensors in Sensors[].
|
||||
PulseSensor *Sensors; // use Sensors[idx] to access a sensor.
|
||||
volatile unsigned long NextSampleMicros; // Desired time to sample next.
|
||||
volatile boolean SawNewSample; // "A sample has arrived from the ISR"
|
||||
PulseSensorSerialOutput SerialOutput; // Serial Output manager.
|
||||
#if PULSE_SENSOR_TIMING_ANALYSIS // Don't use ram and flash we don't need.
|
||||
PulseSensorTimingStatistics *pTiming;
|
||||
#endif // PULSE_SENSOR_TIMING_ANALYSIS
|
||||
};
|
||||
|
||||
/*
|
||||
We include interrupts.h here instead of above
|
||||
because it depends on variables and functions we declare (vs. define)
|
||||
in PulseSensorPlayground.h.
|
||||
*/
|
||||
#include "utility/Interrupts.h"
|
||||
|
||||
#endif // PULSE_SENSOR_PLAYGROUND_H
|
||||
@@ -0,0 +1,390 @@
|
||||
/*
|
||||
Interrupt handling helper functions for PulseSensors.
|
||||
See https://www.pulsesensor.com to get started.
|
||||
|
||||
Copyright World Famous Electronics LLC - see LICENSE
|
||||
Contributors:
|
||||
Joel Murphy, https://pulsesensor.com
|
||||
Yury Gitman, https://pulsesensor.com
|
||||
Bradford Needham, @bneedhamia, https://bluepapertech.com
|
||||
|
||||
Licensed under the MIT License, a copy of which
|
||||
should have been included with this software.
|
||||
|
||||
This software is not intended for medical use.
|
||||
*/
|
||||
|
||||
/*
|
||||
Any Sketch using the Playground must do one of two things:
|
||||
1) #define USE_ARDUINO_INTERRUPTS true - if using interrupts;
|
||||
2) #define USE_ARDUINO_INTERRUPTS false - if not using interrupts.
|
||||
|
||||
Only the Sketch must define USE_ARDUINO_INTERRUPTS.
|
||||
If the Sketch doesn't define USE_ARDUINO_INTERRUPTS, or if some other file
|
||||
defines it as well, a link error will result.
|
||||
|
||||
See notes in PulseSensorPlayground.h
|
||||
|
||||
The code below is rather convoluted, with nested #if's.
|
||||
This structure is used to achieve two goals:
|
||||
1) Minimize the complexity the user has to deal with to use or
|
||||
not use interrupts to sample the PulseSensor data;
|
||||
2) Create an ISR() only if the Sketch uses interrupts. Defining an
|
||||
ISR(), even if not used, may interfere with other libraries' use
|
||||
of interrupts.
|
||||
|
||||
The nesting goes something like this:
|
||||
if the Sketch is being compiled... #if defined(USE_ARDUINO_INTERRUPTS)
|
||||
if the user wants to use interrupts... #if USE_ARDUINO_INTERRUPTS
|
||||
#if's for the various Arduino platforms... #if defined(__AVR_ATmega328P__)...
|
||||
|
||||
RULES of the constant USE_ARDUINO_INTERRUPTS:
|
||||
1) This file, interrupts.h, should be the only file that uses USE_ARDUINO_INTERRUPTS
|
||||
(although PulseSensorPlayground's comments talk about it to the user).
|
||||
If other code in the library wants to know whether interrupts are being used,
|
||||
that code should use PulseSensorPlayground::UsingInterrupts, which is true
|
||||
if the Sketch wants to use interrupts.
|
||||
1) Always use #if USE_ARDUINO_INTERRUPTS inside an #if defined(USE_ARDUINO_INTERRUPTS).
|
||||
If you don't first test the #if defined(...), a compile error will occur
|
||||
when compiling the library modules.
|
||||
2) USE_ARDUINO_INTERRUPTS is defined only when this file is being included
|
||||
by the user's Sketch; not when the rest of the library is compiled.
|
||||
3) USE_ARDUINO_INTERRUPTS is true if the user wants to use interrupts;
|
||||
it's false if they don't.
|
||||
*/
|
||||
|
||||
#ifndef PULSE_SENSOR_INTERRUPTS_H
|
||||
#define PULSE_SENSOR_INTERRUPTS_H
|
||||
|
||||
|
||||
//TODO: if noInterrupts() and interrupts() are defined for Arduino 101,
|
||||
// Use them throughout and eliminate these DISABLE/ENAGLE macros.
|
||||
//
|
||||
// Macros to link to interrupt disable/enable only if they exist
|
||||
// The name is long to avoid collisions with Sketch and Library symbols.
|
||||
#if defined(__arc__)||(ARDUINO_SAMD_MKR1000)||(ARDUINO_SAMD_MKRZERO)||(ARDUINO_SAMD_ZERO)\
|
||||
||(ARDUINO_ARCH_SAMD)||(ARDUINO_ARCH_STM32)||(ARDUINO_STM32_STAR_OTTO)||(ARDUINO_ARCH_NRF5)\
|
||||
||(ARDUINO_ARCH_NRF52)
|
||||
|
||||
#define DISABLE_PULSE_SENSOR_INTERRUPTS
|
||||
#define ENABLE_PULSE_SENSOR_INTERRUPTS
|
||||
#else
|
||||
#define DISABLE_PULSE_SENSOR_INTERRUPTS cli()
|
||||
#define ENABLE_PULSE_SENSOR_INTERRUPTS sei()
|
||||
#endif
|
||||
|
||||
|
||||
// SAVED FOR FUTURE SUPPORT OF TEENSY INTERRUPTS
|
||||
#if defined(__MK66FX1M0__)||(__MK64FX512__)||(__MK20DX256__)||(__MK20DX128__)
|
||||
// #include <FlexiTimer2.h>
|
||||
#endif
|
||||
|
||||
// FOR BOARDS USING THE ESP32 WIFI MODULE
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
// can't use analogWrite yet...
|
||||
#define NO_ANALOG_WRITE = 1
|
||||
#endif
|
||||
/*
|
||||
(internal to the library)
|
||||
Sets up the sample timer interrupt for this Arduino Platform.
|
||||
|
||||
Returns true if successful, false if we don't yet support
|
||||
the timer interrupt on this Arduino.
|
||||
|
||||
NOTE: This is the declaration (vs. definition) of this function.
|
||||
See the definition (vs. declaration) of this function, below.
|
||||
*/
|
||||
boolean PulseSensorPlaygroundSetupInterrupt();
|
||||
boolean PulseSensorPlaygroundDisableInterrupt();
|
||||
boolean PulseSensorPlaygroundEnableInterrupt();
|
||||
|
||||
#if defined(USE_ARDUINO_INTERRUPTS) // that is, if the Sketch is including us...
|
||||
|
||||
/*
|
||||
(internal to the library) True if the Sketch uses interrupts to
|
||||
sample
|
||||
We need to define USE_PS_INTERRUPTS once per Sketch, whether or not
|
||||
the Sketch uses interrupts.
|
||||
Not doing this or doing it for every file that includes interrupts.h
|
||||
would cause a link error.
|
||||
|
||||
To refer to this variable, use "PulseSensorPlayground::UsingInterrupts".
|
||||
|
||||
See PulseSensorPlayground.h
|
||||
*/
|
||||
boolean PulseSensorPlayground::UsingInterrupts = USE_ARDUINO_INTERRUPTS;
|
||||
|
||||
boolean PulseSensorPlaygroundSetupInterrupt() {
|
||||
|
||||
#if !USE_ARDUINO_INTERRUPTS
|
||||
/*
|
||||
The Sketch doesn't want interrupts,
|
||||
so we won't waste Flash space and create complexity
|
||||
by adding interrupt-setup code.
|
||||
*/
|
||||
return true;
|
||||
|
||||
#else
|
||||
// This code sets up the sample timer interrupt
|
||||
// based on the type of Arduino platform.
|
||||
|
||||
/*
|
||||
NOTE: when you change the #if's in this function,
|
||||
be sure to add similar #if's (if necessary) to the ISR() defined
|
||||
below.
|
||||
*/
|
||||
|
||||
#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) || defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega16U4__)
|
||||
|
||||
// check to see if the Servo library is in use
|
||||
#if defined Servo_h
|
||||
// #error "Servos!! Beware" // break compiler for testing
|
||||
// Initializes Timer2 to throw an interrupt every 2mS
|
||||
// Interferes with PWM on pins 3 and 11
|
||||
TCCR2A = 0x02; // Disable PWM and go into CTC mode
|
||||
TCCR2B = 0x05; // don't force compare, 128 prescaler
|
||||
#if F_CPU == 16000000L // if using 16MHz crystal
|
||||
OCR2A = 0XF9; // set count to 249 for 2mS interrupt
|
||||
#elif F_CPU == 8000000L // if using 8MHz crystal
|
||||
OCR2A = 0X7C; // set count to 124 for 2mS interrupt
|
||||
#endif
|
||||
TIMSK2 = 0x02; // Enable OCR2A match interrupt DISABLE BY SETTING TO 0x00
|
||||
ENABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
// #define _useTimer2
|
||||
return true;
|
||||
#else
|
||||
// Initializes Timer1 to throw an interrupt every 2mS.
|
||||
// Interferes with PWM on pins 9 and 10
|
||||
TCCR1A = 0x00; // Disable PWM and go into CTC mode
|
||||
TCCR1C = 0x00; // don't force compare
|
||||
#if F_CPU == 16000000L // if using 16MHz crystal
|
||||
TCCR1B = 0x0C; // prescaler 256
|
||||
OCR1A = 0x007C; // count to 124 for 2mS interrupt
|
||||
#elif F_CPU == 8000000L // if using 8MHz crystal
|
||||
TCCR1B = 0x0B; // prescaler = 64
|
||||
OCR1A = 0x00F9; // count to 249 for 2mS interrupt
|
||||
#endif
|
||||
TIMSK1 = 0x02; // Enable OCR1A match interrupt DISABLE BY SETTING TO 0x00
|
||||
ENABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
return true;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
|
||||
|
||||
// check to see if the Servo library is in use
|
||||
#if defined Servo_h
|
||||
// #error "Servos!! Beware" // break compiler for testing
|
||||
// Initializes Timer1 to throw an interrupt every 2mS.
|
||||
// Interferes with PWM on pins 9 and 10
|
||||
TCCR1A = 0x00; // Disable PWM and go into CTC mode
|
||||
TCCR1C = 0x00; // don't force compare
|
||||
#if F_CPU == 16000000L // if using 16MHz crystal
|
||||
TCCR1B = 0x0C; // prescaler 256
|
||||
OCR1A = 0x007C; // count to 124 for 2mS interrupt
|
||||
#elif F_CPU == 8000000L // if using 8MHz crystal
|
||||
TCCR1B = 0x0B; // prescaler = 64
|
||||
OCR1A = 0x00F9; // count to 249 for 2mS interrupt
|
||||
#endif
|
||||
TIMSK1 = 0x02; // Enable OCR1A match interrupt
|
||||
ENABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
return true;
|
||||
|
||||
#else
|
||||
// Initializes Timer2 to throw an interrupt every 2mS
|
||||
// Interferes with PWM on pins 3 and 11
|
||||
TCCR2A = 0x02; // Disable PWM and go into CTC mode
|
||||
TCCR2B = 0x05; // don't force compare, 128 prescaler
|
||||
#if F_CPU == 16000000L // if using 16MHz crystal
|
||||
OCR2A = 0XF9; // set count to 249 for 2mS interrupt
|
||||
#elif F_CPU == 8000000L // if using 8MHz crystal
|
||||
OCR2A = 0X7C; // set count to 124 for 2mS interrupt
|
||||
#endif
|
||||
TIMSK2 = 0x02; // Enable OCR2A match interrupt
|
||||
ENABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
// #define _useTimer2
|
||||
return true;
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__AVR_ATtiny85__)
|
||||
GTCCR = 0x00; // Disable PWM, don't connect pins to events
|
||||
OCR1A = 0x7D; // Set top of count to 125. Timer match throws the interrupt
|
||||
OCR1C = 0x7D; // Set top of the count to 125. Timer match resets the counter
|
||||
#if F_CPU == 16000000L
|
||||
TCCR1 = 0x89; // Clear Timer on Compare, Set Prescaler to 256
|
||||
#elif F_CPU == 8000000L
|
||||
TCCR1 = 0x88; // Clear Timer on Compare, Set Prescaler to 128
|
||||
#elif F_CPU == 1000000L
|
||||
TCCR1 = 0x85 // Clear Timer on Compare, Set Prescaler to 16
|
||||
#endif
|
||||
bitSet(TIMSK,6); // Enable interrupt on match between TCNT1 and OCR1A
|
||||
ENABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
return true;
|
||||
|
||||
#else
|
||||
return false; // unknown or unsupported platform.
|
||||
#endif
|
||||
|
||||
#endif // USE_ARDUINO_INTERRUPTS
|
||||
}
|
||||
|
||||
boolean PulseSensorPlaygroundDisableInterrupt(){
|
||||
#if USE_ARDUINO_INTERRUPTS
|
||||
#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) || defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega16U4__)
|
||||
// check to see if the Servo library is in use
|
||||
#if defined Servo_h
|
||||
DISABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
TIMSK2 = 0x00; // Disable OCR2A match interrupt
|
||||
ENABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
return true;
|
||||
#else
|
||||
DISABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
TIMSK1 = 0x00; // Disable OCR1A match interrupt
|
||||
ENABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
return true;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
|
||||
// check to see if the Servo library is in use
|
||||
#if defined Servo_h
|
||||
DISABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
TIMSK1 = 0x00; // Disable OCR1A match interrupt
|
||||
ENABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
return true;
|
||||
#else
|
||||
DISABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
TIMSK2 = 0x00; // Disable OCR2A match interrupt
|
||||
ENABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
return true;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__AVR_ATtiny85__)
|
||||
DISABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
bitClear(TIMSK,6); // Disable interrupt on match between TCNT1 and OCR1A
|
||||
ENABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
return true;
|
||||
#endif
|
||||
|
||||
// #else
|
||||
return false; // unknown or unsupported platform.
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
boolean PulseSensorPlaygroundEnableInterrupt(){
|
||||
#if USE_ARDUINO_INTERRUPTS
|
||||
#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) || defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega16U4__) // || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
|
||||
// check to see if the Servo library is in use
|
||||
#if defined Servo_h
|
||||
DISABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
TIMSK2 = 0x02; // Enable OCR2A match interrupt
|
||||
ENABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
return true;
|
||||
#else
|
||||
DISABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
TIMSK1 = 0x02; // Enable OCR1A match interrupt
|
||||
ENABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
return true;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
|
||||
// check to see if the Servo library is in use
|
||||
#if defined Servo_h
|
||||
DISABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
TIMSK1 = 0x02; // Enable OCR1A match interrupt
|
||||
ENABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
return true;
|
||||
#else
|
||||
DISABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
TIMSK2 = 0x02; // Enable OCR2A match interrupt
|
||||
ENABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
return true;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(__AVR_ATtiny85__)
|
||||
DISABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
bitSet(TIMSK,6); // Enable interrupt on match between TCNT1 and OCR1A
|
||||
ENABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
return true;
|
||||
#endif
|
||||
|
||||
// #else
|
||||
return false; // unknown or unsupported platform.
|
||||
#endif
|
||||
}
|
||||
|
||||
#if USE_ARDUINO_INTERRUPTS
|
||||
/*
|
||||
We create the Interrupt Service Routine only if the Sketch is
|
||||
using interrupts. If we defined it when we didn't use it,
|
||||
the ISR() will inappropriately intercept timer interrupts that
|
||||
we don't use when not using interrupts.
|
||||
|
||||
We define the ISR that handles the timer that
|
||||
PulseSensorPlaygroundSetupInterrupt() set up.
|
||||
NOTE: Make sure that this ISR uses the appropriate timer for
|
||||
the platform detected by PulseSensorPlaygroundSetupInterrupt(), above.
|
||||
*/
|
||||
#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) || defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega16U4__) || defined(__AVR_ATtiny85__)
|
||||
#if defined Servo_h
|
||||
ISR(TIMER2_COMPA_vect)
|
||||
{
|
||||
DISABLE_PULSE_SENSOR_INTERRUPTS; // disable interrupts while we do this
|
||||
|
||||
PulseSensorPlayground::OurThis->onSampleTime();
|
||||
|
||||
ENABLE_PULSE_SENSOR_INTERRUPTS; // enable interrupts when you're done
|
||||
}
|
||||
#else
|
||||
ISR(TIMER1_COMPA_vect)
|
||||
{
|
||||
DISABLE_PULSE_SENSOR_INTERRUPTS; // disable interrupts while we do this
|
||||
|
||||
PulseSensorPlayground::OurThis->onSampleTime();
|
||||
|
||||
ENABLE_PULSE_SENSOR_INTERRUPTS; // enable interrupts when you're done
|
||||
}
|
||||
#endif
|
||||
#elif defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
|
||||
#if defined Servo_h
|
||||
ISR(TIMER1_COMPA_vect)
|
||||
{
|
||||
DISABLE_PULSE_SENSOR_INTERRUPTS; // disable interrupts while we do this
|
||||
|
||||
PulseSensorPlayground::OurThis->onSampleTime();
|
||||
|
||||
ENABLE_PULSE_SENSOR_INTERRUPTS; // enable interrupts when you're done
|
||||
}
|
||||
#else
|
||||
ISR(TIMER2_COMPA_vect)
|
||||
{
|
||||
DISABLE_PULSE_SENSOR_INTERRUPTS; // disable interrupts while we do this
|
||||
|
||||
PulseSensorPlayground::OurThis->onSampleTime();
|
||||
|
||||
ENABLE_PULSE_SENSOR_INTERRUPTS; // enable interrupts when you're done
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(__MK66FX1M0__)||(__MK64FX512__)||(__MK20DX256__)||(__MK20DX128__)
|
||||
// Interrupts not supported yet for Teensy
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
#endif // USE_ARDUINO_INTERRUPTS
|
||||
|
||||
#endif // defined(USE_ARDUINO_INTERRUPTS)
|
||||
|
||||
#endif // PULSE_SENSOR_INTERRUPTS_H
|
||||
// #endif
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
PulseSensor measurement manager.
|
||||
See https://www.pulsesensor.com to get started.
|
||||
|
||||
Copyright World Famous Electronics LLC - see LICENSE
|
||||
Contributors:
|
||||
Joel Murphy, https://pulsesensor.com
|
||||
Yury Gitman, https://pulsesensor.com
|
||||
Bradford Needham, @bneedhamia, https://bluepapertech.com
|
||||
|
||||
Licensed under the MIT License, a copy of which
|
||||
should have been included with this software.
|
||||
|
||||
This software is not intended for medical use.
|
||||
*/
|
||||
#include <PulseSensorPlayground.h>
|
||||
|
||||
/*
|
||||
Internal constants controlling the rate of fading for the FadePin.
|
||||
|
||||
FADE_SCALE = FadeLevel / FADE_SCALE is the corresponding PWM value.
|
||||
FADE_LEVEL_PER_SAMPLE = amount to decrease FadeLevel per sample.
|
||||
MAX_FADE_LEVEL = maximum FadeLevel value.
|
||||
|
||||
The time (milliseconds) to fade to black =
|
||||
(MAX_FADE_LEVEL / FADE_LEVEL_PER_SAMPLE) * sample time (2ms)
|
||||
*/
|
||||
#define FADE_SCALE 10
|
||||
#define FADE_LEVEL_PER_SAMPLE 12
|
||||
#define MAX_FADE_LEVEL (255 * FADE_SCALE)
|
||||
|
||||
/*
|
||||
Constructs a Pulse detector that will process PulseSensor voltages
|
||||
that the caller reads from the PulseSensor.
|
||||
*/
|
||||
PulseSensor::PulseSensor() {
|
||||
// Initialize the default configuration
|
||||
InputPin = A0;
|
||||
BlinkPin = -1;
|
||||
FadePin = -1;
|
||||
|
||||
// Initialize (seed) the pulse detector
|
||||
sampleIntervalMs = PulseSensorPlayground::MICROS_PER_READ / 1000;
|
||||
resetVariables();
|
||||
}
|
||||
|
||||
void PulseSensor::resetVariables(){
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
rate[i] = 0;
|
||||
}
|
||||
QS = false;
|
||||
BPM = 0;
|
||||
IBI = 750; // 750ms per beat = 80 Beats Per Minute (BPM)
|
||||
Pulse = false;
|
||||
sampleCounter = 0;
|
||||
lastBeatTime = 0;
|
||||
P = 512; // peak at 1/2 the input range of 0..1023
|
||||
T = 512; // trough at 1/2 the input range.
|
||||
threshSetting = 550; // used to seed and reset the thresh variable
|
||||
thresh = 550; // threshold a little above the trough
|
||||
amp = 100; // beat amplitude 1/10 of input range.
|
||||
firstBeat = true; // looking for the first beat
|
||||
secondBeat = false; // not yet looking for the second beat in a row
|
||||
FadeLevel = 0; // LED is dark.
|
||||
}
|
||||
|
||||
void PulseSensor::analogInput(int inputPin) {
|
||||
InputPin = inputPin;
|
||||
}
|
||||
|
||||
void PulseSensor::blinkOnPulse(int blinkPin) {
|
||||
BlinkPin = blinkPin;
|
||||
}
|
||||
|
||||
void PulseSensor::fadeOnPulse(int fadePin) {
|
||||
FadePin = fadePin;
|
||||
}
|
||||
|
||||
void PulseSensor::setThreshold(int threshold) {
|
||||
DISABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
threshSetting = threshold;
|
||||
thresh = threshold;
|
||||
ENABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
}
|
||||
|
||||
int PulseSensor::getLatestSample() {
|
||||
return Signal;
|
||||
}
|
||||
|
||||
int PulseSensor::getBeatsPerMinute() {
|
||||
return BPM;
|
||||
}
|
||||
|
||||
int PulseSensor::getInterBeatIntervalMs() {
|
||||
return IBI;
|
||||
}
|
||||
|
||||
int PulseSensor::getPulseAmplitude() {
|
||||
return amp;
|
||||
}
|
||||
|
||||
unsigned long PulseSensor::getLastBeatTime() {
|
||||
return lastBeatTime;
|
||||
}
|
||||
|
||||
boolean PulseSensor::sawStartOfBeat() {
|
||||
// Disable interrupts to avoid a race with the ISR.
|
||||
DISABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
boolean started = QS;
|
||||
QS = false;
|
||||
ENABLE_PULSE_SENSOR_INTERRUPTS;
|
||||
|
||||
return started;
|
||||
}
|
||||
|
||||
boolean PulseSensor::isInsideBeat() {
|
||||
return Pulse;
|
||||
}
|
||||
|
||||
void PulseSensor::readNextSample() {
|
||||
// We assume assigning to an int is atomic.
|
||||
Signal = analogRead(InputPin);
|
||||
}
|
||||
|
||||
void PulseSensor::processLatestSample() {
|
||||
// Serial.println(threshSetting);
|
||||
// Serial.print('\t');
|
||||
// Serial.println(thresh);
|
||||
sampleCounter += sampleIntervalMs; // keep track of the time in mS with this variable
|
||||
int N = sampleCounter - lastBeatTime; // monitor the time since the last beat to avoid noise
|
||||
|
||||
// Fade the Fading LED
|
||||
FadeLevel = FadeLevel - FADE_LEVEL_PER_SAMPLE;
|
||||
FadeLevel = constrain(FadeLevel, 0, MAX_FADE_LEVEL);
|
||||
|
||||
// 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
|
||||
// IBI value is unreliable so discard it
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 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 (we detected a beat)
|
||||
FadeLevel = MAX_FADE_LEVEL; // If we're fading, re-light that LED.
|
||||
}
|
||||
}
|
||||
|
||||
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 = threshSetting; // 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 PulseSensor::initializeLEDs() {
|
||||
if (BlinkPin >= 0) {
|
||||
pinMode(BlinkPin, OUTPUT);
|
||||
digitalWrite(BlinkPin, LOW);
|
||||
}
|
||||
if (FadePin >= 0) {
|
||||
#ifndef NO_ANALOG_WRITE
|
||||
pinMode(FadePin, OUTPUT);
|
||||
analogWrite(FadePin, 0); // turn off the LED.
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void PulseSensor::updateLEDs() {
|
||||
if (BlinkPin >= 0) {
|
||||
if(Pulse){
|
||||
digitalWrite(BlinkPin, HIGH);
|
||||
}else{
|
||||
digitalWrite(BlinkPin,LOW);
|
||||
}
|
||||
}
|
||||
|
||||
if (FadePin >= 0) {
|
||||
#ifndef NO_ANALOG_WRITE
|
||||
analogWrite(FadePin, FadeLevel / FADE_SCALE);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
PulseSensor measurement manager.
|
||||
See https://www.pulsesensor.com to get started.
|
||||
|
||||
Copyright World Famous Electronics LLC - see LICENSE
|
||||
Contributors:
|
||||
Joel Murphy, https://pulsesensor.com
|
||||
Yury Gitman, https://pulsesensor.com
|
||||
Bradford Needham, @bneedhamia, https://bluepapertech.com
|
||||
|
||||
Licensed under the MIT License, a copy of which
|
||||
should have been included with this software.
|
||||
|
||||
This software is not intended for medical use.
|
||||
*/
|
||||
#ifndef PULSE_SENSOR_H
|
||||
#define PULSE_SENSOR_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
class PulseSensor {
|
||||
public:
|
||||
// Constructs a PulseSensor manager using a default configuration.
|
||||
PulseSensor();
|
||||
|
||||
// sets variables to default start values
|
||||
void resetVariables();
|
||||
|
||||
// Sets the analog input pin this PulseSensor is connected to.
|
||||
void analogInput(int inputPin);
|
||||
|
||||
// Configures to blink the given pin while inside a pulse.
|
||||
void blinkOnPulse(int blinkPin);
|
||||
|
||||
// Configures to fade the given, starting on start of pulse.
|
||||
void fadeOnPulse(int fadePin);
|
||||
|
||||
// Returns the sample most recently-read from this PulseSensor.
|
||||
int getLatestSample();
|
||||
|
||||
// Returns the latest beats-per-minute measurement on this PulseSensor.
|
||||
int getBeatsPerMinute();
|
||||
|
||||
// Returns the latest inter-beat interval (milliseconds) on this PulseSensor.
|
||||
int getInterBeatIntervalMs();
|
||||
|
||||
// Reads and clears the 'saw start of beat' flag, "QS".
|
||||
boolean sawStartOfBeat();
|
||||
|
||||
// Returns true if this PulseSensor signal is inside a beat vs. outside.
|
||||
boolean isInsideBeat();
|
||||
|
||||
// Returns the latest amp value.
|
||||
int getPulseAmplitude();
|
||||
|
||||
// Returns the sample number of the most recent detected pulse.
|
||||
unsigned long getLastBeatTime();
|
||||
|
||||
//COULD move these to private by having a single public function the ISR calls.
|
||||
// (internal to the library) Read a sample from this PulseSensor.
|
||||
void readNextSample();
|
||||
|
||||
// (internal to the library) Process the latest sample.
|
||||
void processLatestSample();
|
||||
|
||||
// (internal to the library) Set up any LEDs the user wishes.
|
||||
void initializeLEDs();
|
||||
|
||||
// (internal to the library) Update the Blink and Fade LED states.
|
||||
void updateLEDs();
|
||||
|
||||
// (internal to the library) Updtate the thresh variables.
|
||||
void setThreshold(int threshold);
|
||||
|
||||
|
||||
private:
|
||||
// Configuration
|
||||
int InputPin; // Analog input pin for PulseSensor.
|
||||
int BlinkPin; // pin to blink in beat, or -1.
|
||||
int FadePin; // pin to fade on beat, or -1.
|
||||
|
||||
// Pulse detection output variables.
|
||||
// Volatile because our pulse detection code could be called from an Interrupt
|
||||
volatile int BPM; // int that holds raw Analog in 0. updated every call to readSensor()
|
||||
volatile int Signal; // holds the latest incoming raw data (0..1023)
|
||||
volatile int IBI; // int that holds the time interval (ms) between beats! Must be seeded!
|
||||
volatile boolean Pulse; // "True" when User's live heartbeat is detected. "False" when not a "live beat".
|
||||
volatile boolean QS; // The start of beat has been detected and not read by the Sketch.
|
||||
volatile int FadeLevel; // brightness of the FadePin, in scaled PWM units. See FADE_SCALE
|
||||
volatile int threshSetting; // used to seed and reset the thresh variable
|
||||
volatile int amp; // used to hold amplitude of pulse waveform, seeded (sample value)
|
||||
volatile unsigned long lastBeatTime; // used to find IBI. Time (sampleCounter) of the previous detected beat start.
|
||||
|
||||
// Variables internal to the pulse detection algorithm.
|
||||
// Not volatile because we use them only internally to the pulse detection.
|
||||
unsigned long sampleIntervalMs; // expected time between calls to readSensor(), in milliseconds.
|
||||
int rate[10]; // array to hold last ten IBI values (ms)
|
||||
unsigned long sampleCounter; // used to determine pulse timing. Milliseconds since we started.
|
||||
int P; // used to find peak in pulse wave, seeded (sample value)
|
||||
int T; // used to find trough in pulse wave, seeded (sample value)
|
||||
int thresh; // used to find instant moment of heart beat, seeded (sample value)
|
||||
boolean firstBeat; // used to seed rate array so we startup with reasonable BPM
|
||||
boolean secondBeat; // used to seed rate array so we startup with reasonable BPM
|
||||
};
|
||||
#endif // PULSE_SENSOR_H
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
Formatting of Serial output from PulseSensors.
|
||||
See https://www.pulsesensor.com to get started.
|
||||
|
||||
Copyright World Famous Electronics LLC - see LICENSE
|
||||
Contributors:
|
||||
Joel Murphy, https://pulsesensor.com
|
||||
Yury Gitman, https://pulsesensor.com
|
||||
Bradford Needham, @bneedhamia, https://bluepapertech.com
|
||||
|
||||
Licensed under the MIT License, a copy of which
|
||||
should have been included with this software.
|
||||
|
||||
This software is not intended for medical use.
|
||||
*/
|
||||
#include "PulseSensorSerialOutput.h"
|
||||
|
||||
PulseSensorSerialOutput::PulseSensorSerialOutput() {
|
||||
pOutput = NULL;
|
||||
OutputType = SERIAL_PLOTTER;
|
||||
}
|
||||
|
||||
void PulseSensorSerialOutput::setSerial(Stream &output) {
|
||||
pOutput = &output;
|
||||
}
|
||||
|
||||
Stream *PulseSensorSerialOutput::getSerial() {
|
||||
return pOutput;
|
||||
}
|
||||
|
||||
void PulseSensorSerialOutput::setOutputType(byte outputType) {
|
||||
OutputType = outputType;
|
||||
}
|
||||
|
||||
void PulseSensorSerialOutput::outputSample(PulseSensor sensors[], int numSensors) {
|
||||
if (!pOutput) {
|
||||
return; // no serial output object has been set.
|
||||
}
|
||||
|
||||
switch (OutputType) {
|
||||
case SERIAL_PLOTTER:
|
||||
if (numSensors == 1) {
|
||||
pOutput->print(sensors[0].getBeatsPerMinute());
|
||||
pOutput->print(F(","));
|
||||
pOutput->print(sensors[0].getInterBeatIntervalMs());
|
||||
pOutput->print(F(","));
|
||||
pOutput->println(sensors[0].getLatestSample());
|
||||
} else {
|
||||
for (int i = 0; i < numSensors; ++i) {
|
||||
if (i != 0) {
|
||||
pOutput->print(F(","));
|
||||
}
|
||||
pOutput->print(sensors[i].getLatestSample());
|
||||
// Could output BPM and IBI here.
|
||||
}
|
||||
pOutput->println();
|
||||
}
|
||||
break;
|
||||
|
||||
case PROCESSING_VISUALIZER:
|
||||
// Don't print bpm and ibi here; they're printed per-beat.
|
||||
if (numSensors == 1) {
|
||||
outputToSerial('S', sensors[0].getLatestSample());
|
||||
} else {
|
||||
// PulseSensor 0 = a; #1 = b; #2 = c, etc.
|
||||
for(int i = 0; i < numSensors; ++i){
|
||||
outputToSerial('a' + i, sensors[i].getLatestSample());
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// unknown output type: no output
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PulseSensorSerialOutput::outputBeat(PulseSensor sensors[], int numSensors, int sensorIndex) {
|
||||
if (!pOutput) {
|
||||
return; // no serial output object has been set.
|
||||
}
|
||||
|
||||
switch (OutputType) {
|
||||
case SERIAL_PLOTTER:
|
||||
/*
|
||||
The plotter doesn't understand occasionally-printed data,
|
||||
so we print nothing per-beat.
|
||||
*/
|
||||
break;
|
||||
|
||||
case PROCESSING_VISUALIZER:
|
||||
if (numSensors == 1) {
|
||||
outputToSerial('B', sensors[sensorIndex].getBeatsPerMinute());
|
||||
outputToSerial('Q', sensors[sensorIndex].getInterBeatIntervalMs());
|
||||
} else {
|
||||
// PulseSensor 0 = A, M; #1 = B, N; etc.
|
||||
outputToSerial('A' + sensorIndex
|
||||
, sensors[sensorIndex].getBeatsPerMinute());
|
||||
outputToSerial('M' + sensorIndex
|
||||
, sensors[sensorIndex].getInterBeatIntervalMs());
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// unknown output type: no output
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// // testing feedback
|
||||
// void printThreshSetting() {
|
||||
//
|
||||
// }
|
||||
|
||||
void PulseSensorSerialOutput::outputToSerial(char symbol, int data) {
|
||||
if (!pOutput) {
|
||||
return; // no serial output object has been set.
|
||||
}
|
||||
|
||||
pOutput->print(symbol);
|
||||
pOutput->println(data);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
Serial output formatter for the PulseSensor Playground.
|
||||
This object knows all about the formats for our Serial output.
|
||||
See https://www.pulsesensor.com to get started.
|
||||
|
||||
Copyright World Famous Electronics LLC - see LICENSE
|
||||
Contributors:
|
||||
Joel Murphy, https://pulsesensor.com
|
||||
Yury Gitman, https://pulsesensor.com
|
||||
Bradford Needham, @bneedhamia, https://bluepapertech.com
|
||||
|
||||
Licensed under the MIT License, a copy of which
|
||||
should have been included with this software.
|
||||
|
||||
This software is not intended for medical use.
|
||||
*/
|
||||
#ifndef PULSE_SENSOR_SERIAL_OUTPUT_H
|
||||
#define PULSE_SENSOR_SERIAL_OUTPUT_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "PulseSensor.h" // to access PulseSensor state.
|
||||
|
||||
/*
|
||||
Destinations for serial output:
|
||||
PROCESSING_VISUALIZER = write to the Processing Visualizer Sketch.
|
||||
SERIAL_PLOTTER = write to the Arduino IDE Serial Plotter.
|
||||
*/
|
||||
#define PROCESSING_VISUALIZER ((byte) 1)
|
||||
#define SERIAL_PLOTTER ((byte) 2)
|
||||
|
||||
class PulseSensorSerialOutput {
|
||||
public:
|
||||
|
||||
/*
|
||||
Constructs a default Serial output manager.
|
||||
*/
|
||||
PulseSensorSerialOutput();
|
||||
|
||||
/*
|
||||
Tells the library what Serial output to use,
|
||||
such as Serial, Serial1, or a SoftwareSerial.
|
||||
*/
|
||||
void setSerial(Stream &output);
|
||||
|
||||
/*
|
||||
Find what Serial stream we are configured to print to.
|
||||
|
||||
Returns a pointer to the Serial we're configured for
|
||||
(for example Serial, Serial1, or a SoftwareSerial object),
|
||||
or NULL if no Serial output has been set up.
|
||||
*/
|
||||
Stream *getSerial();
|
||||
|
||||
/*
|
||||
Sets the format (destination) of the Serial Output:
|
||||
SERIAL_PLOTTER or PROCESSING_VISUALIZER.
|
||||
*/
|
||||
void setOutputType(byte outputType);
|
||||
|
||||
/*
|
||||
Output the Signal data for all PulseSensors
|
||||
*/
|
||||
void outputSample(PulseSensor sensors[], int numberOfSensors);
|
||||
|
||||
/*
|
||||
Output the per-beat data (Beats per Minute, Inter-beat Interval)
|
||||
for the given PulseSensor.
|
||||
|
||||
sensorIndex = the sensor to output beat information about.
|
||||
Usually is the PulseSensor that a beat was detected on.
|
||||
*/
|
||||
void outputBeat(PulseSensor sensors[], int numberOfSensors, int sensorIndex);
|
||||
|
||||
/*
|
||||
Write the given data prefixed by the given symbol.
|
||||
*/
|
||||
void outputToSerial(char symbol, int data);
|
||||
|
||||
private:
|
||||
// If non-null, the output stream to print to. If null, don't print.
|
||||
Stream *pOutput;
|
||||
|
||||
// The destination of data: PROCESSING_VISUALIZER or SERIAL_PLOTTER
|
||||
int OutputType;
|
||||
|
||||
};
|
||||
#endif // PULSE_SENSOR_SERIAL_OUTPUT_H
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
Sample time statistics functions.
|
||||
Designed to provide insite into the timing accuracy of
|
||||
programs that read data from a PulseSensor.
|
||||
See PulseSensorPlayground.h PULSE_SENSOR_TIMING_ANALYSIS
|
||||
|
||||
See https://www.pulsesensor.com to get started.
|
||||
|
||||
Copyright World Famous Electronics LLC - see LICENSE
|
||||
Contributors:
|
||||
Joel Murphy, https://pulsesensor.com
|
||||
Yury Gitman, https://pulsesensor.com
|
||||
Bradford Needham, @bneedhamia, https://bluepapertech.com
|
||||
|
||||
Licensed under the MIT License, a copy of which
|
||||
should have been included with this software.
|
||||
|
||||
This software is not intended for medical use.
|
||||
*/
|
||||
#include <PulseSensorPlayground.h>
|
||||
|
||||
PulseSensorTimingStatistics::PulseSensorTimingStatistics(
|
||||
long sampleIntervalMicros, int samplesToMeasure) {
|
||||
SamplesDesired = samplesToMeasure;
|
||||
SampleIntervalMicros = sampleIntervalMicros;
|
||||
|
||||
resetStatistics();
|
||||
}
|
||||
|
||||
void PulseSensorTimingStatistics::resetStatistics() {
|
||||
SamplesSeen = 0;
|
||||
MinJitterMicros = 0;
|
||||
MaxJitterMicros = 0;
|
||||
OffsetsSum = 0.0;
|
||||
LastSampleMicros = 0L;
|
||||
}
|
||||
|
||||
int PulseSensorTimingStatistics::recordSampleTime() {
|
||||
unsigned long nowMicros = micros();
|
||||
|
||||
if (SamplesSeen > 0) {
|
||||
long offsetMicros =
|
||||
(long) (nowMicros - LastSampleMicros) - SampleIntervalMicros;
|
||||
offsetMicros = constrain(offsetMicros, -32767, 32767);
|
||||
|
||||
OffsetsSum += (float) offsetMicros;
|
||||
|
||||
if (MinJitterMicros > offsetMicros) {
|
||||
MinJitterMicros = offsetMicros;
|
||||
}
|
||||
if (MaxJitterMicros < offsetMicros) {
|
||||
MaxJitterMicros = offsetMicros;
|
||||
}
|
||||
}
|
||||
|
||||
LastSampleMicros = nowMicros;
|
||||
++SamplesSeen;
|
||||
|
||||
return (SamplesDesired - SamplesSeen);
|
||||
}
|
||||
|
||||
void PulseSensorTimingStatistics::outputStatistics(Stream *pOut) {
|
||||
if (!pOut) {
|
||||
return; // not configured for Serial output.
|
||||
}
|
||||
pOut->print(MinJitterMicros);
|
||||
pOut->print(" ");
|
||||
pOut->print(getAverageOffsetMicros());
|
||||
pOut->print(" ");
|
||||
pOut->println(MaxJitterMicros);
|
||||
}
|
||||
|
||||
int PulseSensorTimingStatistics::getAverageOffsetMicros() {
|
||||
// the number of offsets is the number of samples - 1.
|
||||
if (SamplesSeen - 1 <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return (int) ((OffsetsSum + 0.5) / (SamplesSeen - 1));
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
Sample time statistics functions.
|
||||
Designed to provide insite into the timing accuracy of
|
||||
programs that don't use interrupts to read data from a PulseSensor.
|
||||
|
||||
See https://www.pulsesensor.com to get started.
|
||||
|
||||
Copyright World Famous Electronics LLC - see LICENSE
|
||||
Contributors:
|
||||
Joel Murphy, https://pulsesensor.com
|
||||
Yury Gitman, https://pulsesensor.com
|
||||
Bradford Needham, @bneedhamia, https://bluepapertech.com
|
||||
|
||||
Licensed under the MIT License, a copy of which
|
||||
should have been included with this software.
|
||||
|
||||
This software is not intended for medical use.
|
||||
*/
|
||||
#ifndef PULSE_SENSOR_TIMING_STATISTICS_H
|
||||
#define PULSE_SENSOR_TIMING_STATISTICS_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
/*
|
||||
Timing statistics show how accurate the beats per minute
|
||||
and inter-beat interval measurements are.
|
||||
|
||||
An average offset other than zero shows that samples were recorded
|
||||
at a rate different from the expected rate.
|
||||
For example, for an expected sample interval of 2000 microseconds
|
||||
(500 samples per second), an offset of 60 microseconds indicates that
|
||||
samples were recorded at a rate 3% slower than expected, which in turn
|
||||
shows that the measured beats per minute and inter-beat interval
|
||||
have a 3% error due to timing offset.
|
||||
|
||||
A large span between minimum and maximum jitter shows that sometimes
|
||||
the sampling loop was slow or fast. This could be due to, for example,
|
||||
unexpectedly slow code that executes only every so often.
|
||||
*/
|
||||
class PulseSensorTimingStatistics {
|
||||
public:
|
||||
/*
|
||||
Constructs an object for measuring statistics about the timing
|
||||
of samples from the PulseSensor.
|
||||
|
||||
sampleIntervalMicros = expected time between samples, in microseconds.
|
||||
samplesToMeasure = number of samples to measure timing over.
|
||||
*/
|
||||
PulseSensorTimingStatistics(long sampleIntervalMicros, int samplesToMeasure);
|
||||
|
||||
/*
|
||||
(re)start the collection of timing statistics.
|
||||
Called automatically by the PulseSensorTimingStatistics constructor.
|
||||
*/
|
||||
void resetStatistics();
|
||||
|
||||
|
||||
/*
|
||||
Record the fact that we just now read the PulseSensor output.
|
||||
|
||||
Returns the number of samples remaining to be recorded.
|
||||
The caller should stop calling recordSampleTime() once
|
||||
this function returns 0.
|
||||
*/
|
||||
int recordSampleTime();
|
||||
|
||||
/*
|
||||
Serial prints the sample timing statistics.
|
||||
*/
|
||||
void outputStatistics(Stream *pOut);
|
||||
|
||||
int getMinJitterMicros() {
|
||||
return MinJitterMicros;
|
||||
}
|
||||
int getMaxJitterMicros() {
|
||||
return MaxJitterMicros;
|
||||
}
|
||||
|
||||
/*
|
||||
Returns the average offset seen so far, in microseconds.
|
||||
*/
|
||||
int getAverageOffsetMicros();
|
||||
|
||||
private:
|
||||
long SampleIntervalMicros; // desired sample interval, in microseconds.
|
||||
int SamplesDesired; // total number of samples we want to record.
|
||||
unsigned long LastSampleMicros; // time (microseconds) of the previous sample.
|
||||
int MinJitterMicros; // minimum offset seen.
|
||||
int MaxJitterMicros; // maximum offset seen.
|
||||
float OffsetsSum; // sum of offsets so far.
|
||||
int SamplesSeen; // number of samples seen so far.
|
||||
};
|
||||
#endif // PULSE_SENSOR_TIMING_STATISTICS_H
|
||||
Reference in New Issue
Block a user