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);
}
}












