Sunday, 17 September 2017

Pulse Oximeter Functionality for a Medical Device

It has again been some time since I did any work on the medical device I have been developing, I've as usual, been busy!  The previous post is here in case it is of interest:

http://langster1980.blogspot.co.uk/2017/06/myoware-muscle-sensor-circuits-from.html

The gentlemen for whom I'm developing this hardware for has requested some additional functionality. The additional functionality requested is a Pulse Oximetry measurement.  Pulse Oximetry is the measurement of a person's pulse along with how much oxygen is present within their blood.  It is a common measurement made by medical practitioners to ensure their patients are in good health.  I suspect for the medical device, this information will be correlated with a person's breathing to assess how well a person's lungs are working and how much oxygen from the air is getting into their blood.

Image by Nonin Medical - http://www.nonin.com/What-is-Pulse-Oximetry
Here is an article by Nonin Medical on Pulse Oximetry:

http://www.nonin.com/What-is-Pulse-Oximetry

Here is the wikipedia article on Pulse Oximetry:

https://en.wikipedia.org/wiki/Pulse_oximetry

Here is another article on Pulse Oximetry:

https://www.howequipmentworks.com/pulse_oximeter/

The measurement works by shining a bright light through the a suitable point on the patient's skin and detecting the diffused light and from that the pulse and oxygen levels can be calculated.  The units used are SPO2 which stands for Peripheral Oxygen Saturation where:

SP = Peripheral saturation
O2 = Chemical symbol for an Oxygen molecule

There is a formula associated with Pulse Oximetry:



Where HbO2 is Oxygenated Haemoglobin and Hb is Deoxygenated Haemoglobin*

SPO2 is presented as a percentage and most people in normal conditions exhibit 94 to 99% saturation of oxygen in their blood.  If a person were unwell due to breathing difficulty then their SPO2 is lower.  If a person's SPOis below 65% then their ability to think clearly is impaired and if below 55% most people have lost consciousness.

*Haemoglobin is the red coloured metallo-protein within human blood which contains iron and carries oxygen around the body.

So that is the theory!  Lets talk about how one might design an electronic circuit to measure a person's SPO2 level.  We need to shine a light through an exposed part of the patient's skin say on the top of their index finger and measure the amount of light present at the bottom of the finger after the light has travelled through their skin.  At the same time the light signal needs to be converted to an electrical signal and then filtered.  It then will probably need to be amplified and finally converted from an analogue signal to a digital signal for processing by a microcontroller and finally displaying the resulting measurement.

It is not a particularly difficult circuit to design and plenty of other people have already done so.  I have put some of the sites I used as research below for interest:

https://www.swharden.com/wp/2013-04-14-simple-diy-ecg-pulse-oximeter-version-2/

http://www.instructables.com/id/Simple-DIY-Pulse-Sensor/

http://www.elektroda.pl/rtvforum/topic1655378.html?l=en

I may yet design my own implementation as it could be one way to reduce costs.  There is an Arduino R3 shield available though and it's open source.  It is made by ProtoCentral and is called the Proto Central AFE4490 Pulse Oximeter Shield V2.  It cost £71.12 which I actually think is a bit expensive however it does look to be well designed and implemented.

Image Credit - ProtoCentral AFE4490 Pulse Oximeter Shield for Arduino - v2
It can be bought from their website below:

Pulse Oximeter Arduino Shield

Because the device is completely open source (Very cool - I whole heartedly approve) the schematic is below:
The whole circuit is based around a very clever integrated circuit by Texas Instruments - the
AFE4490. The datasheet for this device is here:

http://www.ti.com/lit/ds/sbas602h/sbas602h.pdf

Basically the optical signal from the supplied finger clip connects to the nine way 'D type' connector on the Arduino shield. The signal output is protected from being too high by the the circuit labelled 'Protection circuit'  The diodes ensure that the electrical signals passed to and from the clip can never be greater than 3.3 Vdc which ensures that no damage can occur.

The analogue signal sent from the clip is processed by the AD4490 device which is a complete analogue front end device for pulse oximetry.  The device provides the electrical signals to drive the sensor clip, a 22 bit analogue to digital converter to change the analogue signal output from the sensor to a form which can be easily read into a microcontroller using the Serial Peripheral Interface (SPI) protocol.

The next part of the circuit is a simple power supply regulator from 5 Vdc to 3.3 Vdc with some filtering to prevent electronic noise from the Arduino R3 affecting the measurement circuitry.

A logic level converter device is present to ensure that the 3.3 Vdc signals from the AD4490 device are turned into 5 Vdc so that they are correctly read into the arduino and processed appropriately. There are two FET logic level converter circuits present to convert the 3.3 Vdc LED diagnostic signals to 5 Vdc for processing by the Arduino.

The final section shows how all the connections on the Shield connect to the Arduino R3.

The circuit looks to be exactly how one would use the AD4490 having read the datasheet in it's entirety.  Reading and understanding or interpreting datasheets is a true skill needed for aspiring electronics design engineers.

ProtoCentral have very helpfully provided example code which can be downloaded from their github site:

https://github.com/Protocentral/AFE4490_Oximeter/tree/master/arduino

I've uploaded the text only code to my arduino R3.

Lets see the circuit in action!


Here is the serial output:


Well there we have it - I'm alive, my blood is well oxygenated and my heart is beating normally! Each measurement is taken once a second and if the clip is removed an error is reported.

I modified the code a little so that I can use the excellent telemetry viewer Java applet to graph the output:

http://farrellf.com/TelemetryViewer/

Here is the modified Arduino code in case it's needed:

 //////////////////////////////////////////////////////////////////////////////////////////  
 //  
 //  AFE44xx Arduino Firmware  
 //  
 //  Copyright (c) 2016 ProtoCentral  
 //    
 //  SpO2 computation based on original code from Maxim Integrated   
 //  
 //  This software is licensed under the MIT License(http://opensource.org/licenses/MIT).   
 //    
 //  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT   
 //  NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   
 //  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,   
 //  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE   
 //  SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.  
 //  
 //  For information on how to use the HealthyPi, visit https://github.com/Protocentral/afe44xx_Oximeter  
 /////////////////////////////////////////////////////////////////////////////////////////  
 #include <string.h>  
 #include <SPI.h>  
 #include <math.h>  
 //afe44xx Register definition  
 #define CONTROL0  0x00  
 #define LED2STC   0x01  
 #define LED2ENDC  0x02  
 #define LED2LEDSTC  0x03  
 #define LED2LEDENDC  0x04  
 #define ALED2STC  0x05  
 #define ALED2ENDC  0x06  
 #define LED1STC   0x07  
 #define LED1ENDC  0x08  
 #define LED1LEDSTC  0x09  
 #define LED1LEDENDC  0x0a  
 #define ALED1STC  0x0b  
 #define ALED1ENDC  0x0c  
 #define LED2CONVST  0x0d  
 #define LED2CONVEND  0x0e  
 #define ALED2CONVST  0x0f  
 #define ALED2CONVEND 0x10  
 #define LED1CONVST  0x11  
 #define LED1CONVEND  0x12  
 #define ALED1CONVST  0x13  
 #define ALED1CONVEND 0x14  
 #define ADCRSTCNT0  0x15  
 #define ADCRSTENDCT0 0x16  
 #define ADCRSTCNT1  0x17  
 #define ADCRSTENDCT1 0x18  
 #define ADCRSTCNT2  0x19  
 #define ADCRSTENDCT2 0x1a  
 #define ADCRSTCNT3  0x1b  
 #define ADCRSTENDCT3 0x1c  
 #define PRPCOUNT  0x1d  
 #define CONTROL1  0x1e  
 #define SPARE1   0x1f  
 #define TIAGAIN   0x20  
 #define TIA_AMB_GAIN 0x21  
 #define LEDCNTRL  0x22  
 #define CONTROL2  0x23  
 #define SPARE2   0x24  
 #define SPARE3   0x25  
 #define SPARE4   0x26  
 #define SPARE4   0x26  
 #define RESERVED1  0x27  
 #define RESERVED2  0x28  
 #define ALARM   0x29  
 #define LED2VAL   0x2a  
 #define ALED2VAL  0x2b  
 #define LED1VAL   0x2c  
 #define ALED1VAL  0x2d  
 #define LED2ABSVAL  0x2e  
 #define LED1ABSVAL  0x2f  
 #define DIAG   0x30  
 #define count 60  
 #define CES_CMDIF_PKT_START_1  0x0A  
 #define CES_CMDIF_PKT_START_2  0xFA  
 #define CES_CMDIF_TYPE_DATA  0x02  
 #define CES_CMDIF_PKT_STOP  0x0B  
 //int IRheartsignal[count];  
 //int Redheartsignal[count];  
 int IRdc[count];  
 int Reddc[count];  
 double difIRheartsig_dc;  
 double difREDheartsig_dc;  
 double powdifIR;  
 double powdifRed;  
 double IRac;   
 double Redac;  
 double SpOpercentage;  
 double Ratio;  
 const int SPISTE = 7; // chip select  
 const int SPIDRDY = 2; // data ready pin   
 volatile int drdy_trigger = LOW;  
 void afe44xxInit (void);  
 void afe44xxWrite (uint8_t address, uint32_t data);  
 uint32_t afe44xxRead (uint8_t address);  
 signed long average_BPM( signed long );  
 volatile char DataPacketHeader[6];  
 volatile char DataPacket[10];  
 volatile char DataPacketFooter[2];  
 int datalen = 0x09;  
 unsigned long time;  
 volatile static int SPI_RX_Buff_Count = 0;  
 volatile char *SPI_RX_Buff_Ptr;  
 volatile int afe44xx_data_ready = false;  
 volatile unsigned int pckt =0, buff=0,t=0;  
 unsigned long ueegtemp = 0, ueegtemp2 = 0;  
 unsigned long IRtemp,REDtemp;  
 signed long seegtemp=0, seegtemp2=0;  
 volatile int i;  
 uint16_t aun_ir_buffer[100]; //infrared LED sensor data  
 uint16_t aun_red_buffer[100]; //red LED sensor data  
 #define FS      25  //sampling frequency  
 #define BUFFER_SIZE (FS*4)   
 #define MA4_SIZE 4 // DONOT CHANGE  
 #define min(x,y) ((x) < (y) ? (x) : (y))  
 const uint8_t uch_spo2_table[184]={ 95, 95, 95, 96, 96, 96, 97, 97, 97, 97, 97, 98, 98, 98, 98, 98, 99, 99, 99, 99,   
        99, 99, 99, 99, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100,   
        100, 100, 100, 100, 99, 99, 99, 99, 99, 99, 99, 99, 98, 98, 98, 98, 98, 98, 97, 97,   
        97, 97, 96, 96, 96, 96, 95, 95, 95, 94, 94, 94, 93, 93, 93, 92, 92, 92, 91, 91,   
        90, 90, 89, 89, 89, 88, 88, 87, 87, 86, 86, 85, 85, 84, 84, 83, 82, 82, 81, 81,   
        80, 80, 79, 78, 78, 77, 76, 76, 75, 74, 74, 73, 72, 72, 71, 70, 69, 69, 68, 67,   
        66, 66, 65, 64, 63, 62, 62, 61, 60, 59, 58, 57, 56, 56, 55, 54, 53, 52, 51, 50,   
        49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 31, 30, 29,   
        28, 27, 26, 25, 23, 22, 21, 20, 19, 17, 16, 15, 14, 12, 11, 10, 9, 7, 6, 5,   
        3, 2, 1 } ;  
 static int32_t an_x[ BUFFER_SIZE];   
 static int32_t an_y[ BUFFER_SIZE];   
 volatile int8_t n_buffer_count; //data length  
 int32_t n_spo2; //SPO2 value  
 int8_t ch_spo2_valid; //indicator to show if the SPO2 calculation is valid  
 int32_t n_heart_rate; //heart rate value  
 int8_t ch_hr_valid; //indicator to show if the heart rate calculation is valid  
 long status_byte=0;  
 uint8_t LeadStatus=0;  
 boolean leadoff_deteted = true;  
 uint8_t spo2_probe_open = false;  
 int dec=0;  
 void setup()  
 {  
   Serial.begin(57600);  
   Serial.println("Initialising AFE44xx.. ");    
   delay(2000) ;  // pause for a moment  
   SPI.begin();   
   // set the directions  
   pinMode (SPISTE,OUTPUT);//Slave Select  
   pinMode (SPIDRDY,INPUT);// data ready   
   attachInterrupt(0, afe44xx_drdy_event, RISING ); // Digital2 is attached to Data ready pin of AFE is interrupt0 in ARduino  
   // set SPI transmission  
   SPI.setClockDivider (SPI_CLOCK_DIV8); // set Speed as 2MHz , 16MHz/ClockDiv  
   SPI.setDataMode (SPI_MODE0);     //Set SPI mode as 0  
   SPI.setBitOrder (MSBFIRST);      //MSB first  
 // Packet structure  
  DataPacketHeader[0] = CES_CMDIF_PKT_START_1; //packet header1 0x0A  
  DataPacketHeader[1] = CES_CMDIF_PKT_START_2; //packet header2 0xFA  
  DataPacketHeader[2] = datalen;        // data length 9  
  DataPacketHeader[3] = (uint8_t)(datalen >> 8);  
  DataPacketHeader[4] = CES_CMDIF_TYPE_DATA;  
  DataPacketFooter[0] = 0x00;  
  DataPacketFooter[1] = CES_CMDIF_PKT_STOP;  
   afe44xxInit ();   
   Serial.println("Initialisation Complete!");  
 }  
 /////////////////////////////////////////////////////////////////////////////////////////////////////////////////  
 void loop()  
 {    
    if (drdy_trigger == HIGH)   
    {  
     detachInterrupt(0);  
     afe44xxWrite(CONTROL0,0x000001);   
     IRtemp = afe44xxRead(LED1VAL);  
     afe44xxWrite(CONTROL0,0x000001);   
     REDtemp = afe44xxRead(LED2VAL);    
     afe44xx_data_ready = true;  
    }   
  if(afe44xx_data_ready == true)  
  {  
    IRtemp = (unsigned long) (IRtemp<<10);  
    seegtemp = (signed long) (IRtemp);  
    seegtemp = (signed long) (seegtemp>>10);   
    REDtemp = (unsigned long) (REDtemp<<10);  
    seegtemp2 = (signed long) (REDtemp);  
    seegtemp2 = (signed long) (seegtemp2>>10);  
    if(dec==20)  
    {  
     aun_ir_buffer[n_buffer_count]=(uint16_t) (seegtemp>>4);  
     aun_red_buffer[n_buffer_count]=(uint16_t) (seegtemp2>>4);  
     n_buffer_count++;  
     dec=0;  
    }  
    dec++;  
    if(n_buffer_count>99)  
    {  
     estimate_spo2(aun_ir_buffer, 100, aun_red_buffer, &n_spo2, &ch_spo2_valid,&n_heart_rate, &ch_hr_valid);   
     if(n_spo2 == -999)  
      Serial.println("Probe error!!!!");  
     else  
     {   
       // removed by AL to allow live graphing by telemetry viewer  
       //Serial.print("calculating sp02...");  
       //Serial.print(" Sp02 : ");  
       //Serial.print(n_spo2);  
       //Serial.print("% ,");   
       //Serial.print("Pulse rate :");  
       //Serial.print(n_heart_rate);              
       //Serial.println(" bpm");  
       Serial.print("SP02 (%), Pulse Rate (BPM)");  
       Serial.print(n_spo2);  
       Serial.print(",");  
       Serial.print(n_heart_rate);  
       Serial.print(",");  
       Serial.println();  
     }  
     n_buffer_count=0;  
    }       
    afe44xx_data_ready = false;  
    drdy_trigger = LOW;  
    attachInterrupt(0, afe44xx_drdy_event, RISING );     
  }            
 }  
 ///////// Gets Fired on DRDY event/////////////////////////////  
 void afe44xx_drdy_event()  
 {  
  drdy_trigger = HIGH;  
 }  
 ////////////////AFE44xx initialization//////////////////////////////////////////  
 void afe44xxInit (void)  
 {   
  // Serial.println("afe44xx Initialization Starts");   
    afe44xxWrite(CONTROL0,0x000000);  
    afe44xxWrite(CONTROL0,0x000008);   
    afe44xxWrite(TIAGAIN,0x000000); // CF = 5pF, RF = 500kR  
    afe44xxWrite(TIA_AMB_GAIN,0x000001);   
    afe44xxWrite(LEDCNTRL,0x001414);    
    afe44xxWrite(CONTROL2,0x000000); // LED_RANGE=100mA, LED=50mA   
    afe44xxWrite(CONTROL1,0x010707); // Timers ON, average 3 samples   
    afe44xxWrite(PRPCOUNT, 0X001F3F);  
   afe44xxWrite(LED2STC, 0X001770);   
   afe44xxWrite(LED2ENDC,0X001F3E);   
   afe44xxWrite(LED2LEDSTC,0X001770);  
   afe44xxWrite(LED2LEDENDC,0X001F3F);  
   afe44xxWrite(ALED2STC, 0X000000);   
   afe44xxWrite(ALED2ENDC, 0X0007CE);  
   afe44xxWrite(LED2CONVST,0X000002);   
   afe44xxWrite(LED2CONVEND, 0X0007CF);  
   afe44xxWrite(ALED2CONVST, 0X0007D2);   
   afe44xxWrite(ALED2CONVEND,0X000F9F);   
   afe44xxWrite(LED1STC, 0X0007D0);   
   afe44xxWrite(LED1ENDC, 0X000F9E);  
   afe44xxWrite(LED1LEDSTC, 0X0007D0);  
   afe44xxWrite(LED1LEDENDC, 0X000F9F);  
   afe44xxWrite(ALED1STC, 0X000FA0);   
   afe44xxWrite(ALED1ENDC, 0X00176E);  
   afe44xxWrite(LED1CONVST, 0X000FA2);  
   afe44xxWrite(LED1CONVEND, 0X00176F);   
   afe44xxWrite(ALED1CONVST, 0X001772);   
   afe44xxWrite(ALED1CONVEND, 0X001F3F);  
   afe44xxWrite(ADCRSTCNT0, 0X000000);  
   afe44xxWrite(ADCRSTENDCT0,0X000000);   
   afe44xxWrite(ADCRSTCNT1, 0X0007D0);   
   afe44xxWrite(ADCRSTENDCT1, 0X0007D0);  
   afe44xxWrite(ADCRSTCNT2, 0X000FA0);  
   afe44xxWrite(ADCRSTENDCT2, 0X000FA0);   
   afe44xxWrite(ADCRSTCNT3, 0X001770);   
   afe44xxWrite(ADCRSTENDCT3, 0X001770);  
   delay(1000);  
   // Serial.println("afe44xx Initialization Done");   
 }  
 /////////////////////////////////////////////////////////////////////////////////////////////////////////////////  
 void afe44xxWrite (uint8_t address, uint32_t data)  
 {  
   digitalWrite (SPISTE, LOW); // enable device  
   SPI.transfer (address); // send address to device  
   SPI.transfer ((data >> 16) & 0xFF); // write top 8 bits  
   SPI.transfer ((data >> 8) & 0xFF); // write middle 8 bits  
   SPI.transfer (data & 0xFF); // write bottom 8 bits    
   digitalWrite (SPISTE, HIGH); // disable device  
 }  
 /////////////////////////////////////////////////////////////////////////////////////////////////////////////////  
 unsigned long afe44xxRead (uint8_t address)  
 {      
   unsigned long data=0;  
   digitalWrite (SPISTE, LOW); // enable device  
   SPI.transfer (address); // send address to device  
   //SPI.transfer (data);  
   data |= ((unsigned long)SPI.transfer (0)<<16); // read top 8 bits data  
   data |= ((unsigned long)SPI.transfer (0)<<8); // read middle 8 bits data  
   data |= SPI.transfer (0); // read bottom 8 bits data  
   digitalWrite (SPISTE, HIGH); // disable device  
   return data; // return with 24 bits of read data  
 }  
 /////////////////////////////////////////////////////////////////////////////////////////////////////////////////  
 void estimate_spo2(uint16_t *pun_ir_buffer, int32_t n_ir_buffer_length, uint16_t *pun_red_buffer, int32_t *pn_spo2, int8_t *pch_spo2_valid, int32_t *pn_heart_rate, int8_t *pch_hr_valid)  
 {  
  uint32_t un_ir_mean,un_only_once ;  
  int32_t k, n_i_ratio_count;  
  int32_t i, s, m, n_exact_ir_valley_locs_count, n_middle_idx;  
  int32_t n_th1, n_npks, n_c_min;    
  int32_t an_ir_valley_locs[15] ;  
  int32_t n_peak_interval_sum;  
  int32_t n_y_ac, n_x_ac;  
  int32_t n_spo2_calc;   
  int32_t n_y_dc_max, n_x_dc_max;   
  int32_t n_y_dc_max_idx, n_x_dc_max_idx;   
  int32_t an_ratio[5], n_ratio_average;   
  int32_t n_nume, n_denom ;  
  // calculates DC mean and subtract DC from ir  
  un_ir_mean =0;   
  for (k=0 ; k<n_ir_buffer_length ; k++ ) un_ir_mean += pun_ir_buffer[k] ;  
  un_ir_mean =un_ir_mean/n_ir_buffer_length ;  
  // remove DC and invert signal so that we can use peak detector as valley detector  
  for (k=0 ; k<n_ir_buffer_length ; k++ )   
   an_x[k] = -1*(pun_ir_buffer[k] - un_ir_mean) ;   
  // 4 pt Moving Average  
  for(k=0; k< BUFFER_SIZE-MA4_SIZE; k++){  
   an_x[k]=( an_x[k]+an_x[k+1]+ an_x[k+2]+ an_x[k+3])/(int)4;      
  }  
  // calculate threshold   
  n_th1=0;   
  for ( k=0 ; k<BUFFER_SIZE ;k++){  
   n_th1 += an_x[k];  
  }  
  n_th1= n_th1/ ( BUFFER_SIZE);  
  if( n_th1<30) n_th1=30; // min allowed  
  if( n_th1>60) n_th1=60; // max allowed  
  for ( k=0 ; k<15;k++) an_ir_valley_locs[k]=0;  
  // since we flipped signal, we use peak detector as valley detector  
  find_peak( an_ir_valley_locs, &n_npks, an_x, BUFFER_SIZE, n_th1, 4, 15 );//peak_height, peak_distance, max_num_peaks   
  n_peak_interval_sum =0;  
  if (n_npks>=2){  
   for (k=1; k<n_npks; k++) n_peak_interval_sum += (an_ir_valley_locs[k] -an_ir_valley_locs[k -1] ) ;  
   n_peak_interval_sum =n_peak_interval_sum/(n_npks-1);  
   *pn_heart_rate =(int32_t)( (FS*60)/ n_peak_interval_sum );  
   *pch_hr_valid = 1;  
  }  
  else {   
   *pn_heart_rate = -999; // unable to calculate because # of peaks are too small  
   *pch_hr_valid = 0;  
  }  
  // load raw value again for SPO2 calculation : RED(=y) and IR(=X)  
  for (k=0 ; k<n_ir_buffer_length ; k++ ) {  
    an_x[k] = pun_ir_buffer[k] ;   
    an_y[k] = pun_red_buffer[k] ;   
  }  
  // find precise min near an_ir_valley_locs  
  n_exact_ir_valley_locs_count =n_npks;   
  //using exact_ir_valley_locs , find ir-red DC andir-red AC for SPO2 calibration an_ratio  
  //finding AC/DC maximum of raw  
  n_ratio_average =0;   
  n_i_ratio_count = 0;   
  for(k=0; k< 5; k++) an_ratio[k]=0;  
  for (k=0; k< n_exact_ir_valley_locs_count; k++){  
   if (an_ir_valley_locs[k] > BUFFER_SIZE ){  
    *pn_spo2 = -999 ; // do not use SPO2 since valley loc is out of range  
    *pch_spo2_valid = 0;   
    return;  
   }  
  }  
  // find max between two valley locations   
  // and use an_ratio betwen AC compoent of Ir & Red and DC compoent of Ir & Red for SPO2   
  for (k=0; k< n_exact_ir_valley_locs_count-1; k++){  
   n_y_dc_max= -16777216 ;   
   n_x_dc_max= -16777216;   
   if (an_ir_valley_locs[k+1]-an_ir_valley_locs[k] >3){  
     for (i=an_ir_valley_locs[k]; i< an_ir_valley_locs[k+1]; i++){  
      if (an_x[i]> n_x_dc_max) {n_x_dc_max =an_x[i]; n_x_dc_max_idx=i;}  
      if (an_y[i]> n_y_dc_max) {n_y_dc_max =an_y[i]; n_y_dc_max_idx=i;}  
    }  
    n_y_ac= (an_y[an_ir_valley_locs[k+1]] - an_y[an_ir_valley_locs[k] ] )*(n_y_dc_max_idx -an_ir_valley_locs[k]); //red  
    n_y_ac= an_y[an_ir_valley_locs[k]] + n_y_ac/ (an_ir_valley_locs[k+1] - an_ir_valley_locs[k]) ;   
    n_y_ac= an_y[n_y_dc_max_idx] - n_y_ac;  // subracting linear DC compoenents from raw   
    n_x_ac= (an_x[an_ir_valley_locs[k+1]] - an_x[an_ir_valley_locs[k] ] )*(n_x_dc_max_idx -an_ir_valley_locs[k]); // ir  
    n_x_ac= an_x[an_ir_valley_locs[k]] + n_x_ac/ (an_ir_valley_locs[k+1] - an_ir_valley_locs[k]);   
    n_x_ac= an_x[n_y_dc_max_idx] - n_x_ac;   // subracting linear DC compoenents from raw   
    n_nume=( n_y_ac *n_x_dc_max)>>7 ; //prepare X100 to preserve floating value  
    n_denom= ( n_x_ac *n_y_dc_max)>>7;  
    if (n_denom>0 && n_i_ratio_count <5 && n_nume != 0)  
    {    
     an_ratio[n_i_ratio_count]= (n_nume*100)/n_denom ; //formular is ( n_y_ac *n_x_dc_max) / ( n_x_ac *n_y_dc_max) ;  
     n_i_ratio_count++;  
    }  
   }  
  }  
  // choose median value since PPG signal may varies from beat to beat  
  sort_ascend(an_ratio, n_i_ratio_count);  
  n_middle_idx= n_i_ratio_count/2;  
  if (n_middle_idx >1)  
   n_ratio_average =( an_ratio[n_middle_idx-1] +an_ratio[n_middle_idx])/2; // use median  
  else  
   n_ratio_average = an_ratio[n_middle_idx ];  
  if( n_ratio_average>2 && n_ratio_average <184){  
   n_spo2_calc= uch_spo2_table[n_ratio_average] ;  
   *pn_spo2 = n_spo2_calc ;  
   *pch_spo2_valid = 1;// float_SPO2 = -45.060*n_ratio_average* n_ratio_average/10000 + 30.354 *n_ratio_average/100 + 94.845 ; // for comparison with table  
  }  
  else{  
   *pn_spo2 = -999 ; // do not use SPO2 since signal an_ratio is out of range  
   *pch_spo2_valid = 0;   
  }  
 }  
 /////////////////////////////////////////////////////////////////////////////////////////////////////////////////  
 void find_peak( int32_t *pn_locs, int32_t *n_npks, int32_t *pn_x, int32_t n_size, int32_t n_min_height, int32_t n_min_distance, int32_t n_max_num )  
 /**  
 * \brief    Find peaks  
 * \par     Details  
 *        Find at most MAX_NUM peaks above MIN_HEIGHT separated by at least MIN_DISTANCE  
 *  
 * \retval    None  
 */  
 {  
   find_peak_above( pn_locs, n_npks, pn_x, n_size, n_min_height );  
   remove_close_peaks( pn_locs, n_npks, pn_x, n_min_distance );  
   *n_npks = min( *n_npks, n_max_num );  
 }  
 /////////////////////////////////////////////////////////////////////////////////////////////////////////////////  
 void find_peak_above( int32_t *pn_locs, int32_t *n_npks, int32_t *pn_x, int32_t n_size, int32_t n_min_height )  
 /**  
 * \brief    Find peaks above n_min_height  
 * \par     Details  
 *        Find all peaks above MIN_HEIGHT  
 *  
 * \retval    None  
 */  
 {  
  int32_t i = 1, n_width;  
  *n_npks = 0;  
  while (i < n_size-1){  
   if (pn_x[i] > n_min_height && pn_x[i] > pn_x[i-1]){   // find left edge of potential peaks  
    n_width = 1;  
    while (i+n_width < n_size && pn_x[i] == pn_x[i+n_width]) // find flat peaks  
     n_width++;  
    if (pn_x[i] > pn_x[i+n_width] && (*n_npks) < 15 ){   // find right edge of peaks  
     pn_locs[(*n_npks)++] = i;    
     // for flat peaks, peak location is left edge  
     i += n_width+1;  
    }  
    else  
     i += n_width;  
   }  
   else  
    i++;  
   // Serial.println("beat");  
  }  
 }  
 /////////////////////////////////////////////////////////////////////////////////////////////////////////////////  
 void remove_close_peaks(int32_t *pn_locs, int32_t *pn_npks, int32_t *pn_x, int32_t n_min_distance)  
 /**  
 * \brief    Remove peaks  
 * \par     Details  
 *        Remove peaks separated by less than MIN_DISTANCE  
 *  
 * \retval    None  
 */  
 {  
  int32_t i, j, n_old_npks, n_dist;  
  /* Order peaks from large to small */  
  sort_indices_descend( pn_x, pn_locs, *pn_npks );  
  for ( i = -1; i < *pn_npks; i++ ){  
   n_old_npks = *pn_npks;  
   *pn_npks = i+1;  
   for ( j = i+1; j < n_old_npks; j++ ){  
    n_dist = pn_locs[j] - ( i == -1 ? -1 : pn_locs[i] ); // lag-zero peak of autocorr is at index -1  
    if ( n_dist > n_min_distance || n_dist < -n_min_distance )  
     pn_locs[(*pn_npks)++] = pn_locs[j];  
   }  
  }  
  // Resort indices int32_to ascending order  
  sort_ascend( pn_locs, *pn_npks );  
 }  
 /////////////////////////////////////////////////////////////////////////////////////////////////////////////////  
 void sort_ascend(int32_t *pn_x, int32_t n_size)   
 /**  
 * \brief    Sort array  
 * \par     Details  
 *        Sort array in ascending order (insertion sort algorithm)  
 *  
 * \retval    None  
 */  
 {  
  int32_t i, j, n_temp;  
  for (i = 1; i < n_size; i++) {  
   n_temp = pn_x[i];  
   for (j = i; j > 0 && n_temp < pn_x[j-1]; j--)  
     pn_x[j] = pn_x[j-1];  
   pn_x[j] = n_temp;  
  }  
 }  
 /////////////////////////////////////////////////////////////////////////////////////////////////////////////////  
 void sort_indices_descend( int32_t *pn_x, int32_t *pn_indx, int32_t n_size)  
 /**  
 * \brief    Sort indices  
 * \par     Details  
 *        Sort indices according to descending order (insertion sort algorithm)  
 *  
 * \retval    None  
 */   
 {  
  int32_t i, j, n_temp;  
  for (i = 1; i < n_size; i++) {  
   n_temp = pn_indx[i];  
   for (j = i; j > 0 && pn_x[n_temp] > pn_x[pn_indx[j-1]]; j--)  
    pn_indx[j] = pn_indx[j-1];  
   pn_indx[j] = n_temp;  
  }  
 }  

Here is the output from Telemetry Viewer 0.4:


I think this is actually a very well implemented product and if I can use it as part of my project it will save me months of development...my only concern is cost and that it updates slowly and it also uses most of the program memory available on an ATMEL 328P Microcontroller.  One final comment I might make is that the example code provided is very long and complex.  It would be better if the initialisation functions and the measurement calculation functions were moved to a library.  It will make reading and writing code much simpler.

Whether I use this arduino shield or not is up for debate...I will probably discuss things further and come to a decision later.

Well that's all for now - Langster!

6 comments:

  1. This comment has been removed by a blog administrator.

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete
  3. This comment has been removed by a blog administrator.

    ReplyDelete
  4. This comment has been removed by a blog administrator.

    ReplyDelete
  5. This comment has been removed by a blog administrator.

    ReplyDelete
  6. Why don't work my SpO2 value?

    ReplyDelete