Since my last blog, I have tried a lot of things, with the sensors I already had.

 

The original plan was to integrate the newer MAX30102 and replace the MAX30100 with it to measure SPO2, HRV and HR more accurately. I was a bit surprised when the sensor did not perform up to my expectations. On digging deeper, I identified my sensor to be a cheap clone; with its IR and red led channels reversed. Also, it did not have a green led installed, which is available in another variant of the MAX3010X series. This was the reason behind the erroneous readings by the sensor. There are a few workarounds as mentioned clearly in this post, but I was lagging behind so I decided to go with the original MAX30100 sensor.

 

I also delved deeper into the Arduino IOT cloud and its working. The limitation that I am seeing the cloud suffers from, (for some reason) is that time based interrupts (like the heart beat detection function in MAX30100 library) fails to be called or detected. I might be missing something but for now it seems that continuous sensors that just measure data directly work very well with the cloud, while the one's that measure time intervals or call interrupts do not work as it seems to interfere somehow with the cloud update function of the cloud itself. I have made a topic on the Arduino Cloud forum regarding the same. So I plan to use the ThingSpeak cloud instead.

 

Lastly, since my country is facing the worst of this second wave of the corona virus, I am unable to get more sensors or materials for an indefinite time period. Even getting PCBs developed is difficult given the current circumstances. Hence, I have decided to make changes to my BuzzBeat v1.0 GP board. I will also try to measure RR and RRV using an analog microphone. But since it might be a bit inconvenient to place the mic such that it can measure breathing noises, I have considered 2 possibilities - attaching an analog mic to my gp board so that a person can breathe onto it or use a mobile phone to capture audio samples for analysis via our mobile app, whichever is more convenient and gives better performance.

 

First, I modified the gp board I used for Buzzbeat v1.0 to incorporate the MLX90614, ECG AD8232 module, OLED 1.3" display, MAX30100 sensor and the Nano 33 IOT. The images are as attached below -

 

So as you can see, there is the ECG sensor, MAX30100 sensor, MLX90614 sensor, etc. connected to my board. There are also a few headers that are used to connect other things like the mic for audio samples and future expansion.

 

I then modified my code such that my system can work in 2 parts. While I tried and tested my code I realized that integrating so many sensors together was very difficult, as the readings of MAX30100 started to get erroneous due to missed readings or false readings, especially for time-critical parameters like Heart rate variability which is measured in milliseconds. Secondly, my vital parameters were gonna be of two types -

1. The ones that vary instantaneously (ECG analog values, PPG analog values, audio analog values)

2. The ones that remain fairly same for at least a few seconds (RR, RRV, HRV, Body Temperature, SPO2 and HR)

Also, Using DSP on an arduino to calculate RR and RRV from breathing sounds taken by the mic was not a good way. So what I did was this - I pushed all the instantaneously changing values like IR PPG signals, Red light PPG Signals, audio signals and ecg signals via serial or local wifi to an app or software so that the computer or mobile phone could analyze the signals and handle tougher math. So I would send the following data to the app -

  1. ECG analog values
  2. Breathing audio signal values
  3. IR LED PPG analog values
  4. Red LED PPG analog values
  5. SPO2
  6. HR
  7. HRV
  8. Body Temperature

 

And these values would be then used to calculate the following on the mobile app -

  1. ECG Signal (from ECG analog values)
  2. PPG Signal (from IR and Red LED analog values)
  3. RR (from breathing audio signal)
  4. RRV (from breathing audio signal)

 

Hence we would have the following data with us finally -

  • Cardio vitals: HR, HRV
  • Pulmonary vitals: RR, RRV
  • Blood related vitals: SPO2
  • Body related vitals: Body temperature
  • Signals: ECG, PPG, Breathing audio

Sounds good. First, we proceed to the microcontroller part. That will be the topic for this blog.

 

We will be using the following sensors:

  • AD8232 for ECG
  • MAX30100 for SPO2, PPG signals and HRV
  • MLX90614 for body temperature
  • OLED Display
  • A mic module for capturing breathing noise

We will be using it as mentioned in the code below:

 

We will be using CSV format to parse data via WiFi to our app. The code has been attached. Analog Outputs of ecg and mics have been connected to A7 and A6 of Arduino Nano 33 IOT respectively. The remaining pins used are A5 and A4 which are SCL and SDA respectively, 5V, 3V3 and GND. Some optional pins that you can use are digital pins for setting the mic threshold to detect a particular amplitude of sound, or to detect whether or not the leads of ECG sensor are attached or not.

#include <Wire.h>
#include "MAX30100_PulseOximeter.h"
#include "MAX30100.h"
#include <Adafruit_MLX90614.h>
#include <Adafruit_GFX.h>
#include <U8x8lib.h>
#include <avr/dtostrf.h>


#define REPORTING_PERIOD_MS     1000


// PulseOximeter is the higher level interface to the sensor
// it offers:
//  * beat detection reporting
//  * heart rate calculation
//  * SpO2 (oxidation level) calculation


PulseOximeter pox;
Adafruit_MLX90614 mlx = Adafruit_MLX90614();
U8X8_SH1106_128X64_NONAME_HW_I2C u8x8(/* reset=*/ U8X8_PIN_NONE);
MAX30100 sensor;


uint32_t tsLastReport = 0;
int hr, spo2, count = 0, flag = 0, PatientID = 0, ecg = 0;
float ambient, object, hrv;
long time1 = 0, time2 = 0; 
uint16_t ir = 0, red = 0;
char str_hrv[10], str_object[10], str_ambient[10];
// Callback (registered below) fired when a pulse is detected
void onBeatDetected()
{
    Serial.println("Beat!");
    time1 = micros() - time2;
    time2 = micros();
}


void setup()
{
    //display connected
    u8x8.begin();
    u8x8.setPowerSave(0);
    u8x8.setFont(u8x8_font_chroma48medium8_r);
    //pinMode(10, INPUT); // Setup for leads off detection LO +
    //pinMode(11, INPUT); // Setup for leads off detection LO -
    
    Serial.begin(115200);
    while (!Serial) {
      ; // wait for serial port to connect. Needed for native USB port only
    }
    Serial.print("Initializing pulse oximeter..");


    // Initialize the PulseOximeter instance
    // Failures are generally due to an improper I2C wiring, missing power supply
    // or wrong target chip
    if (!pox.begin(PULSEOXIMETER_DEBUGGINGMODE_RAW_VALUES)) {
        Serial.println("FAILED");
        for(;;);
    } else {
        Serial.println("SUCCESS");
    }


    // The default current for the IR LED is 50mA and it could be changed
    //   by uncommenting the following line. Check MAX30100_Registers.h for all the
    //   available options.
    pox.setIRLedCurrent(MAX30100_LED_CURR_7_6MA);


    // Register a callback for the beat detection
    pox.setOnBeatDetectedCallback(onBeatDetected);
    mlx.begin();
    time2 = micros();
}


void loop()
{
    // Make sure to call update as fast as possible
    pox.update();
    sensor.update();
    
    while (sensor.getRawValues(&ir, &red)) {}
    
    ecg = analogRead(A7);
    mic = analogRead(A6);
    delay(1);
    
    // Asynchronously dump heart rate and oxidation levels to the serial
    // For both, a value of 0 means "invalid"
    if (millis() - tsLastReport > REPORTING_PERIOD_MS) {
        hr = pox.getHeartRate();
        spo2 = pox.getSpO2();
        ambient = mlx.readAmbientTempF();
        object = mlx.readObjectTempF();
        hrv = (60000000/hr - (float)time1)/1000;
        //send_serial();
        tsLastReport = millis();
        count++;
        flag = 0;
    }
    if ((count%10 == 0) && (flag != 1)) {
      flag = 1;
      Serial.println(count);
      Wire.end();
      send_display(); 
      Telemetry();
      Wire.begin();     
    }
}


void send_serial() {
  Serial.print("bpm / SpO2:");
  Serial.print("Heart rate:");
  Serial.print(hr);
  Serial.print(spo2);
  Serial.print("% / hrv:");
  Serial.print(hrv);
  Serial.print("ms / Ambient:");
  Serial.print(ambient); 
  Serial.print("*F / tObject = "); 
  Serial.print(object); 
  Serial.println("*F");
}


void send_display() {
  u8x8.clearDisplay();
  u8x8.setCursor(0,1);
  u8x8.print("HRV:"); 
  u8x8.print(hrv);
  u8x8.print(" ms");
  u8x8.setCursor(0,2);
  u8x8.print("SpO2:");
  u8x8.print(spo2);
  u8x8.print(" %");
  u8x8.setCursor(0,3);
  u8x8.print("HR:");
  u8x8.print(hr);
  u8x8.print(" bpm");
  u8x8.setCursor(0,4);
  u8x8.print("Temp:");
  u8x8.print(object);
  u8x8.print(" degF");
  u8x8.setCursor(0,5);
  u8x8.print("IR:");
  u8x8.print(ir);
  u8x8.setCursor(0,6);
  u8x8.print("Red:");
  u8x8.print(red);
  //delay(200); 
}


void Telemetry() {
  char buffer[150];
  dtostrf(hrv, 4, 2, str_hrv);
  dtostrf(object, 4, 2, str_object);
  dtostrf(ambient, 4, 2, str_ambient);
  sprintf(buffer,"%d,%d,%d,%d,%s,%s,%s,%d,%d,%d,%dn",PatientID,count/10,hr,spo2,str_hrv,str_object,str_ambient,ir,red,ecg,mic);
  Serial.println();
  Serial.println(buffer);
}

 

This is the demonstration of what the above code and hardware do. The parameters are pretty accurately measured and the prototype looks much better than I expected. I tested and compared the results with a standard oximeter and they matched perfectly! Also, the MLX90614 sensor shows a bit lower temperature reading than it should as it measures the temperature of fingers. We can add the offset between the oral and peripheral temperatures to compensate for this error. The display values are updated every 10 seconds, you can easily change that by editing the above code.

 

In the next blog, we will capture some signals on the edge impulse platform using a serial port and then build some models to identify a few conditions like fever or breathlessness.