top of page

The MAX30102: Tiny Sensor, Big Potential

  • Writer: Hunter Treleaven
    Hunter Treleaven
  • Oct 19, 2025
  • 5 min read

Table of Contents


Small Tech, Big Impact

The MAX30102 is one of the smallest yet most capable sensors used in biomedical prototyping — including my ongoing CardioTrack project. It measures heart rate and blood oxygen levels (SpO₂) using just light, making it ideal for low-cost, portable health-tech experiments.



How It Works

This sensor uses two LEDs — one red and one infrared — that shine through the skin. The light that reflects back changes with every heartbeat as oxygen-rich and oxygen-poor blood absorb light differently.


By comparing how much red and infrared light returns, the MAX30102 can calculate both heart rate and oxygen saturation in real time.


It’s the same core principle used in pulse oximeters — just scaled down for makers, researchers, and innovators.



Built for Precision and Efficiency

Despite its size, the MAX30102 is smartly engineered:

  • FIFO Buffer – Stores up to 32 samples, so no data is lost when the microcontroller is busy.

  • Interrupt Alerts – Notifies the MCU when new data is ready or memory is nearly full.

  • Temperature Compensation – Adjusts readings automatically for changing environments.

  • Low Power Use – Runs efficiently between 3.3–5V, drawing less than 1 mA.


These features make it powerful enough for advanced applications but efficient enough for wearables or battery-based systems.



Easy Integration

The sensor connects via I²C — just two wires for data and clock — making it compatible with microcontrollers like the ESP32, Arduino, or Raspberry Pi.


Developers can skip complex setup by using the SparkFun MAX3010x Library, which handles all data communication behind the scenes.



Why It Matters

The MAX30102 proves how much can be done with small, affordable components.

Within CardioTrack, it’s a key example of how microcontrollers and biomedical sensors can work together to collect, visualize, and timestamp vital signs — bridging the gap between education and innovation in healthcare tech.



Learn More



Measuring Heart Rate (Code)

/*

Heart Rate Monitor using MAX30102 Optical Sensor

------------------------------------------------------------

Description:

This program uses the MAX30102 pulse oximeter and heart rate sensor

to detect heartbeats and calculate the user's beats per minute (BPM).

It averages recent BPM readings for smoother output.


Hardware:

- MAX30102 connected via I2C (SDA, SCL)

- Uses onboard IR LED for pulse detection


Libraries Required:

- Wire.h (for I2C communication)

- MAX30105.h (SparkFun MAX3010x sensor library)

- heartRate.h (for the checkForBeat() algorithm)


Project: CardioTrack

-----------------------------------------------------------

*/

#include <Wire.h>

#include "MAX30105.h"

#include "heartRate.h"


// Create sensor object

MAX30105 particleSensor;


// Heart Rate Data Variables

const byte RATE_SIZE = 4; // Number of samples to average (higher = smoother, slower)

byte rates[RATE_SIZE]; // Stores recent BPM readings

byte rateSpot = 0; // Index pointer for the circular buffer

long lastBeat = 0; // Timestamp (ms) of last detected heartbeat


float beatsPerMinute; // Instantaneous BPM value

int beatAvg; // Rolling average of recent BPM readings


// SETUP FUNCTION

void setup() {

Serial.begin(9600);

Serial.println("Initializing...");


// Initialize the MAX30102 sensor via I2C

if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) {

Serial.println("MAX30102 not found. Check wiring or power connections.");

while (1); // Stop execution if sensor not detected

}


Serial.println("Place your index finger on the sensor with steady pressure.");


// Configure sensor LED and sampling settings

particleSensor.setup(); // Load default configuration

particleSensor.setPulseAmplitudeRed(0x0A); // Dim red LED to show sensor is active

particleSensor.setPulseAmplitudeGreen(0x00); // Disable green LED (not needed for HR)

}


void loop() {

// Read the infrared (IR) value from the sensor

long irValue = particleSensor.getIR();


// Heartbeat detection logic

if (checkForBeat(irValue) == true) {

// A heartbeat was detected!

long delta = millis() - lastBeat; // Time since last beat (ms)

lastBeat = millis();


// Convert delta time into BPM

beatsPerMinute = 60 / (delta / 1000.0);


// Filter out unrealistic values

if (beatsPerMinute < 255 && beatsPerMinute > 20) {

// Store reading in the circular buffer

rates[rateSpot++] = (byte)beatsPerMinute;

rateSpot %= RATE_SIZE; // Wrap index when reaching end of buffer


// Calculate rolling average BPM

beatAvg = 0;

for (byte x = 0; x < RATE_SIZE; x++)

beatAvg += rates[x];

beatAvg /= RATE_SIZE;

}

}


// Serial Output (for monitoring via Serial Plotter/Monitor)

Serial.print("IR=");

Serial.print(irValue);

Serial.print(", BPM=");

Serial.print(beatsPerMinute);

Serial.print(", Avg BPM=");

Serial.print(beatAvg);


// If IR signal is low, assume no finger is on the sensor

if (irValue < 50000)

Serial.print(" No finger?");


Serial.println();


}



Measuring Blood Oxygen Levels (C0de)

/*

MAX30102 SpO₂ and Heart Rate Monitoring

----------------------------------------------------------------

Description:

This sketch interfaces with the MAX30102 optical sensor to measure

heart rate (in BPM) and blood oxygen saturation (SpO₂).

It continuously samples infrared (IR) and red LED signals, applies

a signal-processing algorithm, and outputs results via Serial Monitor.


Hardware:

    - MAX30102 connected via I2C (SDA, SCL)

    - Uses onboard IR LED for pulse detection


  Libraries Required:

    - Wire.h (for I2C communication)

    - MAX30105.h (SparkFun MAX3010x sensor library)

    - spo2_algorithm.h (for SpO₂ & heart rate computation)


  Project: CardioTrack

---------------------------------------------------------------

Notes:

- Place your finger firmly but gently on the sensor.

- Do not move during measurement to reduce noise.

- Works on most Arduino-compatible boards (ESP32, Mega, Uno).

- UNO has memory limitations; this code adapts accordingly.

*/


#include <Wire.h>

#include "MAX30105.h"

#include "spo2_algorithm.h"


// Global Sensor Object

MAX30105 particleSensor;


#define MAX_BRIGHTNESS 255 // LED brightness limit


// Data Buffers for Sensor Readings

#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)

uint16_t irBuffer[100]; // Infrared LED data

uint16_t redBuffer[100]; // Red LED data

uint32_t irBuffer[100]; // Infrared LED data

uint32_t redBuffer[100]; // Red LED data


// Computation Variables

int32_t bufferLength; // Number of samples to process

int32_t spo2; // Calculated SpO₂ percentage

int8_t validSPO2; // Validity flag (1 = valid, 0 = invalid)

int32_t heartRate; // Calculated heart rate (BPM)

int8_t validHeartRate; // Validity flag (1 = valid, 0 = invalid)


// LED Indicators

byte pulseLED = 11; // PWM LED for pulse feedback

byte readLED = 13; // Blinks with each new data read


void setup() {

Serial.begin(9600);

Serial.println("Initializing MAX30102 sensor...");


pinMode(pulseLED, OUTPUT);

pinMode(readLED, OUTPUT);


// Initialize the sensor

if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) {

Serial.println(F("MAX30105 was not found. Check wiring/power."));

while (1); // Halt program

}


Serial.println(F("Attach finger to sensor using gentle pressure."));

Serial.println(F("Press any key to begin data acquisition."));

while (Serial.available() == 0); // Wait for user input

Serial.read(); // Clear input buffer



// Sensor Configuration

byte ledBrightness = 60; // 0 = Off → 255 = 50mA

byte sampleAverage = 4; // Number of samples to average

byte ledMode = 2; // 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green

byte sampleRate = 100; // Sampling frequency (Hz)

int pulseWidth = 411; // Pulse width in µs (higher = more light)

int adcRange = 4096; // ADC range in nA (2048–16384)


// Apply configuration

particleSensor.setup(ledBrightness, sampleAverage, ledMode,

sampleRate, pulseWidth, adcRange);


Serial.println(F("Sensor setup complete. Collecting data..."));

}



void loop() {

bufferLength = 100; // 100 samples ≈ 4 seconds at 25 samples/sec



// Initial Sampling Phase

for (byte i = 0; i < bufferLength; i++) {

while (particleSensor.available() == false)

particleSensor.check(); // Wait for new sample


redBuffer[i] = particleSensor.getRed();

irBuffer[i] = particleSensor.getIR();

particleSensor.nextSample(); // Move to next sample slot


// Display raw readings

Serial.print(F("red="));

Serial.print(redBuffer[i], DEC);

Serial.print(F(", ir="));

Serial.println(irBuffer[i], DEC);

}


// Compute baseline SpO₂ and heart rate

maxim_heart_rate_and_oxygen_saturation(

irBuffer, bufferLength,

redBuffer, &spo2, &validSPO2,

&heartRate, &validHeartRate);



// Continuous Monitoring Loop

while (1) {

// Shift last 75 samples up, discard oldest 25

for (byte i = 25; i < 100; i++) {

redBuffer[i - 25] = redBuffer[i];

irBuffer[i - 25] = irBuffer[i];

}


// Capture 25 new samples

for (byte i = 75; i < 100; i++) {

while (particleSensor.available() == false)

particleSensor.check();


digitalWrite(readLED, !digitalRead(readLED)); // Blink read LED


redBuffer[i] = particleSensor.getRed();

irBuffer[i] = particleSensor.getIR();

particleSensor.nextSample();


// Output current sample and prior computed values

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

}


// Recalculate HR and SpO₂ using latest 100-sample window

maxim_heart_rate_and_oxygen_saturation(

irBuffer, bufferLength,

redBuffer, &spo2, &validSPO2,

&heartRate, &validHeartRate);

}


}


 
 
 

Comments

Rated 0 out of 5 stars.
No ratings yet

Add a rating
bottom of page