MAX30100 Pulse Oximeter Interfacing with NodeMCU

Description

MAX30100 Pulse Oximeter

 

  • The MAX30100 Pulse Oximeter is a medical device that is used to measure blood oxygen saturation levels, heart rate, and pulse strength. 
  • It uses a non-invasive method to measure oxygen saturation levels in the blood. 
  • This module has a pair of LEDs (Light Emitting Diode)  that emit a monochromatic red light at a wavelength of 660nm and infrared light at a wavelength of 940nm.
  • As the photodiode emits light, it falls on the finger and gets absorbed by the oxygenated blood rest light is reflected through the finger and falls on the detector.
  • The detector detects and processes the signals and gives the output.
  • The MAX30100 sensor works on the I2C Serial Communication protocol.

 

MAX30100 module Specification

  • Operating voltage of the module is 1.7V to 3.3V.
  • Supply current of 1200uA.
  • The operating temperature range of the module is -40C to +85C.
  • LED Current range 0mA to 50 mA.
  • LED Pulse width range from 200us to 1.6ms

 

MAX30100 Sensor Pinout

MAX30100 Module Pinout

 

MAX30100 Pin Description

  1. VIN: Power supply pin connects in the range of 
  2. GND: Connect to Supply ground.
  3. SCL: Serial Clock pin for I2C Serial Communication.
  4. SDL: Serial Data pin for I2C Serial Communication.
  5. INT: Active Low Interrupt pin.
  6. IRD: IR LED Cathode and LED Driver Connection pin.
  7. RD: Red LED Cathode and LED Driver Connection pin.

 

MAX30100 Hardware Connection with NodeMCU 

NodeMCU interfacing diagram with MAX30100 

 

Read Heart Rate using MAX30100 and NodeMCU

To find the heart rate (BPM) and Oxygen Saturation (SpO2) using the MAX30100 module, we will use the I2C communication protocol and display the readings on the serial monitor of Arduino IDE.

For the interface, we will use.MAX30100lib by OXullo Intersecans. We can install it from Arduino IDE Library Manager by searching max30100 in the search box.

Open the Arduino IDE and navigate to Sketch -> Include Library -> Manage Libraries…

The library Manager window will pop up. Now type MAX30100 into the search box and click the Install option to install version 1.2.1 or higher.

 

Now open an example of the MAX30100lib dashboard. To open it navigate to File -> Examples -> MAX30100lib -> MAX30100_Minimal

 

Here we have used the same example below

Code for MAX30100 with NodeMCU

/*
Arduino-MAX30100 oximetry / heart rate integrated sensor library
Copyright (C) 2016  OXullo Intersecans <[email protected]>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <Wire.h>
#include "MAX30100_PulseOximeter.h"

#define REPORTING_PERIOD_MS     1000

// PulseOximeter is the higher level interface to the sensor
// it offers:
//  * beat detection reporting
//  * heart rate calculation
//  * SpO2 (oxidation level) calculation
PulseOximeter pox;

uint32_t tsLastReport = 0;

// Callback (registered below) fired when a pulse is detected
void onBeatDetected()
{
    Serial.println("Beat!");
}

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

    Serial.print("Initializing pulse oximeter..");

    // Initialize the PulseOximeter instance
    // Failures are generally due to an improper I2C wiring, missing power supply
    // or wrong target chip
    if (!pox.begin()) {
        Serial.println("FAILED");
        for(;;);
    } else {
        Serial.println("SUCCESS");
    }

    // The default current for the IR LED is 50mA and it could be changed
    //  by uncommenting the following line. Check MAX30100_Registers.h for all the
    //  available options.
    // pox.setIRLedCurrent(MAX30100_LED_CURR_7_6MA);

    // Register a callback for the beat detection
   pox.setOnBeatDetectedCallback(onBeatDetected);
}

void loop()
{
    // Make sure to call update as fast as possible
    pox.update();

    // Asynchronously dump heart rate and oxidation levels to the serial
    // For both, a value of 0 means "invalid"
    if (millis() - tsLastReport > REPORTING_PERIOD_MS) {
        Serial.print("Heart rate:");
        Serial.print(pox.getHeartRate());
       Serial.print("bpm / SpO2:");
        Serial.print(pox.getSpO2());
        Serial.println("%");
 tsLastReport = millis();
}
}

 

  • Now upload the code on the NodeMCU board.
  • After uploading the code open the serial monitor and set the baud rate to 115200 to see the output.

 

Output on the serial monitor

 

Let’s understand the code

At the top, we have to initialize the libraries.

#include <Wire.h>
#include "MAX30100_PulseOximeter.h"

Now we will define the delay using REPORTING_PERIOD_MS and an object poxis created for the PulseOximeter class.

or

Here REPORTING_PERIOD_MS is defined for the delay and a pox object is created for the PulseOximeter class.

#define REPORTING_PERIOD_MS     1000
PulseOximeter pox;

A variable tsLastReport is created with the data type of uint32_t for storing the last beat time.

uint32_t tsLastReport = 0;

Now in the function onBeatDetected(), we will print Beat” as the sensor detects the finger above it. 

void onBeatDetected()
{
    Serial.println("Beat!");
}

 

In the setup function

We have set the baud rate as 115200 in the setup function.

Here, we have applied an if condition to detect the module and print its status of it.

Serial.begin(115200);
if (!pox.begin()) {
        Serial.println("FAILED");
        for(;;);
    } else {
        Serial.println("SUCCESS");
    }
}

Now this is the important function to set the IR LED current using setIRLedCurrent() function. 

The below code line set the current of 7.6mA for the IR LED.

pox.setIRLedCurrent(MAX30100_LED_CURR_7_6MA);

The default library IR LED current is set to 50mA which can cause the problems like LED not turning ON, Getting FAILED message on the serial monitor. At that time, change the LED current to a lower value.

Here are the other possible values you can use on the setIRLedCurrent() function.

  • MAX30100_LED_CURR_0MA
  • MAX30100_LED_CURR_4_4MA
  • MAX30100_LED_CURR_7_6MA
  • MAX30100_LED_CURR_11MA
  • MAX30100_LED_CURR_14_2MA
  • MAX30100_LED_CURR_17_4MA
  • MAX30100_LED_CURR_20_8MA
  • MAX30100_LED_CURR_24MA
  • MAX30100_LED_CURR_27_1MA
  • MAX30100_LED_CURR_30_6MA
  • MAX30100_LED_CURR_33_8MA
  • MAX30100_LED_CURR_37MA
  • MAX30100_LED_CURR_40_2MA
  • MAX30100_LED_CURR_43_6MA
  • MAX30100_LED_CURR_46_8MA
  • MAX30100_LED_CURR_50MA

Note, The brighter LED reaches deeper into your skin. So, you can set it according to it.

 

An important function is called the setup function which is setOnBeatDetectedCallback().

The function registers the callback function.

pox.setOnBeatDetectedCallback(onBeatDetected);

 

In the Loop function

We are using the update() function for getting the updated data from the sensor.

pox.update();

Functions such as getHeartRate() and getSpO2(), we receive the data on heart rate and the SpO2 percentage.

The sensor data would be printed on Serial Monitor every second as the REPORTING_PERIOD_MS is set to 1000.

Here we are using the millis function instead of the delay function because millis function executes an instruction for a specific time in microseconds whereas the delay function stops the code for a specific time.

Now we will print the Heart rate and SpO2 value on the serial monitor.

if (millis() - tsLastReport > REPORTING_PERIOD_MS) {
        Serial.print("Heart rate:");
        Serial.print(pox.getHeartRate());
        Serial.print("bpm / SpO2:");
        Serial.print(pox.getSpO2());
        Serial.println("%");
		tsLastReport = millis();
}

 

MAX30100 Not Working? Let’s see how to fix it.

The MAX30100 module is very famous among hobbyists, but the module has a serious design problem. Let’s see how to fix this issue.

There are two methods to fix this issue, both methods we have tested in our lab which are shown below.

Method 1(Tested)

  1. Disconnect the 4.7kΩ pull-up resistors from the 1.8V supply voltage by cutting the trace along the red line.
  2. Now pulled up all 4.7kΩ resistors to 3.3V by connecting using a piece of wire along the yellow line, as shown in the below diagram.

Below are the images which show the before and after connection.

Method 2 (Tested)

The second method is to remove all 4.7k pull-up resistors and use the external pull resistor on SCL, SDA, and INT signal lines later.

 

After removing the resistor connect the MAX30100 to the NodeMCU as shown in below diagram.

NodeMCU interfacing diagram with MAX30100 for Method 2

 

MAX30100 Heart Rate Sensor on Web Server using NodeMCU

Now let’s display the same readings on the web server using the NodeMCU and Arduino IDE.

Here we have modified the above example to display the result on the web server.

Before uploading the code make sure you have added your SSID, and Password as follows.

const char* ssid = "*Your SSID*";         /*Enter Your SSID*/
const char* password = "*Your Password*"; /*Enter Your Password*/

 

Code for NodeMCU and MAX30100 Heart Rate Sensor on Web Server

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include "MAX30100_PulseOximeter.h"
#include "html.h"

ESP8266WebServer server(80);

const char* ssid = "*Your SSID*";    /*Enter Your SSID*/
const char* password = "Your Password*"; /*Enter Your Password*/


#define REPORTING_PERIOD_MS     1000

// Creating object for PulseOximeter class as pox
PulseOximeter pox;

uint32_t tsLastReport = 0;
float HeartRate,SpO;

// Callback (registered below) fired when a pulse is detected
void onBeatDetected()
{
    Serial.println("Beat!");
}

void MainPage() {
  String _html_page = html_page;              /*Read The HTML Page*/
  server.send(200, "text/html", _html_page);  /*Send the code to the web server*/
}

void MAX30100() {
  String data = "[\""+String(HeartRate)+"\",\""+String(SpO)+"\"]";
  server.send(200, "text/plane", data);
}

void setup()
{
    Serial.begin(115200);
    WiFi.mode(WIFI_STA);                  /*Set the WiFi in STA Mode*/
    WiFi.begin(ssid, password);
    Serial.print("Connecting to ");
    Serial.println(ssid);
    delay(1000);                          /*Wait for 1000mS*/
    while(WiFi.waitForConnectResult() != WL_CONNECTED){Serial.print(".");} /*Wait while connecting to WiFi*/
    Serial.print("Connected to ");
    Serial.println(ssid);
    Serial.print("Your Local IP address is: ");
    Serial.println(WiFi.localIP());       /*Print the Local IP*/
   
    server.on("/", MainPage);             /*Display the Web/HTML Page*/
    server.on("/readmax30100", MAX30100); /*Display the updated Distance value(CM and INCH)*/
    server.begin();                       /*Start Server*/
    delay(1000);                          /*Wait for 1000mS*/


    Serial.print("Initializing pulse oximeter..");

    // Initialize the PulseOximeter instance
    // Failures are generally due to an improper I2C wiring, missing power supply
    // or wrong target chip
    if (!pox.begin()) {
        Serial.println("FAILED");
        for(;;);
    } else {
        Serial.println("SUCCESS");
    }

    // The default current for the IR LED is 50mA and it could be changed
    //  by uncommenting the following line. Check MAX30100_Registers.h for all the
    //  available options.
    // pox.setIRLedCurrent(MAX30100_LED_CURR_7_6MA);

    // Register a callback for the beat detection
   pox.setOnBeatDetectedCallback(onBeatDetected);
}

void loop()
{
    // Make sure to call update as fast as possible
    pox.update();
    server.handleClient();

    // Asynchronously dump heart rate and oxidation levels to the serial
    // For both, a value of 0 means "invalid"
    if (millis() - tsLastReport > REPORTING_PERIOD_MS) {
     
        HeartRate = pox.getHeartRate();
        Serial.print("Heart rate:");
        Serial.print(HeartRate);
       
        SpO = pox.getSpO2();
        Serial.print("bpm / SpO2:");
        Serial.print(SpO);
        Serial.println("%");
tsLastReport = millis();
    }
}

 

  • Now upload the code in NodeMCU.
  • Once you upload the code then open the Serial Monitor on IDE and set the baud rate to 115200. It is necessary reset the NodeMCU and check the IP address as shown below.

 

Output on the webserver

  • Open any internet browser on your mobile and type the IP address seen on the serial monitor and press enter button.
  • If everything goes all right, then you see the heading, a beeping heart, and the Heart rate and SpO2 values.

Note: make sure that both the devices NodeMcu and mobile are connected to the same wifi router/server, if they are connected to the same router then you will able to see the web page.

 

Let’s Understand the code

This code starts with important header files and libraries. 

All the NodeMCU wifi-related functions are included in the ESP8266WiFi.h header file. 

The ESP8266WebServer.h header file has all the definitions that support handling the HTTP GET and POST requests as well as setting up a local host server. 

The library MAX30100_PulseOximeter.h has all the functions for extracting the data from MAX30100 Module.

In the html.h file contains all the web page code that is in HTML language.

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include "MAX30100_PulseOximeter.h"
#include "html.h"

Now, we will HTTP port i.e., Port 80 as follows

ESP8266WebServer server(80);

 

Setup Function:

In the setup function, we set the wifi in the STA mode and connect it to the provided SSID and password

WiFi.mode(WIFI_STA);          /*Set the WiFi in STA Mode*/
WiFi.begin(ssid, password);
Serial.print("Connecting to ");
Serial.println(ssid);
delay(1000);                  /*Wait for 1000mS*/
while(WiFi.waitForConnectResult() != WL_CONNECTED){Serial.print(".");}

After successfully connecting to the server, we will print the IP address on the serial monitor of IDE.

Serial.print("Your Local IP address is: ");
Serial.println(WiFi.localIP());     /*Print the Local IP*/

 

Serving the webpage while handling the Client Request

For handling the client request we use Server.on() function.

We have passed two parameters in this function one is the URL path and the other is the function name to be executed.

So, when a client request is a root (/) path, the function MainPage() gets executed.

At the same time, the client requests the”/readmax30100” path, and the function MAX30100 is executed.

    server.on("/", MainPage);             /*Display the Web/HTML Page*/
    server.on("/readmax30100", MAX30100); /*Display the updated Distance value(CM and INCH)*/
    server.begin();                       /*Start Server*/
    delay(1000); 

Now, we will start the server using server.begin() function.

server.begin();                     /*Start Server*/

 

Functions for Serving HTML

Using the server.send() function, we send the complete HTML page to the client’s request.

In the server.send() function we have passed three parameters first is “200” which the status response code as OK (Standard response for successful HTTP request)

The second parameter is “text/html”, which is the content type we are sending to the client and the third is the “_html_page“ which is the html page code.

void MainPage() {
  String _html_page = html_page;              /*Read The HTML Page*/
  server.send(200, "text/html", _html_page);  /*Send HTM page to Client*/
}

In the MAX30100() function, we send the updated acceleration values to our web page.

void MAX30100() {
  String data = "[\""+String(HeartRate)+"\",\""+String(SpO)+"\"]";
  server.send(200, "text/plane", data);
}

 

Loop Function

Now we used the handleClient() function for handling incoming client requests and serving the HTML web page. 

So, serves and sends the data to the client. 

server.handleClient();

 

HTML Page Code

/*
  NodeMCU MAX30100 HTML WebServer Code 
  http:://www.electronicwings.com
*/

const char html_page[] PROGMEM = R"RawString(
<!DOCTYPE html>
<html>
  <style>
    h1 {text-align: center; font-size: 30px; font-family:sans-serif;}
    p{text-align: center; color: #4CAF50; font-size: 40px;}
    body{ padding:100px 0; }

#heart {
   
    width: 100px;
    height: 90px;
    animation: heartbeat 1s infinite;
    margin:0 auto
}
#heart:before,
#heart:after {
    position: absolute;
    content: "";
    left: 50px;
    top: 0px;
    width: 50px;
    height: 80px;
    background: #c33;
    -moz-border-radius: 50px 50px 0 0;
    border-radius: 50px 50px 0 0;
    -webkit-transform: rotate(-45deg);
       -moz-transform: rotate(-45deg);
        -ms-transform: rotate(-45deg);
         -o-transform: rotate(-45deg);
            transform: rotate(-45deg);
    -webkit-transform-origin: 0 100%;
       -moz-transform-origin: 0 100%;
        -ms-transform-origin: 0 100%;
         -o-transform-origin: 0 100%;
            transform-origin: 0 100%;
}

#heart:after {
    left: 0;
    -webkit-transform: rotate(45deg);
       -moz-transform: rotate(45deg);
        -ms-transform: rotate(45deg);
         -o-transform: rotate(45deg);
            transform: rotate(45deg);
    -webkit-transform-origin: 100% 100%;
       -moz-transform-origin: 100% 100%;
        -ms-transform-origin: 100% 100%;
         -o-transform-origin: 100% 100%;
            transform-origin :100% 100%;
}

      @keyframes heartbeat
      {
        0%
        {
          transform: scale( .75 );
        }
        20%
        {
          transform: scale( 1 );
        }
        40%
        {
          transform: scale( .75 );
        }
        60%
        {
          transform: scale( 1 );
        }
        80%
        {
          transform: scale( .75 );
        }
        100%
        {
          transform: scale( .75 );
        }
      }
     
  </style>

<body>
  <h1>MAX30100 Heart Rate Sensor using NodeMCU</h1><br>
  <div id="heart"></div>
  <p>Heart rate: <span id="heartrate">0</span>bpm</p>
  <p>SpO2 : <span id="spo2">0</span>%</p>
 
<script>
  setInterval(function() {
    var xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = function() {
      if (this.readyState == 4 && this.status == 200) {
        const text = this.responseText;
        const myArr = JSON.parse(text);
      document.getElementById("heartrate").innerHTML = myArr[0];
       document.getElementById("spo2").innerHTML = myArr[1];
      }
    };
    xhttp.open("GET", "readmax30100", true);
    xhttp.send();
  },50);
</script>
</body>
</html>
)RawString";

 

Let’s understand the code step by step

<!DOCTYPE html> is the standard declaration of all the html pages. It is just to inform the browser which type of document is expected.

<!DOCTYPE html>

The HTML tag is the container of the complete HTML page which represents the top of the HTML code.

<html>

Now, we will define the style for a web page using the <style> tag.

Inside the style tag, we have defined the style for the heading h1 and the paragraph p. You can also add images and hyperlinks, etc. in this tag.

Next, we have defined the style for the animation of the beeping of the heart.

The heart shape is created using two elements, ‘: before’ and ‘: after’. Using the position keyword the animation is aligned in the center.

The animation is created using the ‘@keyframes’ rule, which creates a series of keyframes that how the element should change over the defined time.

The heart starts at a scale of 0.75, then grows to a scale of 1, shrinks back down to 0.75, grows again to 1, shrinks again to 0.75, and finally remains at 0.75 for the final frame.

  <style>
    h1 {text-align: center; font-size: 30px; font-family:sans-serif;}
    p{text-align: center; color: #4CAF50; font-size: 40px;}
    body{ padding:100px 0; }

#heart {
   
    width: 100px;
    height: 90px;
    animation: heartbeat 1s infinite;
    margin:0 auto
}
#heart:before,
#heart:after {
    position: absolute;
    content: "";
    left: 50px;
    top: 0px;
    width: 50px;
    height: 80px;
    background: #c33;
    -moz-border-radius: 50px 50px 0 0;
   border-radius: 50px 50px 0 0;
    -webkit-transform: rotate(-45deg);
       -moz-transform: rotate(-45deg);
        -ms-transform: rotate(-45deg);
         -o-transform: rotate(-45deg);
            transform: rotate(-45deg);
    -webkit-transform-origin: 0 100%;
       -moz-transform-origin: 0 100%;
        -ms-transform-origin: 0 100%;
         -o-transform-origin: 0 100%;
            transform-origin: 0 100%;
}

#heart:after {
    left: 0;
    -webkit-transform: rotate(45deg);
       -moz-transform: rotate(45deg);
        -ms-transform: rotate(45deg);
         -o-transform: rotate(45deg);
            transform: rotate(45deg);
    -webkit-transform-origin: 100% 100%;
       -moz-transform-origin: 100% 100%;
        -ms-transform-origin: 100% 100%;
         -o-transform-origin: 100% 100%;
            transform-origin :100% 100%;
}

      @keyframes heartbeat
      {
        0%
        {
          transform: scale( .75 );
        }
        20%
        {
          transform: scale( 1 );
        }
        40%
        {
          transform: scale( .75 );
        }
        60%
        {
          transform: scale( 1 );
        }
        80%
        {
          transform: scale( .75 );
        }
        100%
        {
          transform: scale( .75 );
        }
      }
     
  </style>

 

Now Heart rate and SpO2 values are updated under the span id which is manipulated with JavaScript using the id attribute.

<body>
  <h1>MAX30100 Heart Rate Sensor using NodeMCU</h1><br>
  <div id="heart"></div>
  <p>Heart rate: <span id="heartrate">0</span>bpm</p>
  <p>SpO2 : <span id="spo2">0</span>%</p>

This is the javascript code that comes under the <script> tag, this is called a client-side script.

<script>

In setInterval() method a delay of 50ms is generated and then the function is called.

setInterval(function());

here we are creating the html XMLHttpRequest object

var xhttp = new XMLHttpRequest();

The ready state holds the status of XMLHttpRequest. The event xhttp.onreadstatechange is triggered every time the ready state changes. 

The ready state in the code below is 4 that is the request is finished and the status is 200 which means OK.

var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
	if (this.readyState == 4 && this.status == 200)

 

Now, here is the main thing, we are updating the heart rate and SpO2 values in html page using heartrate and spo2 id’s respectively

document.getElementById("heartrate").innerHTML = myArr[0];
document.getElementById("spo2").innerHTML = myArr[1];

Here we used the AJAX method to send the updated values to the server without refreshing the page.

In the below function, we have used the GET method and sent the readmax30100 function which we defined in the main code asynchronously. 

xhttp.open("GET", "readmax30100", true);

Send the request to the server using xhttp.send(); function.

xhttp.send();

Close the script

</script>

Close the body

</body>

Close the html.

</html>

Components Used

MAX30100 Pulse Oximeter and Heart-Rate Sensor
MAX30100 is an integrated pulse oximetry and heart-rate monitor sensor solution.
1
NodeMCU
NodeMCUNodeMCU
1

Downloads

MAX30100_Minimal Download
MAX30100_NodeMCU_Webserver Download
Ad