Final Program for Cycle Fit Project, Program is able to collect, process and output via bluetooth values for: Pedal Speed (RPM) Heart Rate (BPM) and incline (degrees)

Dependencies:   FXAS21000 FXOS8700Q mbed

Revision:
0:30fa229c34d6
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/BIKE.cpp	Sun Apr 10 20:53:46 2016 +0000
@@ -0,0 +1,417 @@
+#include "mbed.h"
+#include "FXOS8700Q.h"          //Needed for Accelerometer
+#define PI 3.14159265
+#include "PulseSensor.h"
+#include "FXAS21000.h"          //Needed for Gyroscope (If I add it)
+
+DigitalOut gpo(D0);
+DigitalOut led_red(LED_RED);
+DigitalOut led_green(LED_GREEN);
+DigitalOut led_blue(LED_BLUE);
+Serial pc(USBTX, USBRX);
+Serial blue(PTC17,PTC16);
+
+int longTimer = 10;
+double shortTimer = 0.5;
+
+//instantiating timers
+Timer time01;
+Timer time02;
+
+//instantiating summation and counter variables 
+int HR_Sum;
+int RPM_Sum;
+int Acc_Sum;
+int Acc_Count;
+
+//instantiating averaging variables
+int avgRPM;
+int avgAcc;
+int avgHR;
+
+//variables needed for processing
+////////////////////////////////////////Heart Rate////////////////////////////////////////////////////////////////////////
+AnalogIn Pulse_Signal(A0);        //Initialize analog input for pulse signal(Heart Rate sensor)
+int Pulse_Count=0;
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+///////////////////////////////////////Pedal Speed///////////////////////////////////////////////////////////////////////////////
+DigitalIn HallEffect(D2);    //Intializes Digital input into pin D2 (Hall Effect sensor)
+int Hall_Counter = 0;               //Initialize Counter for Hall Effect
+bool Low_Hall = true;               //Initialize Flag for Hall Effect sensor
+bool Button_Pressed = true;         //Initialize flag for output on terminal
+Timer Hall_Timer;                   //Initialize timer for pedal speed calc
+int Pedal_Time;                     //Intialize int for Time Passed 
+int Pedal_Speed;                    //Initialize int for Pedal Speed
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+///////////////////////////////////////Incline////////////////////////////////////////////////////////////////////////////////
+I2C i2c(PTE25, PTE24);                                      //Initialize I2C connection for sensor
+FXOS8700QAccelerometer acc(i2c, FXOS8700CQ_SLAVE_ADDR1);    // Configured for the FRDM-K64F with onboard sensors
+FXAS21000 gyro(D14, D15);
+double angleX, denomX_T, denomX_A, denomX_B, denomX_C;      //intializing variable to hold the angle calculation 
+double angleY, denomY_T, denomY_A, denomY_B, denomY_C;      //and denominator pieces of algorithm for both X&Y axis
+float faX, faY, faZ;                                        //intialize float variables for incoming raw sensor values
+double Start_Angle;                                         //Used for intial calibration to find an offset 
+double Acc_Angle;                                        //Eq: Angle Experienced - Start_Angle = Actual Angle seen by bike 
+
+int drift_counter=0;
+double gyro_reset=0;
+double gyro_drift_degree = 0;
+double gyro_drift_angle = 0;
+double gyro_data[3];
+double gyro_angle = 0;          //always intilize
+double gyro_degree = 0;
+double gyro_drift =0;
+int gyro_read_time;
+
+double Filter_Angle=0;  
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+//////////////////////////////////////////Sensor Processing////////////////////////////////////////////////////////////////////////
+
+//////////////////////////////////////Heart Rate Processing/////////////////////////////////////////////////////////////////////////
+PulseSensor::PulseSensor(PinName analogPin, void (*printDataCallback)(char,int), int callbackRateMs)
+{
+    _started = false;
+    
+    _pAin = new AnalogIn(analogPin);
+    
+    _callbackRateMs = callbackRateMs;
+    
+    _printDataCallback = printDataCallback;
+}
+ 
+ 
+PulseSensor::~PulseSensor()
+{
+    delete _pAin;
+}
+ 
+ 
+void PulseSensor::process_data_ticker_callback(void)
+{
+   // _printDataCallback('S', Signal);        // send Processing the raw Pulse Sensor data  ** Commented out so it doens't stream data
+    if (QS == true) {                       // Quantified Self flag is true when a heartbeat is found
+        
+        //_printDataCallback('B',BPM);        // send heart rate with a 'B' prefix  **Commented out so it doens't stream data
+        //_printDataCallback('Q',IBI);        // send time between beats with a 'Q' prefix **Commented out so it doens't stream data
+        QS = false;                         // reset the Quantified Self flag for next time
+    }
+}
+ 
+ 
+void PulseSensor::sensor_ticker_callback(void)
+{
+    Signal = 1023 * _pAin->read();              // 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
+    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 by waiting 
+                                                    //this also sets limit to HR sensor to max =240 BPMs
+                                                    
+        if ( (Signal > thresh) && (Pulse == false) && (N > (IBI/5)*3) ) {
+            Pulse = true;                               // set the Pulse flag when we think there is a pulse
+            Pulse_Count++;                              //This should count every time there is a pulse ** value needed for loops below 
+            IBI = sampleCounter - lastBeatTime;         // measure time between beats in mS
+            lastBeatTime = sampleCounter;               // keep track of time for next pulse
+ 
+            if(firstBeat) {                        // if it's the first time we found a beat, if firstBeat == TRUE
+                firstBeat = false;                 // clear firstBeat flag
+                return;                            // IBI value is unreliable so discard it
+            }
+            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;
+                }
+            }
+ 
+            // keep a running total of the last 10 IBI values
+            long 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
+            // QS FLAG IS NOT CLEARED INSIDE THIS ISR
+        }
+    }
+ 
+    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 = 512;                          // 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 = true;                     // when we get the heartbeat back
+    }
+}
+
+void sendDataToProcessing(char symbol, int data)
+{
+    pc.printf("%c%d\t\r\n", symbol, data);
+}
+
+bool PulseSensor::start()
+{
+    if (_started == false)
+    {
+        sampleCounter = 0;
+        lastBeatTime = 0;
+        P =512;
+        T = 512;
+        thresh = 512;
+        amp = 100;
+        firstBeat = true;
+        secondBeat = true;
+        
+        BPM=0;
+        Signal=0;
+        IBI = 600;
+        Pulse = false;
+        QS = false;
+        
+        _pulseSensorTicker.attach(this, &PulseSensor::sensor_ticker_callback, ((float)_sensorTickRateMs/1000));
+        _processDataTicker.attach(this, &PulseSensor::process_data_ticker_callback,  ((float)_callbackRateMs/1000));
+        _started = true;
+        return true;
+    }
+    else
+    {
+        return false;
+    }
+}
+ 
+bool PulseSensor::stop()
+{
+    if(_started == true)
+    {
+        _pulseSensorTicker.detach();
+        _processDataTicker.detach();
+        _started = false;
+        return true;
+    }
+    else
+    {
+        return false;
+    }
+}
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+///////////////////////////////////Pedal Speed Processing//////////////////////////////////////////////////////////////////////////////////
+void RPM (void)
+{
+        Hall_Counter=0;                                                 //resets so it adds either a 0 or 1 for the RPM_Sum counter
+        Button_Pressed = true;                                      
+         
+        if(HallEffect==0 && Low_Hall==true) {                                           //If Hall Effect Digital Output is low 
+            Hall_Counter++;                                                              //Add one to counter for calc pedal speed
+            led_green = 0;                                                               //Output Green on LED, simulates wheel rotation "sensed"
+            led_red = 1;                                                                 
+            led_blue = 1; 
+            Low_Hall = false;                                                            //flag to avoid errors                        
+        }
+        
+        else if(HallEffect==1 && Low_Hall==true){                                //Additional logic for accurate readings 
+            led_green = 1;
+            led_red = 0;                                                         //Stays red while hall effect outputs digital high
+            led_blue = 1;
+        }
+        else if(HallEffect==0 && Low_Hall==false){                                                       
+            led_green = 0;
+            led_red = 1; 
+            led_blue = 1;
+            
+        }
+        else if(HallEffect==1 && Low_Hall==false){
+            led_green = 1;
+            led_red = 0;
+            led_blue = 1;
+            Low_Hall = true;
+        }
+       // Hall_Active=0;
+        wait(0.05);
+}
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/////////////////////////////////////Incline Processing/////////////////////////////////////////////////////////////////////////
+void Acc(void)
+    {
+        acc.getX(faX);                                  //get raw accelerometer data in the X axis
+        acc.getY(faY);                                  //"" Y axis
+        acc.getZ(faZ);                                  //"" Z axis
+        denomY_A = pow(faX, 2);
+        denomY_B = pow(faZ, 2);
+        denomY_C = denomY_A + denomY_B;
+        denomY_T = pow(denomY_C, .5);                   //pow returns base raised to the power exponent 
+        
+        angleY = atan(faY/denomY_T) * 180/PI;           //should calculate the angle on the Y axis in degrees based on raw data 
+        Acc_Angle= angleY - Start_Angle;
+    }
+    
+void Offset_Calc(void)                                  //Function for angle calc upon startup
+    {
+        acc.getX(faX);                                  //get raw accelerometer data in the X axis
+        acc.getY(faY);                                  //"" Y axis
+        acc.getZ(faZ); 
+        denomY_A = pow(faX, 2);
+        denomY_B = pow(faZ, 2);
+        denomY_C = denomY_A + denomY_B;
+        denomY_T = pow(denomY_C, .5);                   //pow returns base raised to the power exponent 
+        
+        Start_Angle = atan(faY/denomY_T) * 180/PI;      //Stores intial angle value for offset calculation
+            
+    }
+void Gyro(void)
+    {
+    gyro.ReadXYZ(gyro_data);                        //Gyro output data
+    gyro_degree= gyro_data[0] / 10;                 //take y value (dps) and conver it to just degrees
+    gyro_angle += gyro_degree;                      //add new degree value to old value
+    //pc.printf("Angle: %f \t\r\n",gyro_angle);       //print angle seen by gyro
+    wait_ms(10);                                    //wait needed in order to know what to integrate by
+
+    /*
+    while (drift_counter < 30)
+    {
+    gyro.ReadXYZ(gyro_data);
+    gyro_degree= gyro_data[0];
+    gyro_angle += (gyro_degree+gyro_drift_angle)/10;
+    pc.printf("Angle: %f \t\r\n",gyro_angle);
+    wait_ms(10);
+    drift_counter++;
+    gyro_reset=gyro_angle;
+    }
+    drift_counter=0;
+    */
+    }
+void filter(void)
+    {
+    //Angle = a*(Angle + gyroData *dt) + (1-a)*(accData) filter formula constants subject to change in order to tune filter (must add to 1)
+    // a = tau / ( tau * dt)
+    //tau = ?
+    //dt = 10ms
+    /*
+    The time constant of a filter is the relative duration of signal it will act on.
+    For a low-pass filter, signals much longer than the time constant pass through unaltered
+    while signals shorter than the time constant are filtered out. The opposite is true for a highpass
+    filter.
+    */
+
+    Filter_Angle = 0.35 * (Filter_Angle + gyro_angle) + 0.65*(Acc_Angle);
+    //pc.printf("The Angle after the values are filtered: %f \t\r\n", Filter_Angle);
+
+    }
+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+int main()
+{
+    blue.baud(9600);
+    acc.enable();                                                   //Turn on Accelerometer
+    Offset_Calc();                                                  //Run intial calibration angle
+    PulseSensor Pulse_Signal(A0, sendDataToProcessing);             //intialize pulse sensor to pin A0 on board
+    Pulse_Signal.start();                                           //Turn on pulse sensor 
+    //runs data collection continuously
+    //blue.printf("Bluetooth Start\t\r");
+    
+    while(1)
+    {
+    
+    
+    //reset HR count and RPM count
+    HR_Sum = 0;
+    RPM_Sum = 0;
+    
+    //reset timer01 for HR and RPM
+    time01.reset();
+    time01.start();
+    
+    
+    
+        
+        //Loop includes all four sensors(HR, RPM, & Acc+Gyro)
+        while(time01.read() < longTimer) {              //runs for 10 seconds
+        
+            
+            //reset timer02 for Acc
+            time02.reset();
+            time02.start();
+            
+            Acc();          //might need to move this into the half second loop so that the Acc value the gyro takes to avoid drift is more accuarte 
+            
+            
+            //loop does not includes acc sensor
+            while(time02.read() < shortTimer)                    //half second loop
+            {    
+                //call Hall sensor funtion to pull # of revolutions
+                
+                RPM();                          //Resets every time its run so it sends a 1 or a 0 to add to the RPM_Sum
+                
+                Gyro();
+                filter();
+               
+                RPM_Sum = RPM_Sum + Hall_Counter; // + function value
+                
+            }
+              
+            //Every half second calculate angle
+                
+            /*
+            blue.printf("This is the current angle: %f in degrees\t\r\n\n", Actual_Angle);                          //this is here to check values being found before sending for debugging
+            blue.printf("This is the current pedal count: %d \t\r\n\n",RPM_Sum);
+            blue.printf("This is the current heart rate: %d in BPM \t\r\n\n",Pulse_Signal.BPM);
+            */
+            
+            blue.printf("%d,%d,%f\n",Pulse_Signal.BPM, RPM_Sum, Filter_Angle);      //prints to bluetooth
+            //pc.printf("%d,%d,%f\n",Pulse_Signal.BPM, RPM_Sum, Filter_Angle);      //prints to terminal
+            
+            
+            
+            
+            //send data  avgAcc, avgHR/BPM variable, avgRPM
+                
+        }
+        //reseting average HR and RPM
+        avgHR = 0;
+        avgRPM = 0;
+        
+        
+        //converts sum to beats per min
+        avgHR = HR_Sum*60/longTimer;
+        
+        //converts sum to revolutions per min
+        avgRPM = RPM_Sum*60/longTimer;                  
+        
+    }   
+        
+}