Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated with several time-domain calculations for evaluating Heart Ra… #15

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions PulseSensorAmped_Arduino_1dot4/AllSerialHandling.ino
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,27 @@ void serialOutputWhenBeatHappens(){
Serial.print("*** Heart-Beat Happened *** "); //ASCII Art Madness
Serial.print("BPM: ");
Serial.print(BPM);
Serial.print(" ");
Serial.print(", IBI: ");
Serial.print( IBI );
Serial.print( ", NNDelta: " );
Serial.print( RRDelta );
Serial.print( ", SDNN: " );
Serial.print( SDNN );
Serial.print(" || HRV: ");
Serial.print( int( LN20RMSSD ) );
Serial.print(" (RMSSD: ");
Serial.print( RMSSD );
Serial.print( "), SDSD: " );
Serial.print( SDSD );
Serial.print( ", NN50: " );
Serial.print( NN50 );
Serial.print( ", pNN50: " );
Serial.print( PNN50 );
Serial.println( " " );
} else{
sendDataToSerial('B',BPM); // send heart rate with a 'B' prefix
sendDataToSerial('Q',IBI); // send time between beats with a 'Q' prefix
sendDataToSerial('B',BPM); // send heart rate with a 'B' prefix
sendDataToSerial('Q',IBI); // send time between beats with a 'Q' prefix
sendDataToSerial('H', int( LN20RMSSD ) ); // send HRV with a 'H' prefix
}
}

Expand All @@ -39,8 +56,8 @@ void sendDataToSerial(char symbol, int data ){

// Code to Make the Serial Monitor Visualizer Work
void arduinoSerialMonitorVisual(char symbol, int data ){
const int sensorMin = 0; // sensor minimum, discovered through experiment
const int sensorMax = 1024; // sensor maximum, discovered through experiment
const int sensorMin = 0; // sensor minimum, discovered through experiment
const int sensorMax = 1024; // sensor maximum, discovered through experiment

int sensorReading = data;
// map the sensor range to a range of 12 options:
Expand All @@ -50,7 +67,7 @@ const int sensorMax = 1024; // sensor maximum, discovered through experiment
// range value:
switch (range) {
case 0:
Serial.println(""); /////ASCII Art Madness
Serial.println(""); /////ASCII Art Madness
break;
case 1:
Serial.println("---");
Expand Down
131 changes: 104 additions & 27 deletions PulseSensorAmped_Arduino_1dot4/Interrupt.ino
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@



volatile int rate[10]; // array to hold last ten IBI values
volatile unsigned long sampleCounter = 0; // used to determine pulse timing
volatile unsigned long lastBeatTime = 0; // used to find IBI
volatile int P =512; // used to find peak in pulse wave, seeded
volatile int rate[61]; // array to hold last 61 IBI values
volatile unsigned long sampleCounter = 0; // used to determine pulse timing
volatile unsigned long lastBeatTime = 0; // used to find IBI
volatile int P = 512; // used to find peak in pulse wave, seeded
volatile int T = 512; // used to find trough in pulse wave, seeded
volatile int thresh = 525; // used to find instant moment of heart beat, seeded
volatile int amp = 100; // used to hold amplitude of pulse waveform, seeded
volatile boolean firstBeat = true; // used to seed rate array so we startup with reasonable BPM
volatile boolean secondBeat = false; // used to seed rate array so we startup with reasonable BPM


volatile int successiveRRDeltas[60]; // used to track differences between successive RR values
void interruptSetup(){
// Initializes Timer2 to throw an interrupt every 2mS.
TCCR2A = 0x02; // DISABLE PWM ON DIGITAL PINS 3 AND 11, AND GO INTO CTC MODE
Expand All @@ -24,22 +21,23 @@ void interruptSetup(){

// THIS IS THE TIMER 2 INTERRUPT SERVICE ROUTINE.
// Timer 2 makes sure that we take a reading every 2 miliseconds
ISR(TIMER2_COMPA_vect){ // triggered when Timer2 counts to 124

ISR(TIMER2_COMPA_vect){ // triggered when Timer2 counts to 124
cli(); // disable interrupts while we do this
Signal = analogRead(pulsePin); // read the Pulse Sensor
sampleCounter += 2; // keep track of the time in mS with this variable
int N = sampleCounter - lastBeatTime; // monitor the time since the last beat to avoid noise

// find the peak and trough of the pulse wave
// 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 < 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
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
Expand All @@ -50,10 +48,14 @@ ISR(TIMER2_COMPA_vect){ // triggered when Timer2 counts
IBI = sampleCounter - lastBeatTime; // measure time between beats in mS
lastBeatTime = sampleCounter; // keep track of time for next pulse

int i;
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;
for(i=0; i<=60; i++){ // seed the running total to get a realisitic BPM at startup
rate[i] = IBI;
}
for( i=0; i<59; i++){ // seed the successiveRRDeltas array with 0s
successiveRRDeltas[i] = 0;
}
}

Expand All @@ -64,21 +66,96 @@ ISR(TIMER2_COMPA_vect){ // triggered when Timer2 counts
return; // IBI value is unreliable so discard it
}


// keep a running total of the last 10 IBI values
word runningTotal = 0; // clear the runningTotal variable

for(int i=0; i<=8; i++){ // shift data in the rate array
rate[i] = rate[i+1]; // and drop the oldest IBI value
runningTotal += rate[i]; // add up the 9 oldest IBI values
for( i=0; i<=59; i++){ // update rate array. shift data
rate[i] = rate[i+1]; // and drop the oldest IBI value
}
rate[60] = IBI; // add the latest IBI in the rate array

// calculate a running total of the last 10 IBI values
word runningTotal = 0; // clear the runningTotal variable
for( i=51; i<=60; i++ )
{
runningTotal += rate[i];
}

rate[9] = IBI; // add the latest IBI to the rate array
runningTotal += rate[9]; // add the latest IBI to runningTotal
runningTotal /= 10; // average the last 10 IBI values
BPM = 60000/runningTotal; // how many beats can fit into a minute? that's BPM!
QS = true; // set Quantified Self flag
// QS FLAG IS NOT CLEARED INSIDE THIS ISR

// HRV Analysis
// https://en.wikipedia.org/wiki/Heart_rate_variability#HRV_analysis

for( i=0; i<=58; i++){ // update successive RR difference array. shift data
successiveRRDeltas[i] = successiveRRDeltas[ i + 1 ];
}
successiveRRDeltas[59] = rate[ 60 ] - rate[ 59 ]; // set the newest RR difference as the last entry in the array

RRDelta = successiveRRDeltas[59]; // store the newest RR difference

// SDNN: the standard deviation of NN (RR) intervals.
// alternate calculation method for stdDev without a second loop may be possible:
// https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm
double tVal = 0; // temp utility variable to be re-used in loops
double aveRR = 0; // calculate the average RR value.

for( i=1; i<=60; i++){ // add up the last 60 RR values and divide by 60
aveRR += rate[ i ]; // (Don't include the first entry or you'll add 61 values)
}
aveRR /= 60;

for( i=1; i<=60; i++){ // calculate the standard devation of RR intervals
tVal = aveRR - rate[ i ]; // using the average that was just calculated
SDNN += tVal * tVal;
}
SDNN = sqrt( SDNN / 60 );

// SDSD: standard deviation of successive differences
// The standard deviation of the successive differences between adjacent NNs (RRs).
SDSD = 0;
int aveRRDelta;
for(int i=0; i<=59; i++) // find the average of successive differences
{
aveRRDelta = successiveRRDeltas[ i ];
}
aveRRDelta /= 60;

for(int i=0; i<=59; i++) // calculate the standard deviation of successive differences
{ // using the average that was just calculated
tVal = aveRRDelta - successiveRRDeltas[ i ];
SDSD += tVal * tVal;
}
SDSD = sqrt( SDSD / 60 );

// RMSSD: root mean square of successive differences
// The square root of the mean of the squares of the successive differences between adjacent NNs (RRs).
RMSSD = 0;

for(int i=0; i<=59; i++) // square each successive RR difference
{ // take the sum of all the values
tVal = successiveRRDeltas[ i ]; // divide by the total sample size to get the mean
RMSSD += tVal * tVal; // and take the square root
}
RMSSD = sqrt( RMSSD / 60 );

LN20RMSSD = 20 * log( RMSSD ); // take the natural log of RMSSD
// then scale the result so the range is linear and values
// fall approximately on the scale of 0-100.

// NN50: the number of pairs of successive NNs (RRs) that differ by more than 50 ms.
NN50 = 0; // add all the successive differences that are greater than 50ms
for(int i=0; i<=59; i++)
{
if( abs( successiveRRDeltas[ i ] ) > 50 )
{
NN50 ++;
}
}

// pNN50: the proportion of NN50 divided by total number of NNs (RRs).
PNN50 = NN50 / 60; // divide the total number of successive differences greater than 50s
// that were found and divide by the total sample size to get the
// percent of successive differences greater than 50s.

}
}

Expand Down
11 changes: 11 additions & 0 deletions PulseSensorAmped_Arduino_1dot4/PulseSensorAmped_Arduino_1dot4.ino
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ This code:

Read Me:
https://github.com/WorldFamousElectronics/PulseSensor_Amped_Arduino/blob/master/README.md

Updated with HRV display by Elliot Mebane, Roguish, Inc. Feb 2016
HRV requires 1 minute of data before it's accurate.
---------------------- ---------------------- ----------------------
*/

Expand All @@ -26,6 +29,14 @@ volatile int IBI = 600; // int that holds the time interval between
volatile boolean Pulse = false; // "True" when User's live heartbeat is detected. "False" when not a "live beat".
volatile boolean QS = false; // becomes true when Arduoino finds a beat.

volatile int RRDelta = 0;
volatile double RMSSD = 0;
volatile double LN20RMSSD = 0;
volatile double SDNN = 0;
volatile double SDSD = 0;
volatile double NN50 = 0;
volatile double PNN50 = 0;

// Regards Serial OutPut -- Set This Up to your needs
static boolean serialVisual = false; // Set to 'false' by Default. Re-set to 'true' to see Arduino Serial Monitor ASCII Visual Pulse

Expand Down