DIY MAX30102 Heart Health Monitor Using ESP8266

 



Creating your own heart health monitor with an ESP8266 is a fantastic way to dive into DIY electronics while enhancing your wellness journey. This compact device allows you to monitor key indicators of heart health, including your heart rate (BPM) and blood oxygen levels (SpO2), all displayed in real-time on a small screen. By tracking these metrics, you gain valuable insights into your cardiovascular health, making it a unique tool for personal wellness.

The project isn’t just functional—it’s interactive. It includes a visual breathing monitor that responds to your inhale and exhale patterns, with colours changing to reflect each breath cycle. This breathing feature, along with the heart rate display, adds a mindful, meditative element to the monitor, helping you observe your breathing and its impact on your heart health.

Whether you're a beginner in electronics or an experienced maker, this heart monitor project with ESP8266 is a rewarding experience. Not only will it expand your tech skills, but it also provides a portable wellness tool that can help you stay conscious of your heart health in real-time.

Keep in mind, this is a prototype intended for personal wellness insights, suitable for general daily check-ups for healthy individuals. However, if you have a serious cardiovascular condition, it’s important not to rely on this as a primary monitoring tool, as the readings may occasionally differ from clinical values. Please use it as a supplementary device rather than a substitute for professional medical equipment.

How This Circuit Works


(This is AI Based image and not so much accurate or detailed)

The heart of this DIY heart health monitor is the MAX30105 sensor, which is specially designed to measure heart rate and SpO2 (blood oxygen saturation). This sensor uses photo plethysmography (PPG) to detect changes in blood volume by shining LED light (red, infrared, and green) through your skin and measuring how much light is absorbed. Variations in light absorption correspond to heartbeats, which allows the sensor to calculate both your pulse and SpO2 levels. The ESP8266 reads these measurements, processes the data, and displays the information on a screen.

In the code, the checkForBeat() function monitors infrared light levels from the sensor, detecting peaks that signal heartbeats. Each peak is processed to calculate beats per minute (BPM), while a simple SpO2 formula computes oxygen levels based on the ratio of red to infrared light absorption. The ESP8266 also updates the display with this data, using the added memory to ensure smooth, accurate readings. This combination of hardware and code creates a functional and compact heart health monitor prototype, providing real-time feedback on your pulse and oxygen levels in a user-friendly display.

 

Components Needed:

 


1x ESP8266 NodeMCU Module

1x MAX30102 Heart Rate Sensor

1x1.8 Inch ST7735 LCD Display (In future, you can update with the other displays.)

1x Neopixel LED (This is not the serious component, it’s just for heart beat visualization, you can add this or not it’s your choice.)

1x 470uF Capacitor 25V(for smooth the voltage and reduce noise)

1x 100nF Capacitor(for smooth the voltage and reduce noise)

Why I Chose ESP8266 over Arduino Boards

The ESP8266 offers a significant advantage over standard Arduino boards, especially when it comes to memory management and handling more complex code. In this heart health monitor project, the sensor readings, display updates, and data buffering all demand substantial memory. With limited memory on most Arduino boards, adding code for continuous monitoring, displaying BPM and SpO2 values, and rendering graphics would quickly exceed their capabilities, leading to slower performance or even crashes.

The ESP8266, however, has a larger memory capacity and can handle this more demanding setup with ease. This extra memory allows for smoother data processing and larger buffers, meaning it can store and update sensor values more effectively, which is crucial for maintaining accurate real-time readings. Additionally, the ESP8266’s support for Wi-Fi makes it ideal for future upgrades, like sending health data to a smartphone app or cloud storage. Overall, the ESP8266 is a better choice for this project, balancing memory needs and enabling additional features beyond what typical Arduino boards can handle.

Connections or Circuit Diagram



Setting up the ESP8266 Heart Health Monitor involves a few simple connections between the ESP8266, MAX30105 sensor, and the display. The MAX30105 sensor communicates with the ESP8266 via I2C, requiring only two pins for data transfer, which keeps the wiring straightforward. The display is also connected to the ESP8266 using specific data pins, allowing the screen to display real-time heart rate and SpO2 readings effectively.

Here’s a table showing the necessary connections between the ESP8266 and the other components:

ESP8266 Pin

MAX30105 Pin

Display Pin

Function

3V3 and 5V

5V

5V,RST

Power Supply for Sensor and Display

GND

GND

GND

Ground Connection

D1

SDA

N/A

I2C Data Line

D2

SCL

N/A

I2C Clock Line

D5

N/A

SCL

SPI Clock

D7

N/A

SDA (MOSI)

SPI Data In

D8

N/A

CS

Chip Select

D4

N/A

DC

Data/Command Line

In this setup:

  • 3V3 and GND provide the power and ground connections to the sensor and display.
  • I2C Lines (D1 and D2) connect the MAX30105 sensor for heart rate and SpO2 data.
  • SPI Lines (D5, D7, D8 and D4) control the display to show the readings.

The simplicity of these connections makes this project accessible to beginners, while the ESP8266’s processing power and memory allow it to handle real-time calculations and display updates efficiently.

Code Overview

Here is the code
#include <Wire.h>
#include "MAX30105.h"
#include "heartRate.h"
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <Adafruit_NeoPixel.h>

MAX30105 particleSensor;

#define SCALING 12
#define TRACE_SPEED 0.5
#define TRACE_MIDDLE_Y_POSITION 90
#define TRACE_HEIGHT 60
#define HALF_TRACE_HEIGHT TRACE_HEIGHT / 2
#define TRACE_MIN_Y TRACE_MIDDLE_Y_POSITION - HALF_TRACE_HEIGHT + 1
#define TRACE_MAX_Y TRACE_MIDDLE_Y_POSITION + HALF_TRACE_HEIGHT - 1

#define TFT_CS   D8
#define TFT_RST  -1
#define TFT_DC   D4
#define TFT_SDA  D7
#define TFT_SCL  D5

Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);

#define NEOPIXEL_PIN D6
#define NUMPIXELS 16
Adafruit_NeoPixel pixels(NUMPIXELS, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);

long lastBeat = 0;
float beatsPerMinute = 0.0;
float spO2 = 0.0;

bool isInhaling = false;
bool isExhaling = false;
int stableCount = 0;
const int inhaleExhaleThreshold = 5;
long irMovingAverage = 0;
const int smoothingFactor = 20;

const long MIN_IR_VALUE = 5000;
bool fingerDetected = false;

// Variables for Neopixel Heartbeat Animation
uint8_t heartBrightness = 0;
bool increasingBrightness = true;
unsigned long lastHeartBeatUpdate = 0;
const unsigned long heartbeatInterval = 10;  // Faster interval for animation

void setup() {
  tft.initR(INITR_GREENTAB);   
  tft.setRotation(3); 
  tft.fillScreen(ST7735_BLACK);
  tft.setTextSize(1);
  tft.setTextColor(ST7735_WHITE);

  pixels.begin(); 
  pixels.clear();
  pixels.show();

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

  if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) {
    Serial.println("MAX30105 was not found. Please check wiring/power.");
    while (1);
  }

  byte ledBrightness = 0x1F;
  byte sampleAverage = 8;
  byte ledMode = 3;
  int sampleRate = 100;
  int pulseWidth = 411;
  int adcRange = 4096;

  particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange);
  particleSensor.enableDIETEMPRDY(); 

  tft.setTextSize(2);
  tft.setCursor(0, 10);
  tft.print("BPM");
  tft.setTextSize(1);
  tft.setCursor(1, 0);
  tft.print("ElecoTechoz Heart Health");
  tft.setTextSize(2);
  tft.drawRect(0, TRACE_MIN_Y - 1, 160, TRACE_HEIGHT + 2, ST7735_BLUE);  
}

void loop() {
  static float lastx = 1;
  static int lasty = TRACE_MIDDLE_Y_POSITION;
  static float x = 1;
  int32_t y;
  static uint32_t tsLastReport = 0;
  static int32_t SensorOffset = 10000;

  long irValue = particleSensor.getIR();
  long redValue = particleSensor.getRed();

  // Check if a finger is detected based on IR value
  fingerDetected = (irValue > MIN_IR_VALUE);

  if (fingerDetected) {
    // BPM detection
    if (checkForBeat(irValue)) {
      long delta = millis() - lastBeat;

      if (delta > 300) {
        lastBeat = millis();
        float bpm = 60 / (delta / 1000.0);

        if (bpm > 40 && bpm < 180) {
          beatsPerMinute = bpm;
          tft.fillRect(0, 30, 160, 20, ST7735_BLACK); 
          tft.setCursor(0, 30);

          if (beatsPerMinute < 60 || beatsPerMinute > 110)
            tft.setTextColor(ST7735_BLUE);
          else
            tft.setTextColor(ST7735_GREEN);
          tft.setTextSize(2);
          tft.print(beatsPerMinute);
        }
      }
    }

    spO2 = calculateSpO2Simple(redValue, irValue);

    tft.fillRect(90, 20, 60, 16, ST7735_BLACK); 
    tft.setTextSize(1);
    tft.setCursor(90, 20);
    tft.setTextColor(ST7735_MAGENTA);
    tft.print("SpO2:");
    tft.print(spO2, 1);
    tft.print(" %");

    float temperatureC = particleSensor.readTemperature();
    tft.fillRect(90, 40, 60, 16, ST7735_BLACK); 
    tft.setTextSize(1);  
    tft.setCursor(90, 40);
    tft.setTextColor(ST7735_YELLOW);  
    tft.print("Temp:");
    tft.print(temperatureC, 1);
    tft.print(" C");

    irMovingAverage = (irMovingAverage * (smoothingFactor - 1) + irValue) / smoothingFactor;

    if (irValue < irMovingAverage - 300) {
      if (!isInhaling) {
        isInhaling = true;
        isExhaling = false;
        tft.fillRect(0, 50, 60, 10, ST7735_BLACK);  
        tft.setCursor(0, 50);
        tft.setTextColor(ST7735_GREEN);
        tft.print("Inhale");
        
        pixels.fill(pixels.Color(0, 255, 0)); 
        pixels.show();
      }
    } else if (irValue > irMovingAverage + 300) {
      if (!isExhaling) {
        isExhaling = true;
        isInhaling = false;
        tft.fillRect(0, 50, 60, 10, ST7735_BLACK);  
        tft.setCursor(0, 50);
        tft.setTextColor(ST7735_RED);
        tft.print("Exhale");
        
        pixels.fill(pixels.Color(255, 0, 0)); 
        pixels.show();
      }
    }
  } else {
    tft.fillRect(0, 50, 60, 10, ST7735_BLACK);
    pixels.clear();
    pixels.show();
  }

  y = calculateGraphPosition(fingerDetected ? irValue : SensorOffset, SensorOffset);
  tft.drawLine(lastx, lasty, x, y, ST7735_YELLOW);
  lasty = y;
  lastx = x;
  x += TRACE_SPEED;

  if (x > 158) { 
    tft.fillRect(1, TRACE_MIN_Y, 158, TRACE_HEIGHT, ST7735_BLACK); 
    x = 1;
    lastx = x;
  }

  updateNeopixelHeartbeat();
}

void updateNeopixelHeartbeat() {
  unsigned long currentMillis = millis();

  if (fingerDetected && currentMillis - lastHeartBeatUpdate >= heartbeatInterval) {
    lastHeartBeatUpdate = currentMillis;

    if (increasingBrightness) {
      heartBrightness += 15;
      if (heartBrightness >= 255) increasingBrightness = false;
    } else {
      heartBrightness -= 15;
      if (heartBrightness <= 0) increasingBrightness = true;
    }

    uint32_t color = pixels.Color(255, 105, 180); // True pink color
    uint32_t pulsingColor = pixels.Color((heartBrightness * 255) / 255, (heartBrightness * 105) / 255, (heartBrightness * 180) / 255);
    
    pixels.fill(pulsingColor);
    pixels.show();
  } else if (!fingerDetected) {
    pixels.clear(); 
    pixels.show();
  }
}

int calculateGraphPosition(long irValue, int &SensorOffset) {
  int32_t Diff = irValue - SensorOffset;
  Diff /= SCALING;

  if (Diff < -HALF_TRACE_HEIGHT)
    SensorOffset += (SCALING * (abs(Diff) - 32));
  if (Diff > HALF_TRACE_HEIGHT)
    SensorOffset += (SCALING * (abs(Diff) - 32));

  int y = Diff + (TRACE_MIDDLE_Y_POSITION - HALF_TRACE_HEIGHT);
  y += TRACE_HEIGHT / 4;
  return constrain(y, TRACE_MIN_Y, TRACE_MAX_Y);
}

boolean checkForBeat(long irValue) {
  static long prevIrValue = 0;
  static boolean prevState = false;

  boolean beatDetected = (irValue > prevIrValue + 20) && prevState && (millis() - lastBeat > 300);
  prevIrValue = irValue;
  prevState = (irValue > 5000);

  return beatDetected;
}

float calculateSpO2Simple(long redValue, long irValue) {
  float ratio = (float)redValue / irValue;
  float spO2 = 110.0 - (25.0 * ratio);
  return constrain(spO2, 0.0, 100.0);
}

The code for this DIY Heart Health Monitor with ESP8266 gathers heart rate (BPM) and SpO2 data from the MAX30105 sensor, processes the readings, and displays them on the screen. It starts by initializing the sensor and display, then enters a continuous loop where it checks for heartbeats, calculates SpO2 levels, and shows the results in real time. Each heartbeat triggers a visual pulse, and the display updates with BPM, SpO2, and indicators for breathing and heart status.

To upload the code, open the Arduino IDE, select ESP8266 as the board, choose the correct COM port, and copy-paste the code. After uploading, the device should be ready to monitor and display live heart health data in real-time.

 

Conclusion and Future Upgrades

Congratulations! You've now successfully built a heart rate and SpO2 monitoring system using the MAX30102 sensor and an ST7735 TFT display. This project not only provides real-time health monitoring but also includes an interactive Neopixel heartbeat animation, enhancing the visual appeal and user experience.

The versatility of this setup opens doors for exciting upgrades:

  1. WI-FI Integration: Send the BPM and SpO2 data to a mobile app for remote health tracking.
  2. Battery Operability: Make the device portable by incorporating a rechargeable battery.
  3. Data Logging: Store the readings over time on an SD card for trend analysis.
  4. Advanced Visuals: Upgrade to a larger display or add touch functionality for easier interaction.
  5. Health Alerts: Integrate sound or vibration alerts for abnormal readings.

With further refinements, this project could even be the foundation for a wearable health tracker. Feel free to experiment and expand the functionality as your skills grow. If you found this tutorial helpful, don’t forget to share your creations and improvements!

Happy making! 😊

Comments