Introduction: Guide to Using MAX30102 Heart Rate and Oxygen Sensor With Arduino

Are you interested in using the MAX30102 heart rate and oxygen sensor with Arduino? In this article, let's explore how to use the MAX30102 module to measure oxygen saturation (SpO2) and heart rate (HR) through communication with Arduino.

You will also learn about the functionalities of the sensor, how to connect it to Arduino, and program it to collect SpO2 and HR data. By applying this knowledge, you can create medical applications, health monitoring systems, or other creative projects related to measuring heart rate and blood oxygen levels.

Step 1: Overview of MAX30102 Heart Rate and Oxygen Sensor

This module integrates the advanced MAX30102 IC (an upgrade from MAX30100) from Analog Devices, used for measuring oxygen saturation (SpO2) and heart rate (HR). It combines two LEDs, a photodetector, optimized optics, and low-noise analog signal processing to detect SpO2 and HR signals.

On the backside of the module, there are two LEDs, including a red LED and an infrared LED. On the opposite side, there is a highly sensitive photodetector. The idea is to shine a single LED at a time, detect the reflected light from the sensor, and based on the acquired signature, measure the oxygen level in the blood and heart rate.

Step 2: Power Supply

The MAX30102 heart rate sensor operates at two different voltage ranges: 1.8V for the IC and 3.3V for the LEDs and IR. Therefore, the module integrates a voltage regulator for both 3.3V and 1.8V.

On the backside of the module, you will find a solder jumper used to select between 3.3V and 1.8V logic levels. By default, it is set to 3.3V logic level, suitable for Arduino. However, you can also choose the 1.8V logic level according to specific requirements.

One of the most important features of the MAX30102 sensor is its low power consumption: MAX30102 consumes about 600μA during measurement. Additionally, it can be set to standby mode with a consumption of only 0.7μA. The low power consumption allows deployment in battery-powered devices such as handheld devices, wearables, or smartwatches.

Step 3: Temperature Sensor

MAX30102 has a built-in temperature sensor on the module, used to compensate for environmental changes and calibrate the measurements.

This temperature sensor is reasonably accurate and can measure temperatures ranging from -40˚C to +85˚C with an accuracy of ±1˚C.

Step 4: I2C Communication

The module utilizes I2C communication, using the SDA and SCL data lines to communicate with Arduino. The default I2C addresses are 0xAE HEX (for write operations) and 0xAF HEX (for read operations).

Step 5: FIFO Buffer

The FIFO buffer in the MAX30102 heart rate and oxygen sensor is used to store the measured heart rate and blood oxygen level samples.

The FIFO buffer in the MAX30102 sensor can be configured to store from 32 to 256 data samples, depending on your settings. When the buffer reaches its storage limit, new data samples will overwrite the old ones, following the First-In-First-Out principle.

Step 6: Interrupts

MAX30102 can utilize interrupts for programming. Interrupts can be activated in the following five cases:

  • Power Ready: Activated when the power is turned on or after a power loss. This interrupt notifies the microcontroller that MAX30102 has completed the startup process and is ready for operation.
  • New Data Ready: Activated after each acquisition of SpO2 and heart rate (HR) data samples. This interrupt indicates that new data is ready to be read from the MAX30102 sensor's FIFO memory.
  • Ambient Light Cancellation: Activated when the ambient light cancellation function of the SpO2/HR optical diode reaches its maximum limit and affects the output of the ADC. This interrupt informs about adjusting the ambient light to ensure measurement quality.
  • Temperature Ready: Activated when the temperature conversion process inside MAX30102 is completed. This interrupt indicates that the latest temperature data is ready to be read.
  • FIFO Almost Full: This interrupt alerts the microcontroller that the FIFO is nearing full capacity and needs to be read and processed to avoid data loss.

Step 7: How MAX30102 Heart Rate and Oxygen Sensor Works

The MAX30102 heart rate and oxygen sensor operates based on the principle of light absorption by blood in tissues and blood vessels. MAX30102 utilizes infrared (IR) and red light reflection technology to measure the oxygen saturation (SpO2) and heart rate (HR) of the user.

The sensor incorporates infrared and red LEDs, along with a photonic filter, to generate appropriate light that penetrates the skin and red-colored tissues. The infrared LED penetrates deeper into the skin, while the red LED penetrates further away. When the light is projected through the skin, it encounters reflection from the flowing blood in the blood vessels beneath the skin.

The sensor employs photodiodes to measure the amount of light reflected by the blood. The photodiodes receive the light signals and convert them into analog data. This data is then amplified and converted into digital data by an analog-to-digital converter (ADC).

Step 8: Pinout MAX30102 Module

  • VIN: Positive supply voltage (3.3V or 5V)
  • SCL: Clock signal pin
  • SDA: Data pin
  • INT: Interrupt pin, used to activate interrupts for events
  • IRD: Infrared data pin, this is the output data pin for the infrared sensor
  • RD: Red LED data pin, used for measuring oxygen saturation (SpO2) and heart rate (HR)
  • GND: Ground pin

Step 9: Connection Diagram of MAX30102 Heart Rate and Oxygen Sensor With Arduino.

Step 10: Code BPM

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

MAX30105 particleSensor;

const byte RATE_SIZE = 4; //Increase this for more averaging. 4 is good.
byte rates[RATE_SIZE]; //Array of heart rates
byte rateSpot = 0;
long lastBeat = 0; //Time at which the last beat occurred

float beatsPerMinute;
int beatAvg;

void setup() {
Serial.begin(115200);
Serial.println("Initializing...");

// Initialize sensor
if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) {
Serial.println("MAX30102 was not found. Please check wiring/power. ");
while (1);
}
Serial.println("Place your index finger on the sensor with steady pressure.");

particleSensor.setup(); //Configure sensor with default settings
particleSensor.setPulseAmplitudeRed(0x0A); //Turn Red LED to low to indicate sensor is running
particleSensor.setPulseAmplitudeGreen(0); //Turn off Green LED
}

void loop() {
long irValue = particleSensor.getIR();

if (checkForBeat(irValue) == true) {
//We sensed a beat!
long delta = millis() - lastBeat;
lastBeat = millis();

beatsPerMinute = 60 / (delta / 1000.0);

if (beatsPerMinute < 255 && beatsPerMinute > 20) {
rates[rateSpot++] = (byte)beatsPerMinute; //Store this reading in the array
rateSpot %= RATE_SIZE; //Wrap variable

//Take average of readings
beatAvg = 0;
for (byte x = 0 ; x < RATE_SIZE ; x++)
beatAvg += rates[x];
beatAvg /= RATE_SIZE;
}
}

Serial.print("IR=");
Serial.print(irValue);
Serial.print(", BPM=");
Serial.print(beatsPerMinute);
Serial.print(", Avg BPM=");
Serial.print(beatAvg);

if (irValue < 50000)
Serial.print(" No finger?");

Serial.println();
}

Step 11: Code Sp02

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