Oxygen saturation measurement error

Hi,
I am using Max30102 heart rate sensor and a GY-906-BCC Non-Contact Infrared Temperature Sensor Module to measure blood oxygen saturation and body temperature using Arduino UNO R3 and trying to display it on my LCD screen. My code is:

#include <MAX30105.h>
#include <heartRate.h>
#include <spo2_algorithm.h>

#include <LiquidCrystal_I2C.h>
#include <Wire.h>
#include <Adafruit_MLX90614.h>
#include <EEPROM.h>
#include "MAX30105.h" //sparkfun MAX3010X library
MAX30105 particleSensor;
LiquidCrystal_I2C lcd(0x27,20,4);
//#define MAX30105 //if you have Sparkfun's MAX30105 breakout board , try #define MAX30105 
Adafruit_MLX90614 mlx = Adafruit_MLX90614();

double avered = 0; double aveir = 0;
double sumirrms = 0;
double sumredrms = 0;
int i = 0;
int Num = 100;//calculate SpO2 by this sampling interval
int Temperature;
int temp;
float ESpO2;//initial value of estimated SpO2
float ESpO2_ROM;
double FSpO2 = 0.7; //filter factor for estimated SpO2
double frate = 0.95; //low pass filter for IR/red LED value to eliminate AC component
#define TIMETOBOOT 3000 // wait for this time(msec) to output SpO2
#define SCALE 88.0 //adjust to display heart beat and SpO2 in the same scale
#define SAMPLING 5 //if you want to see heart beat more precisely , set SAMPLING to 1
#define FINGER_ON 30000 // if red signal is lower than this , it indicates your finger is not on the sensor
#define USEFIFO
#define Greenled 8
#define Redled 9
void setup()
{
  Serial.begin(115200);
  
  lcd.init();
  lcd.backlight();
  lcd.setCursor(3,1);
  lcd.print("Running......");
  delay(3000);
  lcd.clear();
  ESpO2 = readEEPROM();
  Temperature = EEPROM.read(6);
  pinMode(Greenled,OUTPUT);
  pinMode(Redled,OUTPUT);
  digitalWrite(Greenled,LOW);
  digitalWrite(Redled,LOW);
  // Initialize sensor
  while (!particleSensor.begin(Wire, I2C_SPEED_FAST)) //Use default I2C port, 400kHz speed
  {
    Serial.println("MAX30102 was not found. Please check wiring/power/solder jumper at MH-ET LIVE MAX30102 board. ");
    //while (1);
  }

  //Setup to sense a nice looking saw tooth on the plotter
  byte ledBrightness = 0x7F; //Options: 0=Off to 255=50mA
  byte sampleAverage = 4; //Options: 1, 2, 4, 8, 16, 32
  byte ledMode = 2; //Options: 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green
  //Options: 1 = IR only, 2 = Red + IR on MH-ET LIVE MAX30102 board
  int sampleRate = 200; //Options: 50, 100, 200, 400, 800, 1000, 1600, 3200
  int pulseWidth = 411; //Options: 69, 118, 215, 411
  int adcRange = 16384; //Options: 2048, 4096, 8192, 16384
  // Set up the wanted parameters
  particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange); //Configure sensor with these settings

  particleSensor.enableDIETEMPRDY();
  mlx.begin();
}

void loop()
{
  
  uint32_t ir, red , green;
  double fred, fir;
  double SpO2 = 0; //raw SpO2 before low pass filtered
  
#ifdef USEFIFO
  particleSensor.check(); //Check the sensor, read up to 3 samples

  while (particleSensor.available()) {//do we have new data
#ifdef MAX30105
   red = particleSensor.getFIFORed(); //Sparkfun's MAX30105
    ir = particleSensor.getFIFOIR();  //Sparkfun's MAX30105
#else
    red = particleSensor.getFIFOIR(); //why getFOFOIR output Red data by MAX30102 on MH-ET LIVE breakout board
    ir = particleSensor.getFIFORed(); //why getFIFORed output IR data by MAX30102 on MH-ET LIVE breakout board
#endif

    
    
    i++;
    fred = (double)red;
    fir = (double)ir;
    avered = avered * frate + (double)red * (1.0 - frate);//average red level by low pass filter
    aveir = aveir * frate + (double)ir * (1.0 - frate); //average IR level by low pass filter
    sumredrms += (fred - avered) * (fred - avered); //square sum of alternate component of red level
    sumirrms += (fir - aveir) * (fir - aveir);//square sum of alternate component of IR level
    if ((i % SAMPLING) == 0) {//slow down graph plotting speed for arduino Serial plotter by thin out
      if ( millis() > TIMETOBOOT) {
        float ir_forGraph = (2.0 * fir - aveir) / aveir * SCALE;
        float red_forGraph = (2.0 * fred - avered) / avered * SCALE;
        //trancation for Serial plotter's autoscaling
        if ( ir_forGraph > 100.0) ir_forGraph = 100.0;
        if ( ir_forGraph < 80.0) ir_forGraph = 80.0;
        if ( red_forGraph > 100.0 ) red_forGraph = 100.0;
        if ( red_forGraph < 80.0 ) red_forGraph = 80.0;
        //        Serial.print(red); Serial.print(","); Serial.print(ir);Serial.print(".");
        float temperature = particleSensor.readTemperatureF();
        
        if (ir < FINGER_ON){
        temp = Temperature;
        EEPROM.write(6,temp);
        ESpO2_ROM = ESpO2;
        writeEEPROM(&ESpO2_ROM);
        lcd.setCursor(0,2);
        lcd.print("Last test: ");
        lcd.setCursor(11,2);
        lcd.print(ESpO2_ROM);
        lcd.print(" ");
        lcd.print("% ");

        lcd.setCursor(0,3);
        lcd.print("Last temp: ");
        lcd.setCursor(11,3);
        lcd.print(temp);
        lcd.print(" ");
        lcd.print("*C");
        
        break;
        }
        if(ir > FINGER_ON){
        Temperature = mlx.readObjectTempC();
        lcd.setCursor(0,0);
        lcd.print("Oxygen % = ");
        lcd.setCursor(11,0);
        lcd.print(ESpO2);
        lcd.print(" ");
        lcd.print("% ");
       // Temperature = Temperature+2;
        lcd.setCursor(0,1);
        lcd.print("Temperature: ");
        lcd.print(Temperature);
        lcd.print(" *C");
        if((ESpO2 >= 90) && (Temperature < 38)){
          digitalWrite(Redled,LOW);
          digitalWrite(Greenled,HIGH);
        }
        if((ESpO2 < 90) || (Temperature > 37)){
          digitalWrite(Greenled,LOW);
          digitalWrite(Redled,HIGH);
          
        }

        }
        
      }
    }
    if ((i % Num) == 0) {
      double R = (sqrt(sumredrms) / avered) / (sqrt(sumirrms) / aveir);
      // Serial.println(R);
      SpO2 = -23.3 * (R - 0.4) + 100; //http://ww1.microchip.com/downloads/jp/AppNotes/00001525B_JP.pdf
      ESpO2 = FSpO2 * ESpO2 + (1.0 - FSpO2) * SpO2;//low pass filter
      //  Serial.print(SpO2);Serial.print(",");Serial.println(ESpO2);
      sumredrms = 0.0; sumirrms = 0.0; i = 0;
      break;
    }
    particleSensor.nextSample(); //We're finished with this sample so move to next sample
   // Serial.println(SpO2);
  }
#endif
}

void writeEEPROM(float *data)
{
 byte ByteArray[4];
 memcpy(ByteArray, data, 4);
 for(int x = 0; x < 4; x++)
 {
   EEPROM.write(x, ByteArray[x]);
 }  

}

float readEEPROM()
{
  float ESpO2 = 85.0;
  byte ByteArray[4];
  for(int x = 0; x < 4; x++)
  {
   ByteArray[x] = EEPROM.read(x);    
  }
  memcpy(&ESpO2, ByteArray, 4);
  return ESpO2;
}

Problem is in my LCD oxygen saturation is showing nan% instead of actual value and body temp is showing 0 *C instead of actual value. Please help to solve

What happens when you print to the serial port some of the intermediate values?

(You've got some crazy scope issues there - I'm pretty sure this code could be simplified)

Edit: use the EEPROM get and put methods - they're much simpler

I am an absolute beginner. I really can't understand the scope issues here. I don't get any output on the serial monitor. The I2C can be detected and LCD turns on, that's what I can say.

That'll be because the serial prints are all commented-out.

So, you didn't write this code, am I right? If you follow the exact hardware recipe of whoever wrote it, and it works for them, it should work for you too. Or, did you try to modify it?

Yeah I didn't write the code. But the same nan value issue occurred with 4-5 other people who tried the code and the hardware

And did they print the intermediate values?

is the LCD showing nan value in SpO2 and temp parameters for the same reason?

For the same reason as what?
Because the serial prints are commented-out?
Unlikely.

not sure. they are not known to me. i saw their comment in the project youtube video. i have limited commenting allowance for a new user in this forum, so probably won't be able to comment again. Is there a solution you can think of?

You could start by un-commenting the serial prints, start using the EEPROM get and put methods, and maybe printing more stuff.
Let us know how it goes.

Which is it?

The code and wiring for the project has been copied from this link:

Sometimes, people get lucky, and get one thing right, and feel so pleased with themselves, they share it with inflict it on other people. I think it may be something to do with Dunning Kruger.

float readEEPROM()
{
  float ESpO2 = 85.0;
  byte ByteArray[4];
  for(int x = 0; x < 4; x++)
  {
   ByteArray[x] = EEPROM.read(x);    
  }
  memcpy(&ESpO2, ByteArray, 4);
  return ESpO2;
}

Why bother initialising ESpO2?
Why not use EEPROM.get?
Anti-plagiarism?

brother you asked where the code is from, I replied with the link where I copied it from. In the report I mentioned the author, which I guess is not plagiarism. Having said that, I already told that I didn't change anything the author wrote in his code. I copy pasted is it is given to see whether the project works or not. As it stands, it doesn't work as expected and shown in the demo. Thanks for your help so far. Cheers

I didn't say you were plagiarising, I just commented that if one were trying to present code in a way that plagiarism would be easy to detect, that's the way that one would do it, by including pointless, but easily-recognisable code.

Semperldem can you tell me why my L and TX,RX of Arduino turns off when I upload this following code? I can measure temperature, BPM with the MAX30102 and arduino uno r3 but when I try to measure SPO2 it just doesn't work. It says Max30105 not found in the serial monitor.

The code is:

#include <Wire.h>
#include "MAX30105.h"
#include "spo2_algorithm.h"

MAX30105 particleSensor;

#define MAX_BRIGHTNESS 255

#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)
//Arduino Uno doesn't have enough SRAM to store 100 samples of IR led data and red led data in 32-bit format
//To solve this problem, 16-bit MSB of the sampled data will be truncated. Samples become 16-bit data.
uint16_t irBuffer[100]; //infrared LED sensor data
uint16_t redBuffer[100];  //red LED sensor data
#else
uint32_t irBuffer[100]; //infrared LED sensor data
uint32_t redBuffer[100];  //red LED sensor data
#endif

int32_t bufferLength; //data length
int32_t spo2; //SPO2 value
int8_t validSPO2; //indicator to show if the SPO2 calculation is valid
int32_t heartRate; //heart rate value
int8_t validHeartRate; //indicator to show if the heart rate calculation is valid

byte pulseLED = 11; //Must be on PWM pin
byte readLED = 13; //Blinks with each data read

void setup()
{
  Serial.begin(115200); // initialize serial communication at 115200 bits per second:

  pinMode(pulseLED, OUTPUT);
  pinMode(readLED, OUTPUT);

  // Initialize sensor
  if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) //Use default I2C port, 400kHz speed
  {
    Serial.println(F("MAX30105 was not found. Please check wiring/power."));
    while (1);
  }

  Serial.println(F("Attach sensor to finger with rubber band. Press any key to start conversion"));
  while (Serial.available() == 0) ; //wait until user presses a key
  Serial.read();

  byte ledBrightness = 60; //Options: 0=Off to 255=50mA
  byte sampleAverage = 4; //Options: 1, 2, 4, 8, 16, 32
  byte ledMode = 2; //Options: 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green
  byte sampleRate = 100; //Options: 50, 100, 200, 400, 800, 1000, 1600, 3200
  int pulseWidth = 411; //Options: 69, 118, 215, 411
  int adcRange = 4096; //Options: 2048, 4096, 8192, 16384

  particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange); //Configure sensor with these settings
}

void loop()
{
  bufferLength = 100; //buffer length of 100 stores 4 seconds of samples running at 25sps

  //read the first 100 samples, and determine the signal range
  for (byte i = 0 ; i < bufferLength ; i++)
  {
    while (particleSensor.available() == false) //do we have new data?
      particleSensor.check(); //Check the sensor for new data

    redBuffer[i] = particleSensor.getRed();
    irBuffer[i] = particleSensor.getIR();
    particleSensor.nextSample(); //We're finished with this sample so move to next sample

    Serial.print(F("red="));
    Serial.print(redBuffer[i], DEC);
    Serial.print(F(", ir="));
    Serial.println(irBuffer[i], DEC);
  }

  //calculate heart rate and SpO2 after first 100 samples (first 4 seconds of samples)
  maxim_heart_rate_and_oxygen_saturation(irBuffer, bufferLength, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate);

  //Continuously taking samples from MAX30102.  Heart rate and SpO2 are calculated every 1 second
  while (1)
  {
    //dumping the first 25 sets of samples in the memory and shift the last 75 sets of samples to the top
    for (byte i = 25; i < 100; i++)
    {
      redBuffer[i - 25] = redBuffer[i];
      irBuffer[i - 25] = irBuffer[i];
    }

    //take 25 sets of samples before calculating the heart rate.
    for (byte i = 75; i < 100; i++)
    {
      while (particleSensor.available() == false) //do we have new data?
        particleSensor.check(); //Check the sensor for new data

      digitalWrite(readLED, !digitalRead(readLED)); //Blink onboard LED with every data read

      redBuffer[i] = particleSensor.getRed();
      irBuffer[i] = particleSensor.getIR();
      particleSensor.nextSample(); //We're finished with this sample so move to next sample

      //send samples and calculation result to terminal program through UART
      Serial.print(F("red="));
      Serial.print(redBuffer[i], DEC);
      Serial.print(F(", ir="));
      Serial.print(irBuffer[i], DEC);

      Serial.print(F(", HR="));
      Serial.print(heartRate, DEC);

      Serial.print(F(", HRvalid="));
      Serial.print(validHeartRate, DEC);

      Serial.print(F(", SPO2="));
      Serial.print(spo2, DEC);

      Serial.print(F(", SPO2Valid="));
      Serial.println(validSPO2, DEC);
    }

    //After gathering 25 new samples recalculate HR and SP02
    maxim_heart_rate_and_oxygen_saturation(irBuffer, bufferLength, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate);
  }
}

What says that? Your program? If so, what causes that to be said?

the serial monitor. i am not sure but i am guessing the buffer issue of the arduino. it doesn't have the sram to take large data

Take a look in the MAX30105 source, and see what causes the "begin" method to return a bad error code, and work back from there.
When I get back to a PC, I'll get the library for myself and take a look.