Table of Contents
- What Is the MAX30102 Sensor?
- How Pulse Oximetry and SpO2 Measurement Work
- MAX30102 Module Pinout and Specifications
- Wiring MAX30102 to Arduino via I2C
- Library Setup and Installation
- Basic Heart Rate Sketch
- Full SpO2 + Heart Rate Project
- Adding an OLED Display
- Calibration and Accuracy Considerations
- Low-Power Mode for Battery Projects
- Troubleshooting Common Issues
- Frequently Asked Questions
Wearable health monitoring was once the exclusive domain of medical-grade equipment costing thousands of rupees. The MAX30102 pulse oximeter and heart rate sensor has changed that completely. For under ₹200, you get a fully integrated optical biosensor that can measure both blood oxygen saturation (SpO2) and heart rate — and connect it directly to an Arduino in minutes.
This tutorial covers everything from the physics of pulse oximetry to a complete working Arduino project that displays SpO2 percentage and BPM on an OLED screen. Whether you are building a fitness band prototype, a patient monitoring POC, or simply exploring biosensors, this guide has you covered.
What Is the MAX30102 Sensor?
The MAX30102 is a highly integrated biosensor IC from Maxim Integrated (now part of Analog Devices). It combines two LEDs (red at 660 nm and infrared at 880 nm), a photodetector, optical elements, and low-noise analog signal processing in a single tiny 5.6 mm × 3.3 mm surface-mount package.
The IC is specifically designed for reflective photoplethysmography (PPG) — measuring the tiny changes in light absorption caused by pulsing blood flow through the skin. By analysing these light fluctuations, it calculates both:
- Heart rate (BPM): The frequency of the pulse waveform directly corresponds to heartbeats per minute
- SpO2 (blood oxygen saturation): Oxygenated and deoxygenated haemoglobin absorb red and infrared light differently; the ratio of these absorptions reveals oxygen saturation percentage
The MAX30102 communicates via I2C at a fixed address of 0x57, making it one of the easiest biosensors to interface with any microcontroller that has I2C support — which includes virtually every Arduino, ESP8266, ESP32, STM32, and Raspberry Pi.
How Pulse Oximetry and SpO2 Measurement Work
Understanding the physics here helps you get better readings and troubleshoot problems effectively.
Photoplethysmography (PPG): Blood absorbs light. When the heart beats, it forces a pulse of blood through the capillaries in your fingertip. This pulse briefly increases the total volume of blood in the tissue, which absorbs slightly more light. The MAX30102’s photodetector captures these tiny intensity variations in reflected light to construct the PPG waveform.
SpO2 calculation: Haemoglobin (Hb) and oxyhaemoglobin (HbO2) have different absorption spectra. Oxygenated haemoglobin absorbs more infrared light but less red light. Deoxygenated haemoglobin does the opposite. By measuring the AC/DC ratio of the red channel versus the infrared channel — a value called the ratio of ratios (R) — the sensor estimates SpO2 using a calibration equation:
SpO2 ≈ −45.060 × R² + 30.354 × R + 94.845
This empirical equation is derived from calibration against laboratory co-oximeters. It is accurate to within ±2% for SpO2 values between 70% and 100% under ideal conditions — comparable to consumer pulse oximeters.
Why finger placement matters: The PPG signal quality depends heavily on having a clean optical path. Motion artefacts, ambient light leakage, and insufficient pressure on the sensor all degrade the signal. The MAX30102 module typically has a small cavity where you rest your fingertip — ensure your finger covers the entire sensor area and remains still during measurement.
MAX30102 Module Pinout and Specifications
Most MAX30102 breakout modules expose 6–8 pins. The essential ones are:
| Pin | Description |
|---|---|
| VIN | Power supply: 1.8 V – 5.5 V (module has onboard 1.8 V regulator) |
| GND | Ground |
| SDA | I2C data line |
| SCL | I2C clock line |
| INT | Interrupt output (active LOW) — fires when FIFO data is ready |
| RD | Red LED current control (on some modules, connected to a header) |
Key specifications:
- Supply voltage: 1.8 V – 5.5 V (VIN; most modules have a built-in 3.3 V / 1.8 V LDO)
- I2C address: 0x57 (fixed)
- LED wavelengths: 660 nm (red), 880 nm (IR)
- ADC resolution: 18-bit
- Sample rates: 50 Hz to 3200 Hz configurable
- FIFO depth: 32 samples
- Shutdown current: 0.7 µA
- Operating temperature: −40 °C to +85 °C
Wiring MAX30102 to Arduino via I2C
The MAX30102 uses I2C, so you only need 4 wires:
| MAX30102 Pin | Arduino Uno/Nano | ESP8266 (NodeMCU) | ESP32 |
|---|---|---|---|
| VIN | 5 V | 3.3 V | 3.3 V |
| GND | GND | GND | GND |
| SDA | A4 | D2 (GPIO4) | GPIO21 |
| SCL | A5 | D1 (GPIO5) | GPIO22 |
The INT pin is optional for basic projects — leave it unconnected if you plan to poll the sensor in your main loop. For interrupt-driven or low-power designs, connect INT to a GPIO configured as an external interrupt input.
Most MAX30102 modules include onboard 4.7 kΩ pull-up resistors on SDA and SCL. If your module does not, add them externally between each I2C line and VIN.
Library Setup and Installation
The best library for the MAX30102 on Arduino is the SparkFun MAX3010x Pulse and Proximity Sensor Library by SparkFun Electronics. It is well-maintained, includes heart rate and SpO2 algorithms, and works with Arduino, ESP8266, and ESP32.
Install it via the Arduino Library Manager:
- Open Arduino IDE → Sketch → Include Library → Manage Libraries
- Search for MAX3010x
- Install the library by SparkFun Electronics
Alternatively, install via PlatformIO:
lib_deps = sparkfun/SparkFun MAX3010x Pulse and Proximity Sensor Library
The library includes several example sketches under File → Examples → SparkFun MAX3010x Pulse and Proximity Sensor Library. Start with the Example1_GettingStarted sketch to verify your wiring before writing custom code.
Basic Heart Rate Sketch
This minimal sketch uses the SparkFun library’s built-in beat detection algorithm to read heart rate from the IR channel:
#include <Wire.h>
#include "MAX30105.h"
#include "heartRate.h"
MAX30105 particleSensor;
const byte RATE_SIZE = 4;
byte rates[RATE_SIZE];
byte rateSpot = 0;
long lastBeat = 0;
float beatsPerMinute;
int beatAvg;
void setup() {
Serial.begin(115200);
Serial.println("MAX30102 Heart Rate Monitor");
if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) {
Serial.println("MAX30102 not found. Check wiring.");
while (1);
}
particleSensor.setup(); // default settings
particleSensor.setPulseAmplitudeRed(0x0A); // low power indicator
particleSensor.setPulseAmplitudeGreen(0); // disable green LED
}
void loop() {
long irValue = particleSensor.getIR();
if (checkForBeat(irValue) == true) {
long delta = millis() - lastBeat;
lastBeat = millis();
beatsPerMinute = 60 / (delta / 1000.0);
if (beatsPerMinute < 255 && beatsPerMinute > 20) {
rates[rateSpot++] = (byte)beatsPerMinute;
rateSpot %= RATE_SIZE;
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, 1);
Serial.print(" Avg BPM="); Serial.println(beatAvg);
if (irValue < 50000) Serial.println(" -- No finger detected");
}
Open Serial Monitor at 115200 baud. Place your fingertip firmly over the sensor. You will see IR values jump above 50,000 when a finger is detected, followed by BPM readings that stabilise after 4–5 heartbeats (the moving average window size).
Full SpO2 + Heart Rate Project
For SpO2 measurement, the SparkFun library includes the spo2_algorithm which uses both red and IR channels. This more complex example averages samples over a 100-reading window for stable results:
#include <Wire.h>
#include "MAX30105.h"
#include "spo2_algorithm.h"
MAX30105 particleSensor;
#define MAX_BRIGHTNESS 255
#define BUFFER_LENGTH 100
uint32_t irBuffer[BUFFER_LENGTH];
uint32_t redBuffer[BUFFER_LENGTH];
int32_t spo2;
int8_t validSPO2;
int32_t heartRate;
int8_t validHeartRate;
void setup() {
Serial.begin(115200);
if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) {
Serial.println("MAX30102 not found!");
while (1);
}
// Configure sensor for SpO2 mode
byte ledBrightness = 60; // 0–255; higher = more IR/red light
byte sampleAverage = 4; // 1, 2, 4, 8, 16, 32
byte ledMode = 2; // 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green
byte sampleRate = 100; // samples/second: 50,100,200,400,800,1000,1600,3200
int pulseWidth = 411; // µs: 69, 118, 215, 411
int adcRange = 4096; // 2048, 4096, 8192, 16384
particleSensor.setup(ledBrightness, sampleAverage, ledMode,
sampleRate, pulseWidth, adcRange);
}
void loop() {
// Fill first 100 samples
for (byte i = 0; i < BUFFER_LENGTH; i++) {
while (particleSensor.available() == false)
particleSensor.check();
redBuffer[i] = particleSensor.getRed();
irBuffer[i] = particleSensor.getIR();
particleSensor.nextSample();
}
maxim_heart_rate_and_oxygen_saturation(irBuffer, BUFFER_LENGTH,
redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate);
// Continuous rolling window: discard first 25, shift, refill
while (1) {
for (byte i = 25; i < BUFFER_LENGTH; i++) {
redBuffer[i - 25] = redBuffer[i];
irBuffer[i - 25] = irBuffer[i];
}
for (byte i = 75; i < BUFFER_LENGTH; i++) {
while (particleSensor.available() == false)
particleSensor.check();
redBuffer[i] = particleSensor.getRed();
irBuffer[i] = particleSensor.getIR();
particleSensor.nextSample();
}
maxim_heart_rate_and_oxygen_saturation(irBuffer, BUFFER_LENGTH,
redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate);
Serial.print(F("HR=")); Serial.print(heartRate, DEC);
Serial.print(F(" valid=")); Serial.print(validHeartRate, DEC);
Serial.print(F(" SpO2=")); Serial.print(spo2, DEC);
Serial.print(F(" valid=")); Serial.println(validSPO2, DEC);
}
}
A validSPO2 value of 1 means the algorithm is confident in the reading. If it returns 0, the signal quality was insufficient — usually due to motion or poor finger placement. Never act on invalid readings.
BMP280 Barometric Pressure and Altitude Sensor
Pair with MAX30102 to build a complete health + environment monitor measuring SpO2, HR, altitude, and pressure.
Adding an OLED Display
A 0.96-inch SSD1306 I2C OLED display is the perfect companion for the MAX30102. Both use I2C, so you add only 2 more wires (SDA and SCL share the same bus).
OLED display wiring:
- OLED VCC → Arduino 3.3 V (most SSD1306 modules accept 3.3 V – 5 V)
- OLED GND → Arduino GND
- OLED SDA → Arduino A4 (same as MAX30102 SDA)
- OLED SCL → Arduino A5 (same as MAX30102 SCL)
The SSD1306 has I2C address 0x3C (or 0x3D on some variants), which is different from the MAX30102 at 0x57 — so both devices coexist on the same bus without any configuration changes.
Add the Adafruit SSD1306 and Adafruit GFX libraries via Library Manager. The display code to overlay heart rate and SpO2 on the OLED is straightforward:
display.clearDisplay();
display.setTextSize(2);
display.setCursor(0, 0);
display.print("HR: "); display.print(heartRate); display.println(" BPM");
display.setCursor(0, 24);
display.print("SpO2:"); display.print(spo2); display.println(" %");
display.display();
Calibration and Accuracy Considerations
The MAX30102 is a consumer-grade sensor, not a medical-grade device. Understanding its limitations is essential:
- Accuracy range: The SparkFun spo2_algorithm is reliable for SpO2 values between 80–100%. Below 80% (clinical hypoxia range), the algorithm’s accuracy degrades significantly.
- Motion artefacts: Even small finger movements cause large signal spikes. Always average readings over multiple samples (100+ preferred) and check the validity flag.
- Ambient light: Strong ambient light (especially sunlight) leaks into the sensor cavity and corrupts readings. Shade the sensor or use a covered enclosure.
- Skin tone variation: Some studies suggest pulse oximetry accuracy varies with darker skin tones. This is a known limitation of optical pulse oximetry in general.
- LED current adjustment: The
ledBrightnessparameter (0–255) in the sensor setup controls LED power. Too low and the signal is weak; too high and the ADC saturates. For most adult fingertips, 60–80 is a good starting value.
For a classroom or prototyping demonstration, the MAX30102 with the SparkFun algorithm gives readings that are impressively close to clinical oximeters (±2–3% is typical). For any safety-critical application, use a certified medical device.
Low-Power Mode for Battery Projects
The MAX30102’s 0.7 µA shutdown current makes it excellent for battery-powered wearables. To shut down the sensor between readings:
// Enter shutdown mode
particleSensor.shutDown();
// ... MCU sleeps here for several seconds ...
// Wake up the sensor and restart
particleSensor.wakeUp();
particleSensor.setup(); // re-configure
With the sensor shut down and the MCU in deep sleep, a typical Arduino + MAX30102 system draws under 20 µA — enabling months of operation from a 500 mAh LiPo battery if readings are taken every 30 seconds.
LM35 Temperature Sensor
Add body temperature measurement alongside SpO2 and heart rate for a more comprehensive health monitor.
Troubleshooting Common Issues
“MAX30102 not found” at startup:
- Recheck SDA/SCL wiring — swapped lines are the most common issue
- Run an I2C scanner sketch to verify the device appears at address 0x57
- Ensure VIN has power: the onboard LED on most modules glows red when powered
IR value stays near zero, no reading:
- Finger is not placed over the sensor window — the sensor has a small oval window, cover it completely
- Too much ambient light — cup your hand over the sensor
SpO2 always reads -999 or 0:
- The algorithm returns -999 when signal quality is insufficient — this is normal until a valid reading accumulates
- Hold finger completely still for at least 10 seconds after placing it on the sensor
- Try increasing LED brightness: change
ledBrightnessfrom 60 to 100 or 150
Readings are wildly inconsistent:
- Apply gentle constant pressure — pressing too hard restricts blood flow, too lightly gives a weak signal
- Ensure the I2C bus is not overloaded with other devices sharing the same pullup resistors
GY-BME280-3.3 Precision Altimeter Sensor
Measures barometric pressure, temperature, and humidity — complement your MAX30102 health station with environmental data.
Frequently Asked Questions
Is the MAX30102 medically accurate?
The MAX30102 with SparkFun’s algorithm typically achieves ±2–3% accuracy compared to clinical pulse oximeters under ideal conditions (still finger, good perfusion, normal skin tone). This is good enough for fitness and wellness projects, but the sensor is NOT FDA-cleared or CE-marked as a medical device. Do not use it for clinical diagnosis or to make health decisions.
What is the difference between MAX30100 and MAX30102?
The MAX30100 is the older version. The MAX30102 adds a green LED channel (for ECG-style applications), has improved SpO2 accuracy, wider supply voltage range (down to 1.8 V), and a larger FIFO buffer (32 samples vs 16). The MAX30102 is strictly better in every way and is now the recommended choice for new designs.
Can I use MAX30102 with ESP8266 or ESP32?
Yes. Connect to the hardware I2C pins (ESP8266: GPIO4/GPIO5; ESP32: GPIO21/GPIO22). Power from 3.3 V. The SparkFun library works identically on these platforms with no changes to the sensor code.
Why does the sensor get warm during operation?
The LEDs generate some heat, especially at higher brightness levels. This is normal. The MAX30102 datasheet recommends keeping the LED current as low as possible for a good signal — excessive brightness saturates the photodetector and wastes power. If the module feels hot to touch, reduce ledBrightness.
Can MAX30102 measure ECG (electrocardiogram)?
No. ECG requires measuring electrical signals from the heart via electrodes placed on the skin. The MAX30102 is an optical sensor that detects blood volume changes optically. It can produce a PPG waveform that superficially resembles an ECG, but it is not an ECG. For actual ECG projects, use an AD8232 or similar ECG front-end IC.
How do I mount MAX30102 for wrist-based heart rate (like a smartwatch)?
Wrist-based PPG is significantly harder than fingertip PPG because the wrist has lower perfusion and more motion artefacts. You need a very high LED brightness (above 100), a tight-fitting band to minimise motion, and more sophisticated signal processing (often involving accelerometer-based motion cancellation). For beginner projects, stick with the fingertip placement.
Conclusion
The MAX30102 is one of the most impressive sensors available to Arduino hobbyists. In a package smaller than a fingernail, it delivers medically-meaningful measurements of heart rate and blood oxygen saturation through sophisticated optical sensing and a capable built-in ADC. With the SparkFun library and the code in this guide, you can have a working SpO2 monitor running in under an hour.
The key to reliable readings lies in understanding the sensor’s optical principles: keep the finger still, cover the entire sensor window, shield from ambient light, and average over enough samples to filter noise. With these practices, the MAX30102 delivers readings that will genuinely impress you with their accuracy for a sub-₹200 component.
From here, you can extend this project to log readings to an SD card, upload them to a cloud dashboard via WiFi, or add an alert system that buzzes a buzzer when SpO2 drops below 95%. The platform is solid — what you build on it is up to your imagination.
Add comment