Skip to content

Commit 0abb6ec

Browse files
author
Warren Gill
committed
Code for pulse sensor from pulsesensor.com
1 parent 864883e commit 0abb6ec

File tree

4 files changed

+498
-0
lines changed

4 files changed

+498
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// WG: Mod for Micro (TIMER2) and timeout delay for LED
2+
volatile int rate[10]; // used to hold last ten IBI values
3+
volatile unsigned long sampleCounter = 0; // used to determine pulse timing
4+
volatile unsigned long lastBeatTime = 0; // used to find the inter beat interval
5+
volatile unsigned long lastLEDtime = 0; // measures the minimum time for lighting the LED
6+
volatile int P =512; // used to find peak in pulse wave
7+
volatile int T = 512; // used to find trough in pulse wave
8+
volatile int thresh = 512; // used to find instant moment of heart beat
9+
volatile int amp = 100; // used to hold amplitude of pulse waveform
10+
volatile boolean firstBeat = true; // used to seed rate array so we startup with reasonable BPM
11+
volatile boolean secondBeat = true; // used to seed rate array so we startup with reasonable BPM
12+
13+
void interruptSetup(){ // Initializes Timer1 to throw an interrupt every 2mS.
14+
TCCR1A = 0x02; // Disable PWM on pins D9 and D10, go into CTC mode
15+
TCCR1B = 0x05; // Don't force compare, 64 prescalar
16+
OCR1A = 0X7C; // Set the top of the count to 124 for 500Hz sample rate
17+
TIMSK1 = 0x02; // Enable interrupt on match between TIMER1 and OCR1A
18+
sei(); // Enable global interrupts
19+
}
20+
21+
// Service TIMER1 interrupts
22+
// Timer1 makes sure that we take a reading every 2 milliseconds
23+
24+
ISR(TIMER1_COMPA_vect){ // triggered when Timer1 counts to 124
25+
cli(); // disable interrupts while we do this
26+
Signal = analogRead(pulsePin); // read the Pulse Sensor
27+
sampleCounter = millis(); // keep track of the time in mS with this variable
28+
int N = sampleCounter - lastBeatTime; // monitor the time since the last beat to avoid noise
29+
30+
// Find the peak and trough of the pulse wave
31+
if (Signal < thresh && N > (IBI/5)*3) { // avoid dichrotic noise by waiting 3/5 of last IBI
32+
if (Signal < T) { // T is the trough
33+
T = Signal; // keep track of lowest point in pulse wave
34+
}
35+
}
36+
37+
if (Signal > thresh && Signal > P) { // thresh condition helps avoid noise
38+
P = Signal; // P is the peak
39+
} // keep track of highest point in pulse wave
40+
41+
// Look for the heartbeat
42+
// Signal surges up in value every time there is a pulse
43+
if (N > 250) { // avoid high frequency noise
44+
if ( (Signal > thresh) && (Pulse == false) && (N > (IBI/5)*3) ) {
45+
Pulse = true; // set the Pulse flag when we think there is a pulse
46+
digitalWrite(blinkPin, HIGH); // turn on pin 13 LED
47+
lastLEDtime = sampleCounter; // remember when the LED was turned on
48+
IBI = sampleCounter - lastBeatTime; // measure time between beats in mS
49+
lastBeatTime = sampleCounter; // keep track of time for next pulse
50+
51+
if (firstBeat) { // if it's the first time we found a beat, firstBeat == TRUE
52+
firstBeat = false; // clear firstBeat flag
53+
return; // IBI value is unreliable so discard it
54+
}
55+
if (secondBeat) { // if this is the second beat, secondBeat == TRUE
56+
secondBeat = false; // clear secondBeat flag
57+
for (int i=0; i<=9; i++) { // seed the running total to get a realisitic BPM at startup
58+
rate[i] = IBI;
59+
}
60+
}
61+
62+
// keep a running total of the last 10 IBI values
63+
word runningTotal = 0; // clear the runningTotal variable
64+
65+
for (int i=0; i<=8; i++) { // shift data in the rate array
66+
rate[i] = rate[i+1]; // and drop the oldest IBI value
67+
runningTotal += rate[i]; // add up the 9 oldest IBI values
68+
}
69+
70+
rate[9] = IBI; // add the latest IBI to the rate array
71+
runningTotal += rate[9]; // add the latest IBI to runningTotal
72+
runningTotal /= 10; // average the last 10 IBI values
73+
BPM = 60000/runningTotal; // how many beats can fit into a minute? that's BPM!
74+
QS = true; // set Quantified Self flag
75+
// NOTE: QS flag is not cleared inside the ISR
76+
}
77+
}
78+
79+
if (Signal < thresh && Pulse == true) { // when the values are going down, the beat is over
80+
if ( sampleCounter > (lastLEDtime + 100) ) { // the LED should be on at least a quarter second so you can see it
81+
digitalWrite(blinkPin, LOW); // turn off pin 13 LED
82+
}
83+
Pulse = false; // reset the Pulse flag so we can do it again
84+
amp = P - T; // get amplitude of the pulse wave
85+
thresh = amp/2 + T; // set thresh at 50% of the amplitude
86+
P = thresh; // reset these for next time
87+
T = thresh;
88+
}
89+
90+
if (N > 2500) { // if 2.5 seconds go by without a beat
91+
thresh = 512; // set thresh default
92+
P = 512; // set P default
93+
T = 512; // set T default
94+
lastBeatTime = sampleCounter; // bring the lastBeatTime up to date
95+
firstBeat = true; // set these to avoid noise
96+
secondBeat = true; // when we get the heartbeat back
97+
}
98+
99+
sei(); // enable interrupts when you're done!
100+
101+
}// end ISR
102+
103+
104+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
2+
/*
3+
>> Pulse Sensor Amped 1.2 <<
4+
This code is for Pulse Sensor Amped by Joel Murphy and Yury Gitman
5+
www.pulsesensor.com
6+
>>> Pulse Sensor purple wire goes to Analog Pin 0 <<<
7+
Pulse Sensor sample aquisition and processing happens in the background via Timer 2 interrupt. 2mS sample rate.
8+
PWM on pins 3 and 11 will not work when using this code, because we are using Timer 2!
9+
The following variables are automatically updated:
10+
Signal : int that holds the analog signal data straight from the sensor. updated every 2mS.
11+
IBI : int that holds the time interval between beats. 2mS resolution.
12+
BPM : int that holds the heart rate value, derived every beat, from averaging previous 10 IBI values.
13+
QS : boolean that is made true whenever Pulse is found and BPM is updated. User must reset.
14+
Pulse : boolean that is true when a heartbeat is sensed then false in time with pin13 LED going out.
15+
16+
This code is designed with output serial data to Processing sketch "PulseSensorAmped_Processing-xx"
17+
The Processing sketch is a simple data visualizer.
18+
All the work to find the heartbeat and determine the heartrate happens in the code below.
19+
Pin 13 LED will blink with heartbeat.
20+
If you want to use pin 13 for something else, adjust the interrupt handler
21+
It will also fade an LED on pin fadePin with every beat. Put an LED and series resistor from fadePin to GND.
22+
Check here for detailed code walkthrough:
23+
http://pulsesensor.myshopify.com/pages/pulse-sensor-amped-arduino-v1dot1
24+
25+
Code Version 02 by Joel Murphy & Yury Gitman Fall 2012
26+
This update changes the HRV variable name to IBI, which stands for Inter-Beat Interval, for clarity.
27+
Switched the interrupt to Timer2. 500Hz sample rate, 2mS resolution IBI value.
28+
Fade LED pin moved to pin 5 (use of Timer2 disables PWM on pins 3 & 11).
29+
Tidied up inefficiencies since the last version.
30+
*
31+
* WG: Made several improvements in the Interrupt code. It works with Arduino Micro
32+
* Also added a counter to keep the LED lit for a minimum time (it was blinking too fast to be visible)
33+
*/
34+
35+
36+
// VARIABLES
37+
int pulsePin = 0; // Pulse Sensor purple wire connected to analog pin 0
38+
int blinkPin = 13; // pin to blink led at each beat
39+
int fadePin = 11; // pin to do fancy classy fading blink at each beat
40+
int fadeRate = 0; // used to fade LED on with PWM on fadePin
41+
42+
43+
// these variables are volatile because they are used during the interrupt service routine!
44+
volatile int BPM; // used to hold the pulse rate
45+
volatile int Signal; // holds the incoming raw data
46+
volatile int IBI = 600; // holds the time between beats, the Inter-Beat Interval
47+
volatile boolean Pulse = false; // true when pulse wave is high, false when it's low
48+
volatile boolean QS = false; // becomes true when Arduoino finds a beat.
49+
50+
51+
void setup(){
52+
pinMode(blinkPin,OUTPUT); // pin that will blink to your heartbeat!
53+
pinMode(fadePin,OUTPUT); // pin that will fade to your heartbeat!
54+
Serial.begin(115200); // we agree to talk fast!
55+
interruptSetup(); // sets up to read Pulse Sensor signal every 2mS
56+
// UN-COMMENT THE NEXT LINE IF YOU ARE POWERING The Pulse Sensor AT LOW VOLTAGE,
57+
// AND APPLY THAT VOLTAGE TO THE A-REF PIN
58+
//analogReference(EXTERNAL);
59+
}
60+
61+
62+
63+
void loop(){
64+
sendDataToProcessing('S', Signal); // send Processing the raw Pulse Sensor data
65+
if (QS == true){ // Quantified Self flag is true when arduino finds a heartbeat
66+
fadeRate = 255; // Set 'fadeRate' Variable to 255 to fade LED with pulse
67+
sendDataToProcessing('B',BPM); // send heart rate with a 'B' prefix
68+
sendDataToProcessing('Q',IBI); // send time between beats with a 'Q' prefix
69+
QS = false; // reset the Quantified Self flag for next time
70+
}
71+
72+
ledFadeToBeat();
73+
74+
delay(10); // take a break
75+
}
76+
77+
78+
void ledFadeToBeat(){
79+
fadeRate -= 15; // set LED fade value
80+
fadeRate = constrain(fadeRate,0,255); // keep LED fade value from going into negative numbers!
81+
analogWrite(fadePin,fadeRate); // fade LED
82+
}
83+
84+
85+
void sendDataToProcessing(char symbol, int data ){
86+
Serial.print(symbol); // symbol prefix tells Processing what type of data is coming
87+
Serial.println(data); // the data to send culminating in a carriage return
88+
}
89+
90+
91+
92+
93+
94+
95+

pulseMonitorPCD8544/Interrupt.ino

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
volatile int rate[10]; // used to hold last ten IBI values
2+
volatile unsigned long sampleCounter = 0; // used to determine pulse timing
3+
volatile unsigned long lastBeatTime = 0; // used to find the inter beat interval
4+
volatile unsigned long lastLEDtime = 0; // measures the minimum time for lighting the LED
5+
volatile int P =512; // used to find peak in pulse wave
6+
volatile int T = 512; // used to find trough in pulse wave
7+
volatile int thresh = 512; // used to find instant moment of heart beat
8+
volatile int amp = 100; // used to hold amplitude of pulse waveform
9+
volatile boolean firstBeat = true; // used to seed rate array so we startup with reasonable BPM
10+
volatile boolean secondBeat = true; // used to seed rate array so we startup with reasonable BPM
11+
12+
void interruptSetup(){ // Initializes Timer1 to throw an interrupt every 2mS.
13+
TCCR1A = 0x02; // Disable PWM on pins D9 and D10, go into CTC mode
14+
TCCR1B = 0x05; // Don't force compare, 64 prescalar
15+
OCR1A = 0X7C; // Set the top of the count to 124 for 500Hz sample rate
16+
TIMSK1 = 0x02; // Enable interrupt on match between TIMER1 and OCR1A
17+
sei(); // Enable global interrupts
18+
}
19+
20+
// Service TIMER1 interrupts
21+
// Timer1 makes sure that we take a reading every 2 milliseconds
22+
23+
ISR(TIMER1_COMPA_vect){ // triggered when Timer1 counts to 124
24+
cli(); // disable interrupts while we do this
25+
Signal = analogRead(pulsePin); // read the Pulse Sensor
26+
sampleCounter = millis(); // keep track of the time in mS with this variable
27+
int N = sampleCounter - lastBeatTime; // monitor the time since the last beat to avoid noise
28+
29+
// Find the peak and trough of the pulse wave
30+
if (Signal < thresh && N > (IBI/5)*3) { // avoid dichrotic noise by waiting 3/5 of last IBI
31+
if (Signal < T) { // T is the trough
32+
T = Signal; // keep track of lowest point in pulse wave
33+
}
34+
}
35+
36+
if (Signal > thresh && Signal > P) { // thresh condition helps avoid noise
37+
P = Signal; // P is the peak
38+
} // keep track of highest point in pulse wave
39+
40+
// Look for the heartbeat
41+
// Signal surges up in value every time there is a pulse
42+
if (N > 250) { // avoid high frequency noise
43+
if ( (Signal > thresh) && (Pulse == false) && (N > (IBI/5)*3) ) {
44+
Pulse = true; // set the Pulse flag when we think there is a pulse
45+
digitalWrite(blinkPin, HIGH); // turn on pin 13 LED
46+
display.setCursor(heartX, heartY); // draw the heart
47+
display.write(3);
48+
49+
IBI = sampleCounter - lastBeatTime; // measure time between beats in mS
50+
lastLEDtime = sampleCounter; // remember when the LED was turned on
51+
lastBeatTime = sampleCounter; // keep track of time for next pulse
52+
53+
if (firstBeat) { // if it's the first time we found a beat, firstBeat == TRUE
54+
firstBeat = false; // clear firstBeat flag
55+
return; // IBI value is unreliable so discard it
56+
}
57+
if (secondBeat) { // if this is the second beat, secondBeat == TRUE
58+
secondBeat = false; // clear secondBeat flag
59+
for (int i=0; i<=9; i++) { // seed the running total to get a realisitic BPM at startup
60+
rate[i] = IBI;
61+
}
62+
}
63+
64+
// keep a running total of the last 10 IBI values
65+
word runningTotal = 0; // clear the runningTotal variable
66+
67+
for (int i=0; i<=8; i++) { // shift data in the rate array
68+
rate[i] = rate[i+1]; // and drop the oldest IBI value
69+
runningTotal += rate[i]; // add up the 9 oldest IBI values
70+
}
71+
72+
rate[9] = IBI; // add the latest IBI to the rate array
73+
runningTotal += rate[9]; // add the latest IBI to runningTotal
74+
runningTotal /= 10; // average the last 10 IBI values
75+
BPM = 60000/runningTotal; // how many beats can fit into a minute? that's BPM!
76+
QS = true; // set Quantified Self flag
77+
// NOTE: QS flag is not cleared inside the ISR
78+
}
79+
}
80+
81+
if (Signal < thresh && Pulse == true) { // when the values are going down, the beat is over
82+
if ( sampleCounter > (lastLEDtime + 100) ) { // the LED should be on at least a quarter second so you can see it
83+
digitalWrite(blinkPin, LOW); // turn off pin 13 LED
84+
display.setCursor(heartX, heartY); // draw the heart
85+
display.write(0); // Erase the heart
86+
}
87+
Pulse = false; // reset the Pulse flag so we can do it again
88+
amp = P - T; // get amplitude of the pulse wave
89+
thresh = amp/2 + T; // set thresh at 50% of the amplitude
90+
P = thresh; // reset these for next time
91+
T = thresh;
92+
}
93+
94+
if (N > 2500) { // if 2.5 seconds go by without a beat
95+
thresh = 512; // set thresh default
96+
P = 512; // set P default
97+
T = 512; // set T default
98+
lastBeatTime = sampleCounter; // bring the lastBeatTime up to date
99+
firstBeat = true; // set these to avoid noise
100+
secondBeat = true; // when we get the heartbeat back
101+
}
102+
103+
sei(); // enable interrupts when you're done!
104+
105+
}// end ISR

0 commit comments

Comments
 (0)