This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

How to detect human presence to measure heart rate using AFE44xx ?

Other Parts Discussed in Thread: AFE4490

Hi all,

I have a query related to AFE4490 module.

I have tested the attached "PulseOxy.c" file to calculate heart rate using AFE4490 and it gives me very well values using the algorithm shown in this file.

Which i got it from 

Now my query is when i take my finger from the module and put any other object , it does the same , i mean it count the HR using reflection and calculation.

So can any body help me to find a way in which it works only upon detection of human / living being and on other objects/non living being it discard the calculation ?

Please let me know if is there any documentation or any guidances are available on the same .

Also please let me know if i can provide any more details.

Thanks & Regards,

Rutvij




0412.PulseOx_HRM.c
/*
 * Copyright (C) 2013 University of Texas at Dallas
 *                    Embedded Systems and Signal Processing Lab
 *                                  and
 *                    Texas Instruments Incorporated
 *
 *  All rights reserved.
 *
 *
 *
 *
 *
 *
 *
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *  Redistributions of source code must retain the above copyright
 *  notice, this list of conditions and the following disclaimer.
 *
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *
 *  Neither the names of Texas Instruments Incorporated nor
 *  The University of Texas at Dallas nor the names of
 *  their contributors may be used to endorse or promote products derived
 *  from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
*/

/**
 * @file PulseOx_HRM.c
 * @brief This class contains the signal processing algorithms
 * @details Filters incoming data from AFE4400 and calculates SpO2 and Heart rate values
 * @version 1.0
 * @date 2012
 */

#include "PulseOx_HRM.h"

int voltageCodeCounter = 0; //Counter for how many times DRDY is received

//Variables for heart rate measurement
int transitions[2] = {-1,-1};
static unsigned int decision_hist[4] = {0, 0, 0, 0};
static unsigned int hist_sum = 0;
static unsigned int hist_count = 0;
static unsigned int delta_hr = 0;

int heartRateCalc = 0;

long previousValue = 0;
long currentValue = 0;

//Variables for SpO2 measurement
long maxIRValue = 0;
long minIRValue = 1000000000;
long maxRedValue = 0;
long minRedValue = 1000000000;

int pulseStarted = 0; //Flag to indicate if at least 1 pulse is completed

unsigned char voltageCodeBuffer[20]; //Buffer to hold voltage code values to be sent over BLE
int bufferCounter = 0; //Counter for indexing into voltage code buffer

char filtered = 1; //Flag set to choose between filtered and unfiltered streams

unsigned int heartRate = 0;
unsigned int heartRate2 = 10;
unsigned int heartReport = 10;
unsigned int pulseOx = 0;

extern long collectRED();
extern long collectIR();
extern long collectIRMinusAMBIR();
extern bool checkStreamStatus();
extern void sendBLEBuffer(unsigned char buffer[]);
extern void sendMessage(char msgByte);

const  long SCALE = 33554432;
const double SOS [3][6] = {
	{1,  0,  -1,  1,  -1.9725464130456409,  0.97381705538051955},
	{1,  0,  -1,  1,  -1.9950869055123981,  0.99513037435061724},
	{1,  0,  -1,  1,  -1.9688341956177746,  0.96906741719379641}};


/**
* @brief Bandpass filter between 0.5 and 3Hz
*
* @param  sample a long
*
* @return  long
*/

long filter(long sample)
{
//    const long b_int [3][3] = {
//	{(long) (SOS [0][0] * SCALE), (long) (SOS [0][1] * SCALE), (long) (SOS [0][2] * SCALE)},
//	{(long) (SOS [1][0] * SCALE), (long) (SOS [1][1] * SCALE), (long) (SOS [1][2] * SCALE)},
//	{(long) (SOS [2][0] * SCALE), (long) (SOS [2][1] * SCALE), (long) (SOS [2][2] * SCALE)}};
//
//    const long a_int [3][3] = {
//	{(long) (SOS [0][3] * SCALE), (long) (SOS [0][4] * SCALE), (long) (SOS [0][5] * SCALE)},
//	{(long) (SOS [1][3] * SCALE), (long) (SOS [1][4] * SCALE), (long) (SOS [1][5] * SCALE)},
//	{(long) (SOS [2][3] * SCALE), (long) (SOS [2][4] * SCALE), (long) (SOS [2][5] * SCALE)}};

	long b_int [3][3] = {0,0,0,0,0,0,0,0,0};
	long a_int [3][3] = {0,0,0,0,0,0,0,0,0};

	int k;
	int j;

	for(k=0; k<3; k++)
	{
		for(j=0; j<3;j++)
		{
			b_int[k][j] = (long) (SOS [k][j] * SCALE);
			a_int[k][j] = (long) (SOS [k][j+3] * SCALE);
		}
	}

//    long s_int [4] = {
//	(long) (s[0] * SCALE), (long) (s[1] * SCALE), (long) (s[2] * SCALE), (long) (s[3] * SCALE)};

	long s_int[4] = {0,0,0,0};

	for(j=0;j<4;j++)
	{
		s_int[j] = (long) (s[j] * SCALE);
	}

  
    static long dly [STAGES][2] = {{0,0}, {0,0}, {0,0}};
    long result, wn;
    long mysample = sample;
    long wa1, wa2, wa3;
    long wb1, wb2, wb3;

    int i;
    for (i = 0; i < STAGES; i++)
    {
            //2nd-order LCCDE code
            //(eqn 8)

            wa1 =  ((long long)mysample * s_int[i]) >> (TRUNC_BITS);
            wa2 = ((long long)a_int[i][1] * dly[i][0]) >> TRUNC_BITS;
            wa3 = ((long long)a_int[i][2] * dly[i][1]) >> TRUNC_BITS;
            wn = wa1 - wa2 - wa3;
          
            //(eqn 9)
            wb1 = ((long long)b_int[i][0] * wn) >> TRUNC_BITS;
            wb2 = ((long long)b_int[i][1] * dly[i][0]) >> TRUNC_BITS;
            wb3 = ((long long)b_int[i][2] * dly[i][1]) >> TRUNC_BITS;

            result = wb1 + wb2 + wb3;
           
            //Update filter buffers for stage i
            dly[i][1] = dly[i][0];
            dly[i][0] = wn;
            mysample = result; //in case we have to loop again


    }
    
 return (long)result;

}

/**
* @brief Calculate heart rate given start and end of pulse
*
* @param  start an int
*
* @param  end an int
*
* @return  unsigned int
*/

unsigned int calcHeartRate(int start, int end)
{
  int pulseLength = 0;
  
  if(start > end) end += 3000; //In case end index of pulse wrapped around 6 -second window
  pulseLength = end - start; //Calculate length of pulse based on start and end indices

  //Check if this is reasonable pulse length
  if((pulseLength >= sampleRate/4) && (pulseLength < sampleRate*3)) 
  {
    
    double tempHeartRate = (60.00 * sampleRate)/ pulseLength;
    
    tempHeartRate *= 100; //Multiply by 100 to maintain 2 decimal points when casting to integer
    
    int heartRate = (int)tempHeartRate; //Cast down to integer to send over BLE
    
    return heartRate;
  }
  
  return 0; //Return 0 for invalid pulse length

}

/**
* @brief Calculate SpO2 given max and min Red and IR values
*
* @param  maxIR a long
*
* @param  minIR a long
*
* @param  maxRed a long
*
* @param  minRed a long
*
* @return  unsigned int
*/

unsigned int calcPulseOx(long maxIR, long minIR, long maxRed, long minRed)
{
  double irDC = minIR;
  double irAC = maxIR - minIR;
  double redDC = minRed;
  double redAC = maxRed - minRed;
  
  double ratio = (redAC/redDC)/(irAC/irDC);
  
  spO2 = 110 - 25*ratio;
  
  double tempSpO2 = 100*spO2; //Multiply by 100 to maintain 2 decimal points when casting down to integer
  
  int pulseOx = (int)tempSpO2; //Cast to int to transfer over BLE
  
  return pulseOx;
  
}

/**
* @brief Sends a formatted pulseOx message over BLE
*
* @param  pulseOx an unsigned int
*
* @return  None
*/

void sendPulseOx(unsigned int pulseOx)
{
  unsigned char pulseOxHigh = pulseOx >> 8;
  unsigned char pulseOxLow = pulseOx;
  
  unsigned char buffer[7];
  
  buffer[0] = 0x0C; //Start byte
  buffer[1] = 0x35; //Message length
  buffer[2] = deviceCode; //Device code
  buffer[3] = 0x43; //PulseOx opcode
  buffer[4] = pulseOxHigh; //PulseOx high byte
  buffer[5] = pulseOxLow; //PulseOx low byte
  buffer[6] = 0x0D; //End byte
  
  int i;
  for(i=0; i<7; i++)
  {
     sendMessage(buffer[i]);
    __delay_cycles(1600);
  }
 
}

/**
* @brief Sends a formatted heart rate message over BLE
*
* @param  heartRate an unsigned int
*
* @return  None
*/

void sendHeartRate(unsigned int heartRate)
{
  
  unsigned char heartRateHigh = heartRate >> 8;
  unsigned char heartRateLow = heartRate;
  
  unsigned char buffer[7];
  
  buffer[0] = 0x0C; //Start byte
  buffer[1] = 0x35; //Message length
  buffer[2] = deviceCode; //Device code
  buffer[3] = 0x44; //Heart rate opcode
  buffer[4] = heartRateHigh; //Heart rate high byte
  buffer[5] = heartRateLow; //Heart rate low byte
  buffer[6] = 0x0D; //End byte
  
  int i;
  for(i=0; i<7; i++)
  {
     sendMessage(buffer[i]);
    __delay_cycles(1600);
  }

}

/**
* @brief Processes the collected Red and IR values
*
* @param  None
*
* @return  None
*/
void processData()
{
     voltageCodeCounter++;
    
    //Start calculating data after skipping 2 seconds
    if(voltageCodeCounter > 1000)
    {
       int index = voltageCodeCounter - 1001;
       
       //Read Red and IR values from AFE4400
       long irSample = collectIR();
       long redSample = collectRED();
       
       //Filter the IR value
       long filtIRSample = filter(irSample);
       
       
       if(checkStreamStatus()) //Send data for graphing only in streaming mode
       {
            //Send 1 of every 5 samples for graphing
            if(voltageCodeCounter % 5 == 0) 
             {
                 if(filtered) //If filtered flag is set stream filtered data
                 {
                       //Do a signed shift to get lower 2 bytes of data
                       voltageCodeBuffer[bufferCounter++] = filtIRSample >> 8;
                       voltageCodeBuffer[bufferCounter++] = filtIRSample;
                       
                       //Pack data in chunks of 20 bytes
                       if(bufferCounter >= 20)
                       {
                         unsigned char tempBuffer[20];
                         
                         //Transfer data and clear buffer
                         int i;
                         for(i=0; i<20; i++)
                         {
                           tempBuffer[i] = voltageCodeBuffer[i];
                           voltageCodeBuffer[i] = 0;
                         }
                         
                         bufferCounter = 0; //Reset index counter
                         
                         sendBLEBuffer(tempBuffer); //Call BLE function to send the data
                       }
                 }
                 
                 //Else stream unfiltered data
                 else
                 {
                     //Do a signed shift to get lower 2 bytes of data
                     voltageCodeBuffer[bufferCounter++] = irSample >> 9;
                     voltageCodeBuffer[bufferCounter++] = irSample >> 1;
                     if(bufferCounter >= 20)
                     {
                       unsigned char tempBuffer[20];
                       
                       //Transfer data and clear buffer
                       int i;
                       for(i=0; i<20; i++)
                       {
                         tempBuffer[i] = voltageCodeBuffer[i];
                         voltageCodeBuffer[i] = 0;
                       }
                       
                       bufferCounter = 0; //Reset index counter
                       
                       sendBLEBuffer(tempBuffer); //Call BLE function to send the data
                     }
                 }
              
             }
       }
          
       //Find max and min IR and Red values in the current pulse
       if(pulseStarted)
       {
         if(irSample > maxIRValue) maxIRValue = irSample;
         if(irSample < minIRValue) minIRValue = irSample;
         if(redSample > maxRedValue) maxRedValue = redSample;
         if(redSample < minRedValue) minRedValue = redSample;
       }

       previousValue = currentValue;
       currentValue = filtIRSample;
       
       //If there was a transition from negative to positive in the filtered data
       if(previousValue < 0 && currentValue > 0)
       {
         if(transitions[0] == -1) transitions[0] = index; //If this is the first transition
         else if(transitions[1] == -1) //If this is the second transition
         {
           transitions[1] = index;
           pulseStarted = 1; //Set pulse started flag only after 2 confirmed transitions
         }
         else
         {
           //Keep indices of last two transitions to estimate length of pulse
           transitions[0] = transitions[1];
           transitions[1] = index;
           
           //Call signal functions to calculate heart rate and SpO2
           heartRate = calcHeartRate(transitions[0], transitions[1]);
           pulseOx = calcPulseOx(maxIRValue, minIRValue, maxRedValue, minRedValue);
            
           //Average heart rate with previous history of values
           if(hist_count < 4)
           {
              decision_hist[hist_count] = heartRate;
              hist_count++;
              hist_sum = hist_sum + heartRate;
              
              heartRate2 = heartRate;
           }
           else
           {
              if (heartRate > (hist_sum/4+300))
              {
                heartRate2 = decision_hist[3] + (delta_hr)*100;
                
                if(delta_hr <3)
                  delta_hr++;
              }
              else if(heartRate < (hist_sum/4-300)) 
              {
                heartRate2 = decision_hist[3] - (300 - (delta_hr)*100);
                
                if(delta_hr > 0)
                  delta_hr--;            
              }
              else
              {
                heartRate2 = heartRate;
                
              } 
              
              hist_sum = hist_sum - decision_hist[0] + heartRate2;              
              decision_hist[0] = decision_hist[1];
              decision_hist[1] = decision_hist[2];
              decision_hist[2] = decision_hist[3];   
              decision_hist[3] = heartRate2;
              
              heartReport = hist_sum/4;

           }
           
           //Reset maximum and minimum defaults for comparison in next cycle
           maxIRValue = 0;
           minIRValue = 1000000000;
           maxRedValue = 0;
           minRedValue = 1000000000;
         }
       }
       
       if(voltageCodeCounter >= 1500 && voltageCodeCounter%500 == 0) //Send heart rate and SpO2 values every 1 seconds
       {
              GPIO_toggleOutputOnPin(PACKET_LED_PORT_ADDRESS,
                                    PACKET_LED_PORT_NUMBER,
                                    PACKET_LED_PIN
                                          );
                   
               if(checkStreamStatus())
               {
                  //Send values only if they are within reasonable range
                  if((heartReport/100) > 40 && (heartReport/100) < 150) sendHeartRate(heartReport);//decision_hist[3]);
                  if(((pulseOx/100) <= 100) && deviceCode == 0x32) sendPulseOx(pulseOx);
               }
               
               else
               {
                 //Send values only if they are within reasonable range
                 if((heartReport/100) > 40 && (heartReport/100) < 150) sendHeartRate(heartReport);
                 if(((pulseOx/100) <= 100) && deviceCode == 0x32) sendPulseOx(pulseOx);
               
               }
               
               if(voltageCodeCounter >= 4000)
               { 
                  voltageCodeCounter = 1000;
               }
       }
      
    }
    
    //Build a filter history for the initial few samples
    else
    {
      filter(collectIRMinusAMBIR());
    }
}