├── images ├── EnableSPI.png ├── selectPort.png ├── EnableSerial.png ├── gnuplotPulse.png ├── uploadButton.png ├── ArdLibManager.png ├── DownloadArduino.png ├── exampleSketch.png ├── serialPlotter5V.png ├── InstallPlayground.png ├── PiConfigWindowSPI.png ├── PulseSensorUno3V3.png ├── serialPlotter3V3.png ├── InterfacingOptions.png ├── PiConfigWindowSerial.png ├── ProcessingSketchbook.png ├── ProcessingPiPulseWave.png ├── PulseSensor_timer_term.png └── PulseSensor_RasPi_MCP3008_fritz.png ├── PulseSensor_C_Pi ├── Pulse_Sensor_gnuplot │ ├── Pulse_Plot_Config.gnu │ └── PulseSensor_gnuplot.c ├── PulseSensor_C_Pi.md └── Pulse_Sensor_Timer │ └── PulseSensor_timer.c ├── PulseSensor_Processing_Pi ├── PulseSensor_Processing_Pi │ ├── keyboard_mouse.pde │ ├── getPulse.pde │ └── PulseSensor_Processing_Pi.pde └── PulseSensor_Processing_Pi.md ├── README.md ├── LICENSE └── PulseSensor_Arduino_Pi └── PulseSensor_Arduino_Pi.md /images/EnableSPI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldFamousElectronics/Raspberry_Pi/HEAD/images/EnableSPI.png -------------------------------------------------------------------------------- /images/selectPort.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldFamousElectronics/Raspberry_Pi/HEAD/images/selectPort.png -------------------------------------------------------------------------------- /images/EnableSerial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldFamousElectronics/Raspberry_Pi/HEAD/images/EnableSerial.png -------------------------------------------------------------------------------- /images/gnuplotPulse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldFamousElectronics/Raspberry_Pi/HEAD/images/gnuplotPulse.png -------------------------------------------------------------------------------- /images/uploadButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldFamousElectronics/Raspberry_Pi/HEAD/images/uploadButton.png -------------------------------------------------------------------------------- /images/ArdLibManager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldFamousElectronics/Raspberry_Pi/HEAD/images/ArdLibManager.png -------------------------------------------------------------------------------- /images/DownloadArduino.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldFamousElectronics/Raspberry_Pi/HEAD/images/DownloadArduino.png -------------------------------------------------------------------------------- /images/exampleSketch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldFamousElectronics/Raspberry_Pi/HEAD/images/exampleSketch.png -------------------------------------------------------------------------------- /images/serialPlotter5V.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldFamousElectronics/Raspberry_Pi/HEAD/images/serialPlotter5V.png -------------------------------------------------------------------------------- /images/InstallPlayground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldFamousElectronics/Raspberry_Pi/HEAD/images/InstallPlayground.png -------------------------------------------------------------------------------- /images/PiConfigWindowSPI.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldFamousElectronics/Raspberry_Pi/HEAD/images/PiConfigWindowSPI.png -------------------------------------------------------------------------------- /images/PulseSensorUno3V3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldFamousElectronics/Raspberry_Pi/HEAD/images/PulseSensorUno3V3.png -------------------------------------------------------------------------------- /images/serialPlotter3V3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldFamousElectronics/Raspberry_Pi/HEAD/images/serialPlotter3V3.png -------------------------------------------------------------------------------- /images/InterfacingOptions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldFamousElectronics/Raspberry_Pi/HEAD/images/InterfacingOptions.png -------------------------------------------------------------------------------- /images/PiConfigWindowSerial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldFamousElectronics/Raspberry_Pi/HEAD/images/PiConfigWindowSerial.png -------------------------------------------------------------------------------- /images/ProcessingSketchbook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldFamousElectronics/Raspberry_Pi/HEAD/images/ProcessingSketchbook.png -------------------------------------------------------------------------------- /images/ProcessingPiPulseWave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldFamousElectronics/Raspberry_Pi/HEAD/images/ProcessingPiPulseWave.png -------------------------------------------------------------------------------- /images/PulseSensor_timer_term.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldFamousElectronics/Raspberry_Pi/HEAD/images/PulseSensor_timer_term.png -------------------------------------------------------------------------------- /images/PulseSensor_RasPi_MCP3008_fritz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WorldFamousElectronics/Raspberry_Pi/HEAD/images/PulseSensor_RasPi_MCP3008_fritz.png -------------------------------------------------------------------------------- /PulseSensor_C_Pi/Pulse_Sensor_gnuplot/Pulse_Plot_Config.gnu: -------------------------------------------------------------------------------- 1 | # GNUPLOT CONFIG FILE 2 | # You can use as-is or modify as needed 3 | 4 | unset log # remove any log-scaling 5 | unset title # remove any titles 6 | unset label # remove any previous labels 7 | set title "Pulse Sensor" 8 | set xlabel "Samples" 9 | set ylabel "Pulse Wave" 10 | #set key 0.01,100 11 | set xr [0:100] 12 | set yr [0:1024] 13 | -------------------------------------------------------------------------------- /PulseSensor_Processing_Pi/PulseSensor_Processing_Pi/keyboard_mouse.pde: -------------------------------------------------------------------------------- 1 | 2 | 3 | void mousePressed(){ 4 | } 5 | 6 | void mouseReleased(){ 7 | } 8 | 9 | void keyPressed(){ 10 | 11 | switch(key){ 12 | case 's': // pressing 's' or 'S' will take a jpg of the processing window 13 | case 'S': 14 | saveFrame("PulseSensor-####.jpg"); // take a shot of that! 15 | break; 16 | case 'r': 17 | case 'R': 18 | resetDataTrace(); 19 | break; 20 | 21 | default: 22 | break; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pulse Sensor + Raspberry Pi 2 | This is the home for example code that connects Pulse Sensor to Raspberry Pi 3 | # Raspberry Pi 3 Ways 4 | There are currently 3 examples. Check back for more soon! 5 | ### [Raspberry Pi + Arduino](https://github.com/WorldFamousElectronics/Raspberry_Pi/blob/master/PulseSensor_Arduino_Pi/PulseSensor_Arduino_Pi.md) 6 | Connect your Arduino to your Pi and start streaming Pulse Sensor data! 7 | ### [Raspberry Pi + Processing](https://github.com/WorldFamousElectronics/Raspberry_Pi/blob/master/PulseSensor_Processing_Pi/PulseSensor_Processing_Pi.md) 8 | Connect your Pulse Sensor to your Pi via ADC and visualize in Processing! 9 | ### [Raspberry Pi + C](https://github.com/WorldFamousElectronics/Raspberry_Pi/blob/master/PulseSensor_C_Pi/PulseSensor_C_Pi.md) 10 | C code that reads your Pulse Sensor via ADC! 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Pulse Sensor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PulseSensor_Processing_Pi/PulseSensor_Processing_Pi/getPulse.pde: -------------------------------------------------------------------------------- 1 | 2 | // Initialize (seed) the pulse detector 3 | void initPulseSensor(){ 4 | BPM = 0; 5 | IBI = 600; // 600ms per beat = 100 Beats Per Minute (BPM) 6 | Pulse = false; 7 | sampleCounter = 0; 8 | lastBeatTime = 0; 9 | P = 512; // peak at 1/2 the input range of 0..1023 10 | T = 512; // trough at 1/2 the input range. 11 | thresh = 550; // threshold a little above the trough 12 | amp = 100; // beat amplitude 1/10 of input range. 13 | firstBeat = true; // looking for the first beat 14 | secondBeat = false; // not yet looking for the second beat in a row 15 | } 16 | 17 | void getPulse(){ 18 | inBytes = MCP3008.transfer(outBytes); 19 | Signal = char(inBytes[1]) << 8; 20 | Signal |= char(inBytes[2]); 21 | sampleIntervalMs =int((1/frameRate)*1000.0); // 22 | sampleCounter += sampleIntervalMs; // keep track of the time in mS with this variable 23 | int N = int(sampleCounter - lastBeatTime); // monitor the time since the last beat to avoid noise 24 | 25 | // Fade the Fading LED 26 | 27 | // find the peak and trough of the pulse wave 28 | if (Signal < thresh && N > (IBI / 5) * 3) { // avoid dichrotic noise by waiting 3/5 of last IBI 29 | if (Signal < T) { // T is the trough 30 | T = Signal; // keep track of lowest point in pulse wave 31 | } 32 | } 33 | 34 | if (Signal > thresh && Signal > P) { // thresh condition helps avoid noise 35 | P = Signal; // P is the peak 36 | } // keep track of highest point in pulse wave 37 | 38 | // NOW IT'S TIME TO LOOK FOR THE HEART BEAT 39 | // signal surges up in value every time there is a pulse 40 | if (N > 250) { // avoid high frequency noise 41 | if ( (Signal > thresh) && (Pulse == false) && (N > (IBI / 5) * 3) ) { 42 | Pulse = true; // set the Pulse flag when we think there is a pulse 43 | IBI = int(sampleCounter - lastBeatTime); // measure time between beats in mS 44 | lastBeatTime = sampleCounter; // keep track of time for next pulse 45 | 46 | if (secondBeat) { // if this is the second beat, if secondBeat == TRUE 47 | secondBeat = false; // clear secondBeat flag 48 | } 49 | 50 | if (firstBeat) { // if it's the first time we found a beat, if firstBeat == TRUE 51 | firstBeat = false; // clear firstBeat flag 52 | secondBeat = true; // set the second beat flag 53 | // IBI value is unreliable so discard it 54 | return; 55 | } 56 | 57 | 58 | 59 | BPM = 60000 / IBI; // how many beats can fit into a minute? that's BPM! 60 | } 61 | } 62 | 63 | if (Signal < thresh && Pulse == true) { // when the values are going down, the beat is over 64 | Pulse = false; // reset the Pulse flag so we can do it again 65 | amp = P - T; // get amplitude of the pulse wave 66 | thresh = amp / 2 + T; // set thresh at 50% of the amplitude 67 | P = thresh; // reset these for next time 68 | T = thresh; 69 | } 70 | 71 | if (N > 2500) { // if 2.5 seconds go by without a beat 72 | thresh = threshSetting; // set thresh default 73 | P = 512; // set P default 74 | T = 512; // set T default 75 | lastBeatTime = sampleCounter; // bring the lastBeatTime up to date 76 | firstBeat = true; // set these to avoid noise 77 | secondBeat = false; // when we get the heartbeat back 78 | BPM = 0; 79 | IBI = 600; // 600ms per beat = 100 Beats Per Minute (BPM) 80 | Pulse = false; 81 | amp = 0; 82 | 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /PulseSensor_Processing_Pi/PulseSensor_Processing_Pi/PulseSensor_Processing_Pi.pde: -------------------------------------------------------------------------------- 1 | /* 2 | THIS PROGRAM IS WRITTEN TO TARGET RASPBERRY PI 3 | PRESS 'S' OR 's' KEY TO SAVE A PICTURE OF THE SCREEN IN SKETCH FOLDER (.jpg) 4 | PRESS 'R' OR 'r' KEY TO RESET THE DATA TRACES 5 | MADE BY JOEL MURPHY APRIL 2019 6 | 7 | THIS CODE PROVIDED AS IS, WITH NO CLAIMS OF FUNCTIONALITY OR EVEN IF IT WILL WORK 8 | WYSIWYG 9 | */ 10 | 11 | import processing.io.*; 12 | PFont font; 13 | SPI MCP3008; 14 | 15 | // VARIABLES USED TO GET PULSE 16 | long sampleCounter,lastBeatTime,jitter; // used to keep track of sample rate 17 | int threshSetting,thresh; // used to find the heart beat 18 | int Signal; // holds the latest raw sensor data 19 | int BPM; // holds the Beats Per Minute value 20 | int IBI; // holds the Interbeat Interval value 21 | int P,T,amp; // keeps track of the peak and trough in the pulse wave 22 | int sampleIntervalMs; // used to find the sample rate 23 | boolean firstBeat,secondBeat; // used to avoid noise on startup 24 | boolean Pulse; // is true when pulse wave is above thresh 25 | int pulseLED = 17; 26 | // VARIABLES TO TALK TO MCP3008 27 | byte[] outBytes = {(byte)0x01,(byte)0x80,(byte)0x00}; // tell MCP we want to read channel 0 28 | byte[] inBytes = new byte[3]; // used to hold the incoming data from MCP 29 | int MCP_SS = 8; // the chip select pin number 30 | // VARIABLES FOR VISUALIZER 31 | int[] RawY; // HOLDS HEARTBEAT WAVEFORM DATA 32 | color eggshell = color(255, 253, 248); // background color 33 | // THESE VARIABLES DETERMINE THE SIZE OF THE DATA WINDOWS 34 | int PulseWindowWidth; 35 | int PulseWindowHeight; 36 | int windowXmargin = 10; 37 | int windowYmargin = 45; 38 | 39 | 40 | 41 | void setup(){ 42 | size(600, 600); // Stage size 43 | frameRate(100); // ask for a fast frame rate 44 | GPIO.pinMode(MCP_SS,GPIO.OUTPUT); 45 | GPIO.pinMode(pulseLED,GPIO.OUTPUT); 46 | //printArray(SPI.list()); 47 | MCP3008 = new SPI(SPI.list()[0]); // use SPI_0 48 | MCP3008.settings(1000000, SPI.MSBFIRST, SPI.MODE3); 49 | font = loadFont("FreeSansBold-48.vlw"); 50 | textFont(font,24); 51 | textAlign(CENTER); 52 | rectMode(CENTER); 53 | ellipseMode(CENTER); 54 | PulseWindowWidth = width-windowXmargin*2; 55 | PulseWindowHeight = height-windowYmargin*2; 56 | RawY = new int[PulseWindowWidth]; // initialize raw pulse waveform array 57 | resetDataTrace(); // set the visualizer line to 0 58 | 59 | background(0); 60 | fill(eggshell); 61 | drawDataWindow(); // draw the pulse window 62 | initPulseSensor(); // initialize the Pulse Sensor variables 63 | threshSetting = 550; // adjust this as needed to avoid noise 64 | } 65 | 66 | 67 | void draw(){ 68 | getPulse(); // sample the Pulse Sensor and try to find a beat 69 | //printRawValues(); // used for debugging 70 | outputLED(); // blink the LED when we find a pulse 71 | background(0); 72 | noStroke(); 73 | drawDataWindow(); // draw the pulse window 74 | drawPulseWaveform(); // draw the pulse wave 75 | // PRINT THE DATA AND VARIABLE VALUES 76 | fill(eggshell); // get ready to print text 77 | text("Pulse Sensor Raspberry Pi",width/2,windowYmargin/2 + (textDescent()+textAscent())/2); // tell them what you are 78 | text(BPM + " BPM IBI: " + IBI + "mS Rate: " + sampleIntervalMs + "mS", 79 | width/2, height-windowYmargin/2+(textDescent()+textAscent())/2); // print BPM, IBI, and sample rate 80 | 81 | } 82 | 83 | void drawDataWindow(){ 84 | // DRAW OUT THE PULSE WINDOW AND BPM WINDOW RECTANGLES 85 | noStroke(); 86 | fill(eggshell); // color for the window background 87 | rect(width/2,height/2,PulseWindowWidth,PulseWindowHeight); 88 | } 89 | 90 | // DRAW THE PULSE WAVE 91 | void drawPulseWaveform(){ 92 | // DRAW THE PULSE WAVEFORM 93 | // prepare pulse data points 94 | int dummy = (1023 - Signal) - 512; // center the waveform around 0 95 | RawY[RawY.length-1] = int(map(dummy,511,-512,PulseWindowHeight+windowYmargin,windowYmargin)); // place the new raw datapoint at the end of the array 96 | for (int i = 0; i < RawY.length-1; i++) { // move the pulse waveform by 97 | RawY[i] = RawY[i+1]; // shifting all raw datapoints one pixel left 98 | } 99 | stroke(250,0,0); // red is a good color for the pulse waveform 100 | noFill(); 101 | beginShape(); // using beginShape() renders fast 102 | for (int x = 1; x < RawY.length-1; x++) { 103 | vertex(x+10, RawY[x]); //+height/2); //draw a line connecting the data points 104 | } 105 | endShape(); 106 | } 107 | 108 | // SET THE PULSE WAVE TO 0 109 | void resetDataTrace(){ 110 | for (int i=0; i` and press either the space bar or Enter/Return. 22 | 23 | ![InterfaceOptions](../images/InterfacingOptions.png) 24 | 25 | In the next pane, arrow down to the SPI option and enable it. 26 | 27 | ![EnableSerial](../images/EnableSPI.png) 28 | 29 | Pi may ask you to reboot, so go ahead and do that, otherwise arrow your way to `` and get out of the config menu. 30 | 31 | ## Connect your Pulse Sensor to Raspberry Pi 32 | If this is your first time using Pulse Sensor, check out our [handy guide](https://docs.google.com/document/d/1d8EwDcXH1AZpIpEnrET28EBgStrbkbppxjQZcNRAlkI/edit?usp=sharing) for getting your Pulse Sensor set up to read beats. We also have lots of [tutorials](https://pulsesensor.com/). 33 | 34 | 35 | The Pulse Sensor outputs an analog signal, which the RasPi cannot natively handle. In the examples below, we are using an Analog to Digital Converter (ADC) IC to digitize the Pulse Sensor signal. As of this writing, the code below supports the MCP3008 ADC IC. (You could use the MCP3004 if you like). The Wiring Pi library has functions that enable us to easily access data from the MCP3008. You can pick one up at [Mouser](https://www.mouser.com/ProductDetail/Microchip-Technology/MCP3008-I-SL?qs=BYQkrObauiuZK6Atf%2FfReA%3D%3D&gclid=CjwKCAjwhbHlBRAMEiwAoDA343G0yGlECsWZ5zo-5UbrMk58sLaK11XtHWNU8w9fzKlpIiY343y0YBoCrBgQAvD_BwE) or [SparkFun](https://www.sparkfun.com/products/15099) or [Adafruit](https://www.adafruit.com/product/856). 36 | 37 | The RasPi interfaces the MCP3008 via the hardware SPI pins. Here's a diagram to help you assemble the circuit. 38 | 39 | ![Fritzing Diagram](../images/PulseSensor_RasPi_MCP3008_fritz.png) 40 | 41 | In addition to the MCP3008, you will need a [breadboard](https://www.adafruit.com/product/64) and some [male/female jumper cables](https://www.adafruit.com/product/826). We want to blink an LED, so you should have one of those handy, along with a resistor (220ohm to 1K should work fine). 42 | 43 | ## Pulse Sensor Sample Timing 44 | In order to get accurate BPM data from the Pulse Sensor, it is important to have fast and regular reading of the Pulse Sensor analog signal. By fast, we mean 500Hz (1 sample every 2 milliseconds). By regular, we mean 1 sample every 2 milliseconds. Period. Not 2.5 milliseconds, not 1.8 milliseconds, not whenever the OS decides to get around to it. This is much easier to do on a microcontroller where you are not running on top of an operating system. Arduino, for example, has no problem setting its hardware timer to sample data every 2 milliseconds on the dot. But we're not in Arduino, we're in RasPi, and things are different. Since we're using Processing, we have to rely on the frame rate to time our samples in the draw() loop. Depending on whatever else your RasPi is doing, and how much work Processing is doing, your sample rate may vary a great deal. Our example sketch will give you feedback on sample timing rate and accuracy. 45 | 46 | ## Set Up Processing 47 | If you don't already have Processing on your RasPi, you can download it from [Processing](https://pi.processing.org/download/). That link will take you to the page that tells you all about installing Processing on your Pi. 48 | 49 | Processing keeps your code in a sketch folder. Normally this folder is 50 | 51 | /home/pi/Documents/Processing 52 | 53 | After you download this repository, move the folder called `PulseSensor_Processing_Pi' and all of its contents into the Processing sketch folder. Then open Processing and go to 54 | 55 | File > Sketchbook... 56 | 57 | ![Sketchbook](../images/ProcessingSketchbook.png) 58 | 59 | When you run the sketch, you will see a window much like this one 60 | 61 | ![PulseSensorPi](../images/ProcessingPiPulseWave.png) 62 | 63 | The red wave plot is the raw data from your Pulse Sensor. At the bottom, there is some pulse data and process data. The **BPM** value is calculated every time Processing finds a beat. The **IBI** value is also updated every time there is a beat. **IBI** stands for Interbeat Interval, and it is the amount of time between each heartbeat. The **Rate** is the time between each sample. Since Processing can only sample each time it goes through the draw() loop, this is based on the `frameRate` variable provided by Processing. This should give you some indication as to how accurate your BPM values are. 64 | 65 | ### Other features of the sketch: 66 | 67 | Press 's' or 'S' to take a screenshot of the program window. It will save in the sketch folder with the name PulseSensor.jpg 68 | 69 | Press 'r' or 'R' to reset the pulse wave trace -------------------------------------------------------------------------------- /PulseSensor_Arduino_Pi/PulseSensor_Arduino_Pi.md: -------------------------------------------------------------------------------- 1 | # Pulse Sensor + Arduino + Pi 2 | Connect Pulse Sensor to Raspberry Pi with Arduino! 3 | 4 | ## Things you'll need 5 | 6 | * Pulse Sensor 7 | * Raspberry Pi (we use a RasPi 3 B) 8 | * Arduino compatible microcontroller board 9 | * USB cable 10 | 11 | There are many was to set up your Pi. We used [Adafruit's](https://learn.adafruit.com/series/learn-raspberry-pi) tutorial to get set up. Once you have the Pi OS up and running, you will want to make sure that your configuration settings allow us to connect the Arduino. In the GUI, select `Raspberry Pi Configuration`, then open the `Interfaces` tab and enable Serial Port. This process may require you to restart your Pi. 12 | 13 | ![Conf_Window_Serial_Enable](../images/PiConfigWindowSerial.png) 14 | 15 | To do this on the command line, you need to edit your config file. Open a terminal window and type in 16 | 17 | sudo raspi-config 18 | 19 | This will open up a configuration panel. Use the arrow keys to move down to `Interfacing Options` then press the right arrow to highlight `` and press either the space bar or Enter/Return. 24 | 25 | ![InterfaceOptions](../images/InterfacingOptions.png) 26 | 27 | In the next pane, arrow down to the SPI option and enable it. 28 | 29 | ![EnableSerial](../images/EnableSPI.png) 30 | 31 | Pi may ask you to reboot, so go ahead and do that, otherwise arrow your way to `` and get out of the config menu. 32 | 33 | 34 | ## Connect your Pulse Sensor to Raspberry Pi 35 | If this is your first time using Pulse Sensor, check out our [handy guide](https://docs.google.com/document/d/1d8EwDcXH1AZpIpEnrET28EBgStrbkbppxjQZcNRAlkI/edit?usp=sharing) for getting your Pulse Sensor set up to read beats. We also have lots of [tutorials](https://pulsesensor.com/). 36 | 37 | The Pulse Sensor outputs an analog signal, which the RasPi cannot natively handle. In the examples below, we are using an Analog to Digital Converter (ADC) IC to digitize the Pulse Sensor signal. As of this writing, the code below supports the MCP3008 ADC IC. (You could use the MCP3004 if you like). The Wiring Pi library has functions that enable us to easily access data from the MCP3008. You can pick one up at [Mouser](https://www.mouser.com/ProductDetail/Microchip-Technology/MCP3008-I-SL?qs=BYQkrObauiuZK6Atf%2FfReA%3D%3D&gclid=CjwKCAjwhbHlBRAMEiwAoDA343G0yGlECsWZ5zo-5UbrMk58sLaK11XtHWNU8w9fzKlpIiY343y0YBoCrBgQAvD_BwE) or [SparkFun](https://www.sparkfun.com/products/15099) or [Adafruit](https://www.adafruit.com/product/856). 38 | 39 | The RasPi interfaces the MCP3008 via the hardware SPI pins. Here's a diagram to help you assemble the circuit. 40 | 41 | ![Fritzing Diagram](../images/PulseSensor_RasPi_MCP3008_fritz.png) 42 | 43 | In addition to the MCP3008, you will need a [breadboard](https://www.adafruit.com/product/64) and some [male/female jumper cables](https://www.adafruit.com/product/826). We want to blink an LED, so you should have one of those handy, along with a resistor (220ohm to 1K should work fine). 44 | 45 | 46 | # Pulse Sensor Timer 47 | In order to get accurate BPM data from the Pulse Sensor, it is important to have fast and regular reading of the Pulse Sensor analog signal. By fast, we mean 500Hz (1 sample every 2 milliseconds). By regular, we mean 1 sample every 2 milliseconds. Period. Not 2.5 milliseconds, not 1.8 milliseconds, not whenever the OS decides to get around to it. This is much easier to do on a microcontroller where you are not running on top of an operating system. Arduino, for example, has no problem setting its hardware timer to sample data every 2 milliseconds on the dot. But we're not in Arduino, we're in RasPi, and things are different. 48 | 49 | In an effort to get 'as close to the metal' as we can on the RasPi, we are using an alarm timer in this code to trigger a regular interrupt. Even though we are using a hardware clock to interrupt us, we still will get some jitter from the OS not releasing at a predictable time. 50 | 51 | 52 | To use this code, make a folder called `PulseSensor` and put it in your `/home/pi/Documents/` folder and place the file `PulseSensor_timer.c` in it. We're doing this because the program will store data while it's running in `/home/pi/Documents/PulseSensor`. The file is called `PULSE_DATA_.dat` and ends with a time stamp. 53 | 54 | Compile the C code by typing 55 | 56 | gcc pulseTimer PulseSensor_timer.c -lwiringPi 57 | 58 | into a terminal window that is open in the PulseSensor folder. Then type 59 | 60 | ./pulseTimer 61 | 62 | to launch the program. Aside from blinking the LED to your heartbeat, this code will print data to the terminal window, and write the same data to file. The data is tab separated and arranged thusly: 63 | 64 | sampleCount | Signal | BPM | IBI | Jitter 65 | ------------- | ------ | --- | --- | ------ 66 | 67 | * **sampleCount:** pretty straightforward. A counter that increases with every sample 68 | * **Signal:** is the raw value from the ADC, which ranges from 0 - 1023 69 | * **BPM:** Beats Per Minute. That's heart rate! 70 | * **IBI:** Interbeat Interval. The time between beats in milliseconds 71 | * **Jitter:** amount of time in milliseconds that our reading is off from the 2mS target 72 | 73 | Here's a sample of what the data looks like in the terminal 74 | 75 | ![PulseSensor_timer terminal data](../images/PulseSensor_timer_term.png) 76 | 77 | ### TO DO: 78 | Build option to read from file 79 | 80 | We are using the `ualarm()` function call which has been deprecated. Future versions of this code will use the recommended replacement. 81 | 82 | # Pulse Sensor gnuplot 83 | Well, it's great and all to have tightly timed samples, but we *really* want to see the pulse data graphed live while it's running. With the Raspberry Pi this is possible, but again, since we're riding on an operating system things can get a little glitchy. For this C code example, we are going to use the `delayMicroseconds()` function from the Wiring Pi library to create a software timer for timing our samples. This program will open a pipe to Gnuplot, a light weight data plotting program, to do the visualization. In Terminal, navigate to the folder where you put the C code and type in 84 | 85 | gcc gnuplotPulse PulseSensor_gnuplot.c -lwiringPi 86 | 87 | Then type `./gnuplotPulse` to launch the program. Here's an example of what the plot looks like. 88 | 89 | ![gnuplotPulse](../images/gnuplotPulse.png) 90 | 91 | This program will write a data file on your RasPi in `/home/pi/Documents/PulseSensor/PulseSensor_gnuplot/PulseData/` with the name `PulsePlot_.dat` in the same format as the PulseSensor Timer code above: 92 | 93 | sampleCount | Signal | BPM | IBI | Jitter 94 | ------------- | ------ | --- | --- | ------ 95 | 96 | Gnuplot is a pretty awesome graphing software, and we're super psyched that it can be tricked into live-plotting in real time over a pipe! 97 | 98 | ### TO DO: 99 | Build option to read from file. 100 | 101 | Print out the BPM on the Gnuplot window (line 100, collumn 3). 102 | 103 | Print out the jitter on the Gnuplot window. 104 | 105 | Command line option to graph the jitter.. 106 | 107 | Dynamically adjust the `delayMicroseconds()` time value to reduce jitter. 108 | -------------------------------------------------------------------------------- /PulseSensor_C_Pi/Pulse_Sensor_gnuplot/PulseSensor_gnuplot.c: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | THIS CODE IS RELEASED WITHOUT WARRANTY OF FITNESS 4 | OR ANY PROMISE THAT IT WORKS, EVEN. WYSIWYG. 5 | 6 | YOU SHOULD HAVE RECEIVED A LICENSE FROM THE MAIN 7 | BRANCH OF THIS REPO. IF NOT, IT IS USING THE 8 | MIT FLAVOR OF LICENSE 9 | 10 | */ 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | // PULSE SENSOR LEDS 25 | #define BLINK_LED 0 26 | //#define FADE_LED // NEEDS THIS FEATURE ADDED 27 | 28 | // MCP3004/8 SETTINGS 29 | #define BASE 100 30 | #define SPI_CHAN 0 31 | #define PULSE_SIGNAL_MAX 1023 32 | #define PULSE_SIGNAL_MIN 0 33 | #define PULSE_SIGNAL_IDLE 512 34 | #define JITTER_SIGNAL_IDLE 0 35 | #define DATA_POINTS 100 36 | 37 | 38 | // VARIABLES FOR GNUPLOT 39 | char * plotDataFilename = "/home/pi/Documents/PulseSensor/PulseSensor_gnuplot/PlotData.dat"; 40 | FILE * plotDataFile; 41 | int plotData[DATA_POINTS]; 42 | // VARIABLES USED TO DETERMINE SAMPLE JITTER & TIME OUT 43 | volatile unsigned int eventCounter, thisTime, lastTime, elapsedTime; 44 | volatile int sumJitter,jitter; 45 | unsigned int timeOutStart, dataRequestStart, m; 46 | // VARIABLES USED TO DETERMINE BPM 47 | volatile int Signal; 48 | volatile unsigned int sampleCounter; 49 | volatile int threshSetting,lastBeatTime,fadeLevel; 50 | volatile int thresh = 550; // SHOULD BE ADJUSTABLE ON COMMAND LINE 51 | volatile int P = 512; // set P default 52 | volatile int T = 512; // set T default 53 | volatile int firstBeat = 1; // set these to avoid noise 54 | volatile int secondBeat = 0; // when we get the heartbeat back 55 | volatile int QS = 0; 56 | volatile int rate[10]; 57 | volatile int BPM = 0; 58 | volatile int IBI = 600; // 600ms per beat = 100 Beats Per Minute (BPM) 59 | volatile int Pulse = 0; // set to 1 while Signal > Threshold 60 | volatile int amp = 100; // beat amplitude 1/10 of input range. 61 | // FILE STUFF 62 | char rawDatafilename [100]; 63 | FILE * pulseData; 64 | struct tm *timenow; 65 | // FUNCTION PROTOTYPES 66 | void getPulse(); 67 | void stopTimer(void); 68 | void initPulseSensorVariables(void); 69 | void initJitterVariables(void); 70 | void initPlotVariables(int idle); 71 | void loadNewPlotData(void); 72 | void incrementPlotData(void); 73 | 74 | 75 | void usage() 76 | { 77 | fprintf 78 | (stderr, 79 | "\n" \ 80 | "Usage: ./gnuplotPulse ... [OPTION] ...\n" \ 81 | " NO OPTIONS AVAILABLE YET\n"\ 82 | "\n"\ 83 | " Data file saved as\n"\ 84 | " /PulseData/PulsePlot_.dat\n"\ 85 | " Data format tab separated:\n"\ 86 | " sampleCount Signal BPM IBI Pulse Jitter\n"\ 87 | "\n"\ 88 | " Closing Program with ^C in this window will kill gnuplot\n"\ 89 | "\n" 90 | ); 91 | } 92 | 93 | void sigHandler(int sig_num){ 94 | printf("\nkilling gnuplot and exiting\n"); 95 | system("pkill gnuplot"); 96 | exit(EXIT_SUCCESS); 97 | } 98 | 99 | void fatal(int show_usage, char *fmt, ...) 100 | { 101 | char buf[128]; 102 | va_list ap; 103 | char kill[20]; 104 | 105 | va_start(ap, fmt); 106 | vsnprintf(buf, sizeof(buf), fmt, ap); 107 | va_end(ap); 108 | 109 | fprintf(stderr, "%s\n", buf); 110 | 111 | if (show_usage) usage(); 112 | 113 | fflush(stderr); 114 | printf("\nkilling gnuplot and exiting\n"); 115 | system("pkill gnuplot"); 116 | fprintf(pulseData,"#%s",fmt); 117 | fclose(pulseData); 118 | 119 | exit(EXIT_FAILURE); 120 | } 121 | 122 | // SAVED FOR FUTURE FEATURES 123 | static int initOpts(int argc, char *argv[]) 124 | { 125 | //int i, opt; 126 | //while ((opt = getopt(argc, argv, ":")) != -1) 127 | //{ 128 | //i = -1; 129 | //switch (opt) 130 | //{ 131 | //case '': 132 | //default: /* '?' */ 133 | //usage(); 134 | //} 135 | //} 136 | return optind; 137 | } 138 | 139 | 140 | int main(int argc, char *argv[]) 141 | { 142 | signal(SIGINT,sigHandler); 143 | usage(); 144 | //int settings = 0; 145 | // command line settings 146 | //settings = initOpts(argc, argv); 147 | time_t now = time(NULL); 148 | timenow = gmtime(&now); 149 | 150 | strftime(rawDatafilename, sizeof(rawDatafilename), 151 | "/home/pi/Documents/PulseSensor/PulseSensor_gnuplot/PulseData/PulsePlot_%Y-%m-%d_%H:%M:%S.dat", timenow); 152 | pulseData = fopen(rawDatafilename, "w+"); 153 | fprintf(pulseData,"#Running with delayMicroseconds Timer\n"); 154 | fprintf(pulseData,"#sampleCount\tSignal\tBPM\tIBI\tjitter\n"); 155 | 156 | // open gnupolot with a FIFO 157 | printf("\nOpening gnuplotPipe\n"); 158 | FILE * gnuplotPipe = popen("gnuplot -persistent", "w"); 159 | // tell gnuplot to read a config file 160 | fprintf(gnuplotPipe,"load 'Pulse_Plot_Config.gnu'\n"); 161 | printf("Starting wiringPi and initializeing variables\n"); 162 | 163 | wiringPiSetup(); //use the wiringPi pin numbers 164 | mcp3004Setup(BASE,SPI_CHAN); // setup the mcp3004 library 165 | pinMode(BLINK_LED, OUTPUT); digitalWrite(BLINK_LED,LOW); 166 | initPulseSensorVariables(); // initilaize Pulse Sensor beat finder 167 | initPlotVariables(PULSE_SIGNAL_IDLE); // initialize plot values 168 | printf("here we go\n"); 169 | 170 | while(1) 171 | { 172 | delayMicroseconds(1800); // DELAY TIME NEEDS TO BE ATOMATED 173 | getPulse(); 174 | fprintf(pulseData,"%d\t%d\t%d\t%d\t%d\n", 175 | sampleCounter,Signal,BPM,IBI,jitter 176 | ); 177 | //printf("%d\t%d\t%d\t%d\t%d\n", 178 | //sampleCounter,Signal,IBI,BPM,jitter 179 | //); 180 | 181 | if(sampleCounter%40 == 0){ 182 | incrementPlotData(); 183 | loadNewPlotData(); 184 | fprintf(gnuplotPipe,"plot \"PlotData.dat\" with lines lt rgb \"red\"\n"); 185 | fflush(gnuplotPipe); 186 | //int writeTime = micros() - timeOutStart; 187 | //if(writeTime > 2000){ 188 | //printf("%d\n",writeTime); 189 | //} 190 | } 191 | } 192 | 193 | return 0; 194 | 195 | }//int main(int argc, char *argv[]) 196 | 197 | 198 | void initPulseSensorVariables(void){ 199 | for (int i = 0; i < 10; ++i) { 200 | rate[i] = 0; 201 | } 202 | QS = 0; 203 | BPM = 0; 204 | IBI = 600; // 600ms per beat = 100 Beats Per Minute (BPM) 205 | Pulse = 0; 206 | sampleCounter = 0; 207 | lastBeatTime = 0; 208 | P = 512; // peak at 1/2 the input range of 0..1023 209 | T = 512; // trough at 1/2 the input range. 210 | threshSetting = 550; // used to seed and reset the thresh variable 211 | thresh = 550; // threshold a little above the trough 212 | amp = 100; // beat amplitude 1/10 of input range. 213 | firstBeat = 1; // looking for the first beat 214 | secondBeat = 0; // not yet looking for the second beat in a row 215 | lastTime = micros(); 216 | } 217 | 218 | void getPulse(){ //int sig_num){ 219 | 220 | thisTime = micros(); 221 | Signal = analogRead(BASE); 222 | elapsedTime = thisTime - lastTime; 223 | lastTime = thisTime; 224 | jitter = elapsedTime - 2000; 225 | sumJitter += jitter; 226 | 227 | 228 | sampleCounter += 2; // keep track of the time in mS with this variable 229 | int N = sampleCounter - lastBeatTime; // monitor the time since the last beat to avoid noise 230 | 231 | // find the peak and trough of the pulse wave 232 | if (Signal < thresh && N > (IBI / 5) * 3) { // avoid dichrotic noise by waiting 3/5 of last IBI 233 | if (Signal < T) { // T is the trough 234 | T = Signal; // keep track of lowest point in pulse wave 235 | } 236 | } 237 | 238 | if (Signal > thresh && Signal > P) { // thresh condition helps avoid noise 239 | P = Signal; // P is the peak 240 | } // keep track of highest point in pulse wave 241 | 242 | // NOW IT'S TIME TO LOOK FOR THE HEART BEAT 243 | // signal surges up in value every time there is a pulse 244 | if (N > 250) { // avoid high frequency noise 245 | if ( (Signal > thresh) && (Pulse == 0) && (N > ((IBI / 5) * 3)) ) { 246 | Pulse = 1; // set the Pulse flag when we think there is a pulse 247 | IBI = sampleCounter - lastBeatTime; // measure time between beats in mS 248 | lastBeatTime = sampleCounter; // keep track of time for next pulse 249 | 250 | if (secondBeat) { // if this is the second beat, if secondBeat == TRUE 251 | secondBeat = 0; // clear secondBeat flag 252 | for (int i = 0; i <= 9; i++) { // seed the running total to get a realisitic BPM at startup 253 | rate[i] = IBI; 254 | } 255 | } 256 | 257 | if (firstBeat) { // if it's the first time we found a beat, if firstBeat == TRUE 258 | firstBeat = 0; // clear firstBeat flag 259 | secondBeat = 1; // set the second beat flag 260 | // IBI value is unreliable so discard it 261 | return; 262 | } 263 | 264 | 265 | // keep a running total of the last 10 IBI values 266 | int runningTotal = 0; // clear the runningTotal variable 267 | 268 | for (int i = 0; i <= 8; i++) { // shift data in the rate array 269 | rate[i] = rate[i + 1]; // and drop the oldest IBI value 270 | runningTotal += rate[i]; // add up the 9 oldest IBI values 271 | } 272 | 273 | rate[9] = IBI; // add the latest IBI to the rate array 274 | runningTotal += rate[9]; // add the latest IBI to runningTotal 275 | runningTotal /= 10; // average the last 10 IBI values 276 | BPM = 60000 / runningTotal; // how many beats can fit into a minute? that's BPM! 277 | QS = 1; // set Quantified Self flag (we detected a beat) 278 | //fadeLevel = MAX_FADE_LEVEL; // If we're fading, re-light that LED. 279 | } 280 | } 281 | 282 | if (Signal < thresh && Pulse == 1) { // when the values are going down, the beat is over 283 | Pulse = 0; // reset the Pulse flag so we can do it again 284 | amp = P - T; // get amplitude of the pulse wave 285 | thresh = amp / 2 + T; // set thresh at 50% of the amplitude 286 | P = thresh; // reset these for next time 287 | T = thresh; 288 | } 289 | 290 | if (N > 2500) { // if 2.5 seconds go by without a beat 291 | thresh = threshSetting; // set thresh default 292 | P = 512; // set P default 293 | T = 512; // set T default 294 | lastBeatTime = sampleCounter; // bring the lastBeatTime up to date 295 | firstBeat = 1; // set these to avoid noise 296 | secondBeat = 0; // when we get the heartbeat back 297 | QS = 0; 298 | BPM = 0; 299 | IBI = 600; // 600ms per beat = 100 Beats Per Minute (BPM) 300 | Pulse = 0; 301 | amp = 100; // beat amplitude 1/10 of input range. 302 | 303 | } 304 | 305 | 306 | } 307 | 308 | void initPlotVariables(int idle){ 309 | for(int i=0; i 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | 25 | #define OPT_R 10 // min uS allowed lag btw alarm and callback 26 | #define OPT_U 2000 // sample time uS between alarms 27 | #define OPT_O_ELAPSED 0 // output option uS elapsed time between alarms 28 | #define OPT_O_JITTER 1 // output option uS jitter (elapsed time - sample time) 29 | #define OPT_O 1 // defaoult output option 30 | #define OPT_C 10000 // number of samples to run (testing) 31 | #define OPT_N 1 // number of Pulse Sensors (only 1 supported) 32 | 33 | #define TIME_OUT 30000000 // uS time allowed without callback response 34 | // PULSE SENSOR LEDS 35 | #define BLINK_LED 0 36 | // MCP3004/8 SETTINGS 37 | #define BASE 100 38 | #define SPI_CHAN 0 39 | 40 | // FIFO STUFF 41 | #define PULSE_EXIT 0 // CLEAN UP AND SHUT DOWN 42 | #define PULSE_IDLE 1 // STOP SAMPLING, STAND BY 43 | #define PULSE_ON 2 // START SAMPLING, WRITE DATA TO FILE 44 | #define PULSE_DATA 3 // SEND DATA PACKET TO FIFO 45 | #define PULSE_CONNECT 9 // CONNECT TO OTHER END OF PIPE 46 | 47 | // VARIABLES USED TO DETERMINE SAMPLE JITTER & TIME OUT 48 | volatile unsigned int eventCounter, thisTime, lastTime, elapsedTime, jitter; 49 | volatile int sampleFlag = 0; 50 | volatile int sumJitter, firstTime, secondTime, duration; 51 | unsigned int timeOutStart, dataRequestStart, m; 52 | // VARIABLES USED TO DETERMINE BPM 53 | volatile int Signal; 54 | volatile unsigned int sampleCounter; 55 | volatile int threshSetting,lastBeatTime,fadeLevel; 56 | volatile int thresh = 550; 57 | volatile int P = 512; // set P default 58 | volatile int T = 512; // set T default 59 | volatile int firstBeat = 1; // set these to avoid noise 60 | volatile int secondBeat = 0; // when we get the heartbeat back 61 | volatile int QS = 0; 62 | volatile int rate[10]; 63 | volatile int BPM = 0; 64 | volatile int IBI = 600; // 600ms per beat = 100 Beats Per Minute (BPM) 65 | volatile int Pulse = 0; 66 | volatile int amp = 100; // beat amplitude 1/10 of input range. 67 | // LED CONTROL 68 | volatile int fadeLevel = 0; 69 | // FILE STUFF 70 | char filename [100]; 71 | struct tm *timenow; 72 | // FUNCTION PROTOTYPES 73 | void getPulse(int sig_num); 74 | void startTimer(int r, unsigned int u); 75 | void stopTimer(void); 76 | void initPulseSensorVariables(void); 77 | void initJitterVariables(void); 78 | 79 | FILE *data; 80 | 81 | void usage() 82 | { 83 | fprintf 84 | (stderr, 85 | "\n" \ 86 | "Usage: sudo ./pulseProto ... [OPTION] ...\n" \ 87 | " NO OPTIONS AVAILABLE YET\n"\ 88 | "\n"\ 89 | " Data file saved as\n"\ 90 | " /home/pi/Documents/PulseSensor/PULSE_DATA \n"\ 91 | " Data format tab separated:\n"\ 92 | " sampleCount Signal BPM IBI Pulse Jitter\n"\ 93 | "\n" 94 | ); 95 | } 96 | 97 | void sigHandler(int sig_num){ 98 | printf("\nkilling timer\n"); 99 | startTimer(OPT_R,0); // kill the alarm 100 | exit(EXIT_SUCCESS); 101 | } 102 | 103 | void fatal(int show_usage, char *fmt, ...) 104 | { 105 | char buf[128]; 106 | va_list ap; 107 | char kill[20]; 108 | 109 | va_start(ap, fmt); 110 | vsnprintf(buf, sizeof(buf), fmt, ap); 111 | va_end(ap); 112 | 113 | fprintf(stderr, "%s\n", buf); 114 | 115 | if (show_usage) usage(); 116 | 117 | fflush(stderr); 118 | printf("killing timer\n"); 119 | startTimer(OPT_R,0); // kill the alarm 120 | fprintf(data,"#%s",fmt); 121 | fclose(data); 122 | 123 | exit(EXIT_FAILURE); 124 | } 125 | 126 | // SAVED FOR FUTURE FEATURES 127 | static int initOpts(int argc, char *argv[]) 128 | { 129 | //int i, opt; 130 | //while ((opt = getopt(argc, argv, ":")) != -1) 131 | //{ 132 | //i = -1; 133 | //switch (opt) 134 | //{ 135 | //case '': 136 | //default: /* '?' */ 137 | //usage(); 138 | //} 139 | //} 140 | return optind; 141 | } 142 | 143 | 144 | int main(int argc, char *argv[]) 145 | { 146 | signal(SIGINT,sigHandler); 147 | //int settings = 0; 148 | // command line settings 149 | //settings = initOpts(argc, argv); 150 | time_t now = time(NULL); 151 | timenow = gmtime(&now); 152 | 153 | strftime(filename, sizeof(filename), 154 | "/home/pi/Documents/PulseSensor/PULSE_DATA_%Y-%m-%d_%H:%M:%S.dat", timenow); 155 | data = fopen(filename, "w+"); 156 | fprintf(data,"#Running with %d latency at %duS sample rate\n",OPT_R,OPT_U); 157 | fprintf(data,"#sampleCount\tSignal\tBPM\tIBI\tjitter\n"); 158 | 159 | printf("Ready to run with %d latency at %duS sample rate\n",OPT_R,OPT_U); 160 | 161 | wiringPiSetup(); //use the wiringPi pin numbers 162 | //piHiPri(99); 163 | mcp3004Setup(BASE,SPI_CHAN); // setup the mcp3004 library 164 | pinMode(BLINK_LED, OUTPUT); digitalWrite(BLINK_LED,LOW); 165 | 166 | initPulseSensorVariables(); // initilaize Pulse Sensor beat finder 167 | 168 | startTimer(OPT_R, OPT_U); // start sampling 169 | 170 | 171 | while(1) 172 | { 173 | if(sampleFlag){ 174 | sampleFlag = 0; 175 | timeOutStart = micros(); 176 | digitalWrite(BLINK_LED,Pulse); 177 | // PRINT DATA TO TERMINAL 178 | printf("%lu\t%d\t%d\t%d\t%d\n", 179 | sampleCounter,Signal,BPM,IBI,jitter 180 | ); 181 | // PRINT DATA TO FILE 182 | fprintf(data,"%d\t%d\t%d\t%d\t%d\t%d\n", 183 | sampleCounter,Signal,IBI,BPM,jitter,duration 184 | ); 185 | } 186 | if((micros() - timeOutStart)>TIME_OUT){ 187 | fatal(0,"0-program timed out",0); 188 | } 189 | } 190 | 191 | return 0; 192 | 193 | }//int main(int argc, char *argv[]) 194 | 195 | void startTimer(int r, unsigned int u){ 196 | int latency = r; 197 | unsigned int micros = u; 198 | 199 | signal(SIGALRM, getPulse); 200 | int err = ualarm(latency, micros); 201 | if(err == 0){ 202 | if(micros > 0){ 203 | printf("ualarm ON\n"); 204 | }else{ 205 | printf("ualarm OFF\n"); 206 | } 207 | } 208 | 209 | } 210 | 211 | void initPulseSensorVariables(void){ 212 | for (int i = 0; i < 10; ++i) { 213 | rate[i] = 0; 214 | } 215 | QS = 0; 216 | BPM = 0; 217 | IBI = 600; // 600ms per beat = 100 Beats Per Minute (BPM) 218 | Pulse = 0; 219 | sampleCounter = 0; 220 | lastBeatTime = 0; 221 | P = 512; // peak at 1/2 the input range of 0..1023 222 | T = 512; // trough at 1/2 the input range. 223 | threshSetting = 550; // used to seed and reset the thresh variable 224 | thresh = 550; // threshold a little above the trough 225 | amp = 100; // beat amplitude 1/10 of input range. 226 | firstBeat = 1; // looking for the first beat 227 | secondBeat = 0; // not yet looking for the second beat in a row 228 | lastTime = micros(); 229 | timeOutStart = lastTime; 230 | } 231 | 232 | void getPulse(int sig_num){ 233 | 234 | if(sig_num == SIGALRM) 235 | { 236 | thisTime = micros(); 237 | Signal = analogRead(BASE); 238 | elapsedTime = thisTime - lastTime; 239 | lastTime = thisTime; 240 | jitter = elapsedTime - OPT_U; 241 | sumJitter += jitter; 242 | sampleFlag = 1; 243 | 244 | 245 | sampleCounter += 2; // keep track of the time in mS with this variable 246 | int N = sampleCounter - lastBeatTime; // monitor the time since the last beat to avoid noise 247 | 248 | // FADE LED HERE, IF WE COULD FADE... 249 | 250 | // find the peak and trough of the pulse wave 251 | if (Signal < thresh && N > (IBI / 5) * 3) { // avoid dichrotic noise by waiting 3/5 of last IBI 252 | if (Signal < T) { // T is the trough 253 | T = Signal; // keep track of lowest point in pulse wave 254 | } 255 | } 256 | 257 | if (Signal > thresh && Signal > P) { // thresh condition helps avoid noise 258 | P = Signal; // P is the peak 259 | } // keep track of highest point in pulse wave 260 | 261 | // NOW IT'S TIME TO LOOK FOR THE HEART BEAT 262 | // signal surges up in value every time there is a pulse 263 | if (N > 250) { // avoid high frequency noise 264 | if ( (Signal > thresh) && (Pulse == 0) && (N > ((IBI / 5) * 3)) ) { 265 | Pulse = 1; // set the Pulse flag when we think there is a pulse 266 | IBI = sampleCounter - lastBeatTime; // measure time between beats in mS 267 | lastBeatTime = sampleCounter; // keep track of time for next pulse 268 | 269 | if (secondBeat) { // if this is the second beat, if secondBeat == TRUE 270 | secondBeat = 0; // clear secondBeat flag 271 | for (int i = 0; i <= 9; i++) { // seed the running total to get a realisitic BPM at startup 272 | rate[i] = IBI; 273 | } 274 | } 275 | 276 | if (firstBeat) { // if it's the first time we found a beat, if firstBeat == TRUE 277 | firstBeat = 0; // clear firstBeat flag 278 | secondBeat = 1; // set the second beat flag 279 | // IBI value is unreliable so discard it 280 | return; 281 | } 282 | 283 | 284 | // keep a running total of the last 10 IBI values 285 | int runningTotal = 0; // clear the runningTotal variable 286 | 287 | for (int i = 0; i <= 8; i++) { // shift data in the rate array 288 | rate[i] = rate[i + 1]; // and drop the oldest IBI value 289 | runningTotal += rate[i]; // add up the 9 oldest IBI values 290 | } 291 | 292 | rate[9] = IBI; // add the latest IBI to the rate array 293 | runningTotal += rate[9]; // add the latest IBI to runningTotal 294 | runningTotal /= 10; // average the last 10 IBI values 295 | BPM = 60000 / runningTotal; // how many beats can fit into a minute? that's BPM! 296 | QS = 1; // set Quantified Self flag (we detected a beat) 297 | //fadeLevel = MAX_FADE_LEVEL; // If we're fading, re-light that LED. 298 | } 299 | } 300 | 301 | if (Signal < thresh && Pulse == 1) { // when the values are going down, the beat is over 302 | Pulse = 0; // reset the Pulse flag so we can do it again 303 | amp = P - T; // get amplitude of the pulse wave 304 | thresh = amp / 2 + T; // set thresh at 50% of the amplitude 305 | P = thresh; // reset these for next time 306 | T = thresh; 307 | } 308 | 309 | if (N > 2500) { // if 2.5 seconds go by without a beat 310 | thresh = threshSetting; // set thresh default 311 | P = 512; // set P default 312 | T = 512; // set T default 313 | lastBeatTime = sampleCounter; // bring the lastBeatTime up to date 314 | firstBeat = 1; // set these to avoid noise 315 | secondBeat = 0; // when we get the heartbeat back 316 | QS = 0; 317 | BPM = 0; 318 | IBI = 600; // 600ms per beat = 100 Beats Per Minute (BPM) 319 | Pulse = 0; 320 | amp = 100; // beat amplitude 1/10 of input range. 321 | 322 | } 323 | 324 | duration = micros()-thisTime; 325 | 326 | } 327 | 328 | } 329 | --------------------------------------------------------------------------------