Back to All Blog Posts
Tutorial EltroNerd Engineering

IoT Heart Rate Monitoring System using max30102

IoT-based Heart Rate Monitoring System using MAX30102 (pulse oximeter & heart rate sensor) and MPU6050 (accelerometer + gyroscope).

IoT Heart Rate Monitoring System — MAX30102 + MPU6050 + OLED (Step-by-step)

Complete step-by-step guide, wiring, firmware, algorithms, troubleshooting, and deployment notes — written in Markdown so you can copy, print, or paste into a repo README.


1. Project summary

This project builds a portable IoT heart-rate monitor using the MAX30102 sensor (PPG: heart rate + SpO₂), the MPU6050 (accelerometer + gyro), and an SSD1306 OLED display connected to an ESP32. The ESP32 will process the raw signals, detect heart beats (BPM), monitor motion/fall events, display results locally on OLED, and publish JSON telemetry over MQTT to a cloud/dashboard.

What you’ll get:

  • Real-time BPM telemetry on OLED
  • Basic SpO₂ estimation (note: SpO₂ calculation is non-trivial; use a library for production accuracy)
  • Motion / fall detection and activity context from MPU6050
  • MQTT publishing for dashboards/alerts

⚠️ Medical disclaimer: This is a hobbyist project. Do not use it as a medical diagnostic device. Always consult professionals for medical-grade monitoring.


2. Parts & BOM

  • ESP32 development board (any variant with Wi‑Fi)
  • MAX30102 (or MAX30105) sensor module
  • MPU6050 module
  • SSD1306 OLED display (128x64 I²C)
  • Breadboard and jumper wires
  • LiPo battery + charger (TP4056) for portability

Libraries you will need (Arduino/PlatformIO):

  • SparkFun MAX3010x (or MAX30105) — for MAX30102 sensor
  • Adafruit_MPU6050 and Adafruit_Sensor — for MPU6050
  • Adafruit_GFX and Adafruit_SSD1306 — for OLED display
  • WiFi.h (built-in)
  • PubSubClient — for MQTT

3. Wiring / Pinout (ESP32)

I²C bus (shared):

  • MAX30102 VCC → 3.3V

  • MAX30102 GND → GND

  • MAX30102 SDA → GPIO 21 (SDA)

  • MAX30102 SCL → GPIO 22 (SCL)

  • MPU6050 VCC → 3.3V

  • MPU6050 GND → GND

  • MPU6050 SDA → GPIO 21 (SDA)

  • MPU6050 SCL → GPIO 22 (SCL)

  • OLED SSD1306: VCC → 3.3V, GND → GND, SDA → 21, SCL → 22

All devices share the same I²C bus.


4. Step-by-step assembly (hardware)

  1. Place ESP32, MAX30102, MPU6050, and OLED on the breadboard.
  2. Connect all devices to the shared I²C bus (GPIO 21 SDA, GPIO 22 SCL).
  3. Ensure a stable 3.3V supply for all modules.
  4. Double-check wiring before powering.

5. Firmware (ESP32 sketch with OLED)

This sketch now includes OLED support to show BPM, fall status, and basic sensor data locally.

c
#include <Wire.h>
#include "MAX30105.h"
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// ======= CONFIG =======
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

const char* WIFI_SSID = "YOUR_WIFI_SSID";
const char* WIFI_PASS = "YOUR_WIFI_PASS";
const char* MQTT_SERVER = "test.mosquitto.org";
const char* MQTT_TOPIC  = "iot/heartmonitor";

WiFiClient espClient;
PubSubClient mqtt(espClient);

MAX30105 particleSensor;
Adafruit_MPU6050 mpu;

unsigned long lastBeatMs = 0;
int bpm = 0;
bool pulseDetected = false;

void connectWiFi(){
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) delay(300);
}

void reconnectMQTT(){
  while (!mqtt.connected()){
    mqtt.connect("ESP32Heart");
    delay(100);
  }
}

void setup() {
  Serial.begin(115200);
  Wire.begin();

  connectWiFi();
  mqtt.setServer(MQTT_SERVER, 1883);

  // MAX30102 init
  if (!particleSensor.begin(Wire)){
    Serial.println("MAX301xx not found");
    while(1) delay(1000);
  }
  particleSensor.setup();

  // MPU6050 init
  if (!mpu.begin()){
    Serial.println("MPU6050 not found");
    while(1) delay(1000);
  }

  // OLED init
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 not found"));
    while(1);
  }
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0,0);
  display.println("Heart Monitor Ready");
  display.display();
}

void loop() {
  if (!mqtt.connected()) reconnectMQTT();
  mqtt.loop();

  // --- Read PPG ---
  long irValue = particleSensor.getIR();
  unsigned long now = millis();

  if (irValue > 50000 && !pulseDetected) {
    if (now - lastBeatMs > 250) {
      bpm = 60000 / (now - lastBeatMs);
      lastBeatMs = now;
      pulseDetected = true;
    }
  }
  if (irValue < 50000) pulseDetected = false;

  // --- Read MPU6050 ---
  sensors_event_t a, g, temp;
  mpu.getEvent(&a, &g, &temp);
  float accelMag = sqrt(a.acceleration.x*a.acceleration.x + a.acceleration.y*a.acceleration.y + a.acceleration.z*a.acceleration.z);
  bool fallDetected = (accelMag > 25.0);

  // --- Update OLED ---
  display.clearDisplay();
  display.setCursor(0,0);
  display.setTextSize(2);
  display.print("BPM: ");
  display.println(bpm);
  display.setTextSize(1);
  display.setCursor(0,30);
  display.print("AccelMag: ");
  display.println(accelMag);
  display.setCursor(0,45);
  display.print("Fall: ");
  display.println(fallDetected ? "YES" : "NO");
  display.display();

  // --- Publish JSON ---
  static unsigned long lastPublish = 0;
  if (now - lastPublish >= 1000) {
    lastPublish = now;
    String payload = "{\"bpm\":" + String(bpm) + ",\"fall\":" + String(fallDetected) + "}";
    mqtt.publish(MQTT_TOPIC, payload.c_str());
    Serial.println(payload);
  }

  delay(50);
}

6. OLED Display output

The SSD1306 will show:

  • BPM (updated in real time)
  • Accel magnitude (total acceleration vector)
  • Fall detected status (YES/NO)

This allows you to monitor locally without needing MQTT.


7. Next steps

  • Add SpO₂ calculation and display it on OLED.
  • Add history graph or scrolling BPM trend on OLED.
  • Optimize text layout for clarity (big BPM number, smaller motion data).

Try this project in your browser

Compile code and simulate hardware output with EltroNerd Cloud IDE.

Launch Cloud IDE