Skip to content

Heart Guard: Revolutionizing Health Monitoring with Wireless Data Acquisition

You can view the entire report of the project on my partner site Click Here

Introduction

Healthcare today is facing major challenges due to growing populations, limited resources, and not enough medical staff. This makes it hard for many people to get timely medical care. Our project aims to help solve these problems by using technology. We propose a solution that reduces the need for patients with chronic conditions to visit treatment centers in person. Our plan involves using a monitoring device that can track important health indicators like blood sugar levels, body temperature, heart rate, blood pressure, and oxygen levels. This device will send data to a system where doctors can monitor multiple patients remotely. This approach will help doctors manage more patients efficiently, reduce the strain on healthcare resources, and make it easier for people to get the care they need

What i Have Done in the Project

Task I

To begin, we used various microcontrollers, including the MKR1010 and arduino UNO , and connected the MAX30100 sensor to them to understand its functionality. Although the MAX30100 and MAX30102 library provided code for measuring heart rate and SpO2, we needed to make some adjustments for it to work correctly with our setup.

Here is the C code designed to interface with the MAX30100 sensor for measuring heart rate and SpO2 levels. This code initializes the sensor, configures its settings, and continuously reads the sensor data to calculate and display heart rate and oxygen

#include <Wire.h>
#include "MAX30105.h"
#include "heartRate.h"

// Create an instance of the MAX30105 class to interact with the sensor
MAX30105 particleSensor;

// Define the size of the rates array for averaging BPM; can be adjusted for smoother results
const byte RATE_SIZE = 4; // Increase this for more averaging. 4 is a good starting point.
byte rates[RATE_SIZE]; // Array to store heart rate readings for averaging
byte rateSpot = 0; // Index for inserting the next heart rate reading into the array
long lastBeat = 0; // Timestamp of the last detected beat, used to calculate BPM

float beatsPerMinute; // Calculated heart rate in beats per minute
int beatAvg; // Average heart rate after processing multiple readings

void setup() {
  Serial.begin(115200); // Start serial communication at 115200 baud rate
  Serial.println("Initializing...");

  // Attempt to initialize the MAX30105 sensor. Check for a successful connection and report.
  if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) { // Start communication using fast I2C speed
    Serial.println("MAX30102 was not found. Please check wiring/power. ");
    while (1); // Infinite loop to halt further execution if sensor is not found
  }
  Serial.println("Place your index finger on the sensor with steady pressure.");

  particleSensor.setup(); // Configure sensor with default settings for heart rate monitoring
  particleSensor.setPulseAmplitudeRed(0x0A); // Set the red LED pulse amplitude (intensity) to a low value as an indicator
  particleSensor.setPulseAmplitudeGreen(0); // Turn off the green LED as it's not used here
}

void loop() {
  long irValue = particleSensor.getIR(); // Read the infrared value from the sensor

  if (checkForBeat(irValue) == true) { // Check if a heart beat is detected
    long delta = millis() - lastBeat; // Calculate the time between the current and last beat
    lastBeat = millis(); // Update lastBeat to the current time

    beatsPerMinute = 60 / (delta / 1000.0); // Calculate BPM

    // Ensure BPM is within a reasonable range before updating the rates array
    if (beatsPerMinute < 255 && beatsPerMinute > 20) {
      rates[rateSpot++] = (byte)beatsPerMinute; // Store this reading in the rates array
      rateSpot %= RATE_SIZE; // Wrap the rateSpot index to keep it within the bounds of the rates array

      // Compute the average of stored heart rates to smooth out the BPM
      beatAvg = 0;
      for (byte x = 0 ; x < RATE_SIZE ; x++)
        beatAvg += rates[x];
      beatAvg /= RATE_SIZE;
    }
  }

  // Output the current IR value, BPM, and averaged BPM to the serial monitor
  Serial.print("IR=");
  Serial.print(irValue);
  Serial.print(", BPM=");
  Serial.print(beatsPerMinute);
  Serial.print(", Avg BPM=");
  Serial.print(beatAvg);

  // Check if the sensor reading suggests that no finger is placed on the sensor
  if (irValue < 50000)
    Serial.print(" No finger?");

  Serial.println();
}

After downloading the required lib, the code was working properly in Arduino IDE C languageā€¦

Task II

Next, we received an ESP32 S3 microcontroller with a 1.28-inch touch LCD screen. Our task was to write code using MicroPython to get the MAX30102 sensor working with this sensor. This involved creating a program that would enable the sensor to communicate with the ESP32 S3 and display the results on the screen.

Here’s the MicroPython code to interface with the MAX30102 sensor and measure heart rate, written for use with the Thonny IDE

from machine import Pin, SPI, SoftI2C
from utime import ticks_diff, ticks_us, ticks_ms, sleep
import gc9a01py as gc9a01
from max30102 import MAX30102, MAX30105_PULSE_AMP_MEDIUM
from fonts.romfonts import vga2_bold_16x32 as font4

error = True

class HeartRateMonitor:
    """A simple heart rate monitor that uses a moving window to smooth the signal and find peaks."""

    def _init_(self, sample_rate=100, window_size=10, smoothing_window=5):
        self.sample_rate = sample_rate
        self.window_size = window_size
        self.smoothing_window = smoothing_window
        self.samples = []
        self.timestamps = []
        self.filtered_samples = []

    def add_sample(self, sample):
        """Add a new sample to the monitor."""
        timestamp = ticks_ms()
        self.samples.append(sample)
        self.timestamps.append(timestamp)

        # Apply smoothing
        if len(self.samples) >= self.smoothing_window:
            smoothed_sample = (
                sum(self.samples[-self.smoothing_window :]) / self.smoothing_window
            )
            self.filtered_samples.append(smoothed_sample)
        else:
            self.filtered_samples.append(sample)

        # Maintain the size of samples and timestamps
        if len(self.samples) > self.window_size:
            self.samples.pop(0)
            self.timestamps.pop(0)
            self.filtered_samples.pop(0)

    def find_peaks(self):
        """Find peaks in the filtered samples."""
        peaks = []

        if len(self.filtered_samples) < 3:  # Need at least three samples to find a peak
            return peaks

        # Calculate dynamic threshold based on the min and max of the recent window of filtered samples
        recent_samples = self.filtered_samples[-self.window_size :]
        min_val = min(recent_samples)
        max_val = max(recent_samples)
        threshold = (
            min_val + (max_val - min_val) * 0.5
        )  # 50% between min and max as a threshold

        for i in range(1, len(self.filtered_samples) - 1):
            if (
                self.filtered_samples[i] > threshold
                and self.filtered_samples[i - 1] < self.filtered_samples[i]
                and self.filtered_samples[i] > self.filtered_samples[i + 1]
            ):
                peak_time = self.timestamps[i]
                peaks.append((peak_time, self.filtered_samples[i]))

        return peaks

    def calculate_heart_rate(self):
        """Calculate the heart rate in beats per minute (BPM)."""
        peaks = self.find_peaks()

        if len(peaks) < 2:
            return None  # Not enough peaks to calculate heart rate

        # Calculate the average interval between peaks in milliseconds
        intervals = []
        for i in range(1, len(peaks)):
            interval = ticks_diff(peaks[i][0], peaks[i - 1][0])
            intervals.append(interval)

        average_interval = sum(intervals) / len(intervals)

        # Convert intervals to heart rate in beats per minute (BPM)
        heart_rate = (
            60000 / average_interval
        )  # 60 seconds per minute * 1000 ms per second

        return heart_rate

def setup_display():
    """Initialize the GC9A01 display."""
    spi = SPI(2, baudrate=80000000, polarity=0, sck=Pin(10), mosi=Pin(11))
    tft = gc9a01.GC9A01(
        spi, dc=Pin(8, Pin.OUT), cs=Pin(9, Pin.OUT),
        reset=Pin(14, Pin.OUT), backlight=Pin(2, Pin.OUT),
        rotation=0
    )
    return tft

def update_display(tft, heart_rate):
    """Update the display with the current heart rate."""
    tft.fill(gc9a01.BLACK)
    line = tft.height // 2
    col = tft.width // 3
    if heart_rate is not None:
        display_text = "HR: {:.0f} BPM".format(heart_rate)
    else:
        display_text = "No HR Data"
    tft.text(font4, display_text, col, line, gc9a01.WHITE, gc9a01.RED)
    if error==True:
        tft.text(font4, "warning", col,0, gc9a01.WHITE, gc9a01.RED)

def main():
    # Initialize I2C and sensor
    i2c = SoftI2C(
        sda=Pin(15),  # Here, use your I2C SDA pin
        scl=Pin(16),  # Here, use your I2C SCL pin
        freq=400000,
    )

    sensor = MAX30102(i2c=i2c)

    if sensor.i2c_address not in i2c.scan():
        print("Sensor not found.")
        return
    elif not (sensor.check_part_id()):
        print("I2C device ID not corresponding to MAX30102 or MAX30105.")
        return
    else:
        print("Sensor connected and recognized.")

    print("Setting up sensor with default configuration.", "\n")
    sensor.setup_sensor()

    sensor_sample_rate = 400
    sensor.set_sample_rate(sensor_sample_rate)

    sensor_fifo_average = 8
    sensor.set_fifo_average(sensor_fifo_average)

    sensor.set_active_leds_amplitude(MAX30105_PULSE_AMP_MEDIUM)

    actual_acquisition_rate = int(sensor_sample_rate / sensor_fifo_average)

    sleep(1)

    print(
        "Starting data acquisition from RED & IR registers...",
        "press Ctrl+C to stop.",
        "\n",
    )
    sleep(1)

    # Initialize heart rate monitor
    hr_monitor = HeartRateMonitor(
        sample_rate=actual_acquisition_rate,
        window_size=int(actual_acquisition_rate * 3),
    )

    # Initialize display
    tft = setup_display()

    hr_compute_interval = 2  # seconds
    ref_time = ticks_ms()  # Reference time

    while True:
        sensor.check()

        if sensor.available():
            red_reading = sensor.pop_red_from_storage()
            ir_reading = sensor.pop_ir_from_storage()

            hr_monitor.add_sample(ir_reading)

        if ticks_diff(ticks_ms(), ref_time) / 1000 > hr_compute_interval:
            heart_rate = hr_monitor.calculate_heart_rate()
            update_display(tft, heart_rate)
            ref_time = ticks_ms()

if _name_ == "_main_":
    main()

Final Look

This project aims to demonstrate how we can customize health monitoring devices and leverage IoT technology to gather patient data remotely. By doing so, doctors can monitor patients’ conditions without requiring their physical presence. This approach has the potential to reduce the high patient volumes at healthcare facilities worldwide. Additionally, it could generate new job opportunities by enabling hospitals to produce simple. Furthermore, integrating various devices, such as scales and glucose monitors, into a single application would allow doctors to efficiently track and manage the health data of numerous patients daily

Conclusion

Heart Guard revolutionizes health monitoring by offering real-time, wireless data acquisition, allowing doctors to track vital signs such as heart rate and SpO2 remotely. This technology alleviates the burden on healthcare facilities by reducing the need for in-person visits, facilitates early detection of health issues, and integrates various health metrics into a single platform. It also promotes job creation through simple manufacturing techniques and improves patient convenience by enabling them to manage their health from home, ultimately leading to more cost-effective and efficient healthcare solutions


Last update: September 7, 2024