Title: Vital Care
By: Md. Kamrul Hussain
Project Category: Design Challenge
Project Name: Design For A Cause 2021
Blog post: 03
Previous Blogs:
Blog No. | Title |
---|---|
01 | Introduction |
02.a | SpO2 basics [PPG] |
In my previous blog I shared my experiment with PPG signal from scratch, using basic transimpedance amplifier and filter circuits. In this blog I'll share my work on measuring Heart Rate and SPO2 using MAX30102 module. I am using the MAXREFDES117# which utilizes the heart-rate/SpO2 sensor (MAX30102), an efficient, low-power step-down converter (MAX1921), and an accurate level translator (MAX14595). The entire design typically operates at less than 5.5mW and the module can be placed on a person’s fingertip, earlobe, or other fleshy extremity.
The step-down converter MAX1921 converts the 2V to 5.5V supply input and generates the 1.8V rail for the heart-rate sensor. The MAX14595 level translator provides an interface between the heart-rate/SpO2 sensor and the controller board, which generally use a different logic level.
System Diagram: Functional Diagram:
I have used Arduino NANO 33 IOT to configure the MAX30102 module and read sampled data, calculated heart rate and SpO2 and display through a terminal program. I have utilized the RD117_ARDUINO library from MAXIM, which was modified by Sparkfun for their MAX30105 particle sensor module. Both MAX30102 and MAX30105 shares the same chip identification number, therefore the library works for both the chips. I slightly modified it according to my requirements where the process flow is shown below.
Process flow:
Configuration:
Sampling Rate and Pulse width -
A pulse width tells how long in time a signal is active for. In terms of power consumption, the longer a signal is active, the more energy it consumes. The drive frequency for the LEDs is selected by the sample rate. Correspondingly, a higher sample rate results in a higher drive frequency, thereby consuming more energy. In this Arduino platforms store 4 seconds of samples collected at 25sps. A Pulse width of 411usec is chosen which gives 18bit resolution.
Mode-
SpO2 mode [0x011] is selected and ADC is configured for Full Scale range at 4096nA
The LED current can be programmed from 0 to 50mA with proper supply voltage. At finger tip 0x1B [~6mA] provides enough strong PPG signals to process.
Post-processing requires several steps which include filtering, peak-to-peak detection, normalization, and digital signal processing.
PPG signal has two peaks - systolic and diastolic. Heart rate is found by measuring the time between consecutive systolic peaks and then multiplying it by 60.
DC and AC components should be verified first. The next step is to normalize the two (by taking the ratio of AC to DC) and then finding a value “R,” which is the ratio of normalized Red to normalized IR data. Finally, a linear approximation can be used to find SpO2. AN6409 application note from Maxim Integrated presents an effective concept of finding out the DC and AC components from the PPG signal.
the DC component was found by using a straight-line approximation between two valleys. Let’s label the points first. Call the valley between 50 to 100 “1” and the valley between 150 to 200 “3.” The peak between 150 and 200 is called “2.”
The DC component is the offset of the AC signal, which can be found by first drawing a line between 1 and 3 and then drawing a line parallel to the y axis from 2 to the line connecting 1 and 3. The DC value is the point where the two lines intersect.
The AC component on the other hand is the distance between the peak (“2”) and the DC value.
The AC and DC components are labeled above each graph. The following equations were used to first find “R” and then SpO2.
SpO2 = 104 – 17R = 104 – 17 (0.43) = 96.8%
Output:
{gallery} MAX30102 output |
---|
Serial Terminal prints: RED and IR PPG values and calculated Heart Rate with SpO2 |
Plot PPG: Lower red plot represents RED PPG and upper orange plot represents IR PPG |
Plot PPG HR and SpO2: Heart Rate and SpO2 variations are plotted for the time being with PPG signals. Both HR and SpO2 plots are multiplied by 1000 to fit with the PPG plot for better visualization |
#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[50]; //infrared LED sensor data uint16_t redBuffer[50]; //red LED sensor data #else uint32_t irBuffer[50]; //infrared LED sensor data uint32_t redBuffer[50]; //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 = 9; //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: while (!Serial); 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 = 27; //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 = 50; //buffer length of 50 stores 2 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 50 samples (first 2 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 15 sets of samples in the memory and shift the last 15 sets of samples to the top for (byte i = 15; i < 50; i++) { redBuffer[i - 15] = redBuffer[i]; irBuffer[i - 15] = irBuffer[i]; } heartRate = heartRate * 1000; spo2 = spo2 * 1000; //take 15 sets of samples before calculating the heart rate. for (byte i = 35; i < 50; 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('\t'); Serial.print(F("IR= ")); Serial.print(irBuffer[i], DEC); Serial.print('\t'); Serial.print(F("HR= ")); Serial.print(heartRate, DEC); Serial.print('\t'); //Serial.print(F(", HRvalid=")); //Serial.print(validHeartRate, DEC); Serial.print(F("SPO2= ")); Serial.println(spo2, DEC); //Serial.print(F(", SPO2Valid=")); //Serial.println(validSPO2, DEC); } //After gathering 15 new samples recalculate HR and SP02 maxim_heart_rate_and_oxygen_saturation(irBuffer, bufferLength, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate); } }