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(orMAX30105) — for MAX30102 sensorAdafruit_MPU6050andAdafruit_Sensor— for MPU6050Adafruit_GFXandAdafruit_SSD1306— for OLED displayWiFi.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)
- Place ESP32, MAX30102, MPU6050, and OLED on the breadboard.
- Connect all devices to the shared I²C bus (GPIO 21 SDA, GPIO 22 SCL).
- Ensure a stable 3.3V supply for all modules.
- 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.
#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).