初始化提交

This commit is contained in:
王立帮
2024-07-20 22:09:06 +08:00
commit c247dd07a6
6876 changed files with 2743096 additions and 0 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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));
}

View File

@@ -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