This is essentially what we showed at the Mid Process critique. What we did was single out one of the variables that arduino processes from all the data gathered from the pulse sensor (there are nearly a dozen different variables fed to processing from arduino, but the simplest one to read also happens to be the one most steady and simple to manipulate.) That variable was very simply called "heart" and essentially arduino just feeds Processing a character (via the serial connection) every time it gets a pulse. Simplest variable, no mathematics required, but also the one that works the best for what we need because there is virtually no delay in processing this variable.
Every time Processing gets that character from arduino saying that its received a pulse, the outline of the person standing in fromt of it pulses.
It was a start, but we received many new ideas on how to further this project for the final critique which I have been researching extensively.
PROCESSING CODE: (not including a tab for "serialEvent" class)
//-------------------------------// VIDEO
import processing.video.*; // - Video library
import blobDetection.*; // - BlobDetection library
Capture cam;
BlobDetection theBlobDetection;
PImage img;
boolean newFrame=false;
//-------------------------------// END VIDEO
import processing.serial.*; // this is how we talk to arduino
PFont font, rateFont, smallFont; // create font instances
Serial port; // create and name the serial port
int StrokeX = 0; // used to pulse the lines from the blob detection
int heart = 0; // used to time pulsing of heart graphic with your heart beat
int pulseRate = 0; // used to hold pulse rate value sent from arduino (beats per minute)
int Sensor = 0; // used to hold raw sensor data from arduino
int HRV; // time between this current beat and the last beat in mS (used for Heart Rate Variability)
int Ypos; // used to print the new Pulse Sensor data point
int[] pulseY; // used to hold pulse waveform Y positions
int[] rateY; // used to hold bpm waveform Y positions
boolean beat = false; // used to advance heart rate graph
boolean newRate = false; // used to update heart rate display
//-------------------------------// SETUP
void setup() {
size(800,600); // Stage size
frameRate(30); // refresh rate
//-------------------------------// VIDEO
cam = new Capture(this, 40*4, 30*4, 15); // Capture and start BlobDetection
img = new PImage(80,60); // img which will be sent to detection (a smaller copy of the cam frame which you won't actually see);
theBlobDetection = new BlobDetection(img.width, img.height);
theBlobDetection.setPosDiscrimination(true);
theBlobDetection.setThreshold(0.2f); // will detect bright areas whose luminosity > 0.2f;
//-------------------------------// END VIDEO
//-------------------------------// SERIAL CONNECTION
// find and establish contact with the serial port
println(Serial.list()); // print a list of available serial ports
port = new Serial(this, Serial.list()[0], 115200); // choose the right one in square brackets
port.bufferUntil('\n'); // arduino will end each ascii string with a '\n' at the end (carriage return)
port.clear(); // flush the serial buffer
//-------------------------------// END SERIAL CONNECTION
}
//-------------------------------// VIDEO
void captureEvent(Capture cam) {
cam.read();
newFrame = true;
}
//-------------------------------// END VIDEO
void draw(){
printScreen(); // DRAW THE MAJOR SCREEN COMPONENTS AND TEXT
// THESE ARE THE HEART RATE DRAWING ROUTINES
if (beat == true){ // only move the heart rate line over once every time the heart beats (beat flag set in serialEvent tab)
beat = false; // reset beat flag
for (int i=0; i<rateY.length-1; i++){
rateY[i] = rateY[i+1]; // shif the bpm Y coordinates over one pixel to the left
}
}
//-------------------------------// VIDEO
if (newFrame) {
newFrame=false;
//image(cam,0,0,width,height);
img.copy(cam, 0, 0, cam.width, cam.height, 0, 0, img.width, img.height);
// fastblur(img, 2);
theBlobDetection.computeBlobs(img.pixels);
drawBlobsAndEdges(true,true);
}
//-------------------------------// END VIDEO
} //end of draw loop
void printScreen(){ // DRAW MAJOR SCREEN ELEMENTS AND TEXT
heart--; // the heart variable is used to time how long the heart graphic swells when your heart beats
if (heart < 0){heart = 0;} // don't let the heart variable go into negative numbers
if (heart > 0){ // if a beat happened recently,
//strokeWeight(15); // make the heart big
StrokeX=15;
}
StrokeX=1;
} // END OF printScreen FUNCTION
// ==================================================
// drawBlobsAndEdges()
// ==================================================
void drawBlobsAndEdges(boolean drawBlobs, boolean drawEdges) {
fill(0,0,0,10);
rectMode(CORNER);
heart--; // the heart variable is used to time how long the heart graphic swells when your heart beats
if (heart < 0){heart = 0;} // don't let the heart variable go into negative numbers
if (heart > 0){ // if a beat happened recently,
//strokeWeight(15); // make the heart big
StrokeX=15;
fill(0,0,0,255);
rect(0,0,width,height);
}
StrokeX=1;
noFill();
Blob b;
EdgeVertex eA,eB;
for (int n=0 ; n<theBlobDetection.getBlobNb() ; n++) {
b=theBlobDetection.getBlob(n);
if (b!=null) {
// Edges
if (drawEdges) {
strokeWeight(StrokeX);
stroke(255,0,0);
for (int m=0;m<b.getEdgeNb();m++) {
eA = b.getEdgeVertexA(m);
eB = b.getEdgeVertexB(m);
if (eA !=null && eB !=null)
strokeWeight(StrokeX);
line(eA.x*width, eA.y*height,eB.x*width, eB.y*height);
}
}
}
}
}
-------------------------------------------
ARDUINO CODE:
long Hxv[4]; // these arrays are used in the digital filter
long Hyv[4]; // H for highpass, L for lowpass
long Lxv[4];
long Lyv[4];
unsigned long readings; // used to help normalize the signal
unsigned long peakTime; // used to time the start of the heart pulse
unsigned long lastPeakTime = 0;// used to find the time between beats
volatile int Peak; // used to locate the highest point in positive phase of heart beat waveform
int rate; // used to help determine pulse rate
volatile int BPM; // used to hold the pulse rate
int offset = 0; // used to normalize the raw data
int sampleCounter; // used to determine pulse timing
int beatCounter = 1; // used to keep track of pulses
volatile int Signal; // holds the incoming raw data
int NSignal; // holds the normalized signal
volatile int FSignal; // holds result of the bandpass filter
volatile int HRV; // holds the time between beats
volatile int Scale = 13; // used to scale the result of the digital filter. range 12<>20 : high<>low amplification
volatile int Fade = 0;
boolean first = true; // reminds us to seed the filter on the first go
volatile boolean Pulse = false; // becomes true when there is a heart pulse
volatile boolean B = false; // becomes true when there is a heart pulse
volatile boolean QS = false; // becomes true when pulse rate is determined. every 20 pulses
int pulsePin = 0; // pulse sensor purple wire connected to analog pin 0
void setup(){
pinMode(13,OUTPUT); // pin 13 will blink to your heartbeat!
Serial.begin(115200); // we agree to talk fast!
// this next bit will wind up in the library. it initializes Timer1 to throw an interrupt every 1mS.
TCCR1A = 0x00; // DISABLE OUTPUTS AND BREAK PWM ON DIGITAL PINS 9 & 10
TCCR1B = 0x11; // GO INTO 'PHASE AND FREQUENCY CORRECT' MODE, NO PRESCALER
TCCR1C = 0x00; // DON'T FORCE COMPARE
TIMSK1 = 0x01; // ENABLE OVERFLOW INTERRUPT (TOIE1)
ICR1 = 8000; // TRIGGER TIMER INTERRUPT EVERY 1mS
sei(); // MAKE SURE GLOBAL INTERRUPTS ARE ENABLED
}
void loop(){
Serial.print("S"); // S tells processing that the following string is sensor data
Serial.println(Signal); // Signal holds the latest raw Pulse Sensor signal
if (B == true){ // B is true when arduino finds the heart beat
Serial.print("B"); // 'B' tells Processing the following string is HRV data (time between beats in mS)
Serial.println(HRV); // HRV holds the time between this pulse and the last pulse in mS
B = false; // reseting the QS for next time
}
if (QS == true){ // QS is true when arduino derives the heart rate by averaging HRV over 20 beats
Serial.print("Q"); // 'QS' tells Processing that the following string is heart rate data
Serial.println(BPM); // BPM holds the heart rate in beats per minute
QS = false; // reset the B for next time
}
Fade -= 15;
Fade = constrain(Fade,0,255);
analogWrite(11,Fade);
delay(20); // take a break
}
// THIS IS THE TIMER 1 INTERRUPT SERVICE ROUTINE. IT WILL BE PUT INTO THE LIBRARY
ISR(TIMER1_OVF_vect){ // triggered every time Timer 1 overflows
// Timer 1 makes sure that we take a reading every milisecond
Signal = analogRead(pulsePin);
// First normailize the waveform around 0
readings += Signal; // take a running total
sampleCounter++; // we do this every milisecond. this timer is used as a clock
if ((sampleCounter %300) == 0){ // adjust as needed
offset = readings / 300; // average the running total
readings = 0; // reset running total
}
NSignal = Signal - offset; // normalizing here
// IF IT'S THE FIRST TIME THROUGH THE SKETCH, SEED THE FILTER WITH CURRENT DATA
if (first = true){
for (int i=0; i<4; i++){
Lxv[i] = Lyv[i] = NSignal <<10; // seed the lowpass filter
Hxv[i] = Hyv[i] = NSignal <<10; // seed the highpass filter
}
first = false; // only seed once please
}
// THIS IS THE BANDPAS FILTER. GENERATED AT www-users.cs.york.ac.uk/~fisher/mkfilter/trad.html
// BUTTERWORTH LOWPASS ORDER = 3; SAMPLERATE = 1mS; CORNER = 5Hz
Lxv[0] = Lxv[1]; Lxv[1] = Lxv[2]; Lxv[2] = Lxv[3];
Lxv[3] = NSignal<<10; // insert the normalized data into the lowpass filter
Lyv[0] = Lyv[1]; Lyv[1] = Lyv[2]; Lyv[2] = Lyv[3];
Lyv[3] = (Lxv[0] + Lxv[3]) + 3 * (Lxv[1] + Lxv[2])
+ (3846 * Lyv[0]) + (-11781 * Lyv[1]) + (12031 * Lyv[2]);
// Butterworth; Highpass; Order = 3; Sample Rate = 1mS; Corner = .8Hz
Hxv[0] = Hxv[1]; Hxv[1] = Hxv[2]; Hxv[2] = Hxv[3];
Hxv[3] = Lyv[3] / 4116; // insert lowpass result into highpass filter
Hyv[0] = Hyv[1]; Hyv[1] = Hyv[2]; Hyv[2] = Hyv[3];
Hyv[3] = (Hxv[3]-Hxv[0]) + 3 * (Hxv[1] - Hxv[2])
+ (8110 * Hyv[0]) + (-12206 * Hyv[1]) + (12031 * Hyv[2]);
FSignal = Hyv[3] >> Scale; // result of highpass shift-scaled
//PLAY AROUND WITH THE SHIFT VALUE TO SCALE THE OUTPUT ~12 <> ~20 = High <> Low Amplification.
if (FSignal >= Peak && Pulse == false){ // heart beat causes ADC readings to surge down in value.
Peak = FSignal; // finding the moment when the downward pulse starts
peakTime = sampleCounter; // recodrd the time to derive HRV.
}
// NOW IT'S TIME TO LOOK FOR THE HEART BEAT
if ((sampleCounter %20) == 0){// only look for the beat every 20mS. This clears out alot of high frequency noise.
if (FSignal < 0 && Pulse == false){ // signal surges down in value every time there is a pulse
Pulse = true; // Pulse will stay true as long as pulse signal < 0
digitalWrite(13,HIGH); // pin 13 will stay high as long as pulse signal < 0
Fade = 255; // set the fade value to highest for fading LED on pin 11 (optional)
HRV = peakTime - lastPeakTime; // measure time between beats
lastPeakTime = peakTime; // keep track of time for next pulse
B = true; // set the Quantified Self flag when HRV gets updated. NOT cleared inside this ISR
rate += HRV; // add to the running total of HRV used to determine heart rate
beatCounter++; // beatCounter times when to calculate bpm by averaging the beat time values
if (beatCounter == 10){ // derive heart rate every 10 beats. adjust as needed
rate /= beatCounter; // averaging time between beats
BPM = 60000/rate; // how many beats can fit into a minute?
beatCounter = 0; // reset counter
rate = 0; // reset running total
QS = true; // set Beat flag when BPM gets updated. NOT cleared inside this ISR
}
}
if (FSignal > 0 && Pulse == true){ // when the values are going up, it's the time between beats
digitalWrite(13,LOW); // so turn off the pin 13 LED
Pulse = false; // reset these variables so we can do it again!
Peak = 0; //
}
}
}// end isr
No comments:
Post a Comment