Measuring oxygen saturation and heart rate with the ESP8266 and MAX30101 Sensor: complete guide


As part of the continuous monitoring of our health, measuring oxygen saturation and heart rate is certainly an operation of primary importance. In the context of technological evolution, the integration of wearable health monitoring devices is transforming our ability to take care of ourselves more advanced than ever. In this scenario, the MAX30101 sensor emerges as a benchmark for simultaneously measuring oxygen saturation (SpO2) and heart rate. This small, but powerful, device offers not only an extraordinary opportunity for real-time monitoring, but is also suitable for communicating remotely via a device such as the ESP8266 thanks to IoT (Internet of Things) technology.

This article is a complete guide to using the MAX30101 sensor, which will teach you how to obtain SpO2 and heart rate measurements. But there’s more: you’ll discover how to view this data in real time through a TFT display and connect it to the Arduino Cloud ecosystem for remote viewing. This approach opens up new perspectives for health monitoring, allowing instant access to your data wherever you are.

Whether you are a tech-savvy experimenter or an individual eager to monitor your health, you will learn how to fully exploit the potential of the MAX30101 sensor for continuous monitoring and remote data sharing. We’ll explore the installation, setup, and data transmission steps and walk you through understanding how this device is changing the way we approach health tracking.

Also in this project we will use the excellent PlatformIO as an IDE.

What the MAX30101 sensor is and how it works

The MAX30101 is an optical tracking sensor that is primarily designed to measure heart rate and oxygen saturation (SpO2). It is widely used in health monitoring applications, wearable devices, medical electronics and DIY projects for monitoring physical conditions. It communicates via the I2C bus. Here is a detailed explanation of what the MAX30101 is and how it works:

Sensor Description:

  • The MAX30101 is a module that combines a red LED light source, an infrared (IR) LED light source and a photodiode for detecting the return waves of these lights.
  • It uses an optical reflection technique, where LED lights emit radiation into the skin and the photodiode detects changes in absorption of these lights due to blood flow.
  • The sensor can measure both light reflected by the skin (used to detect heartbeats) and absorbed light (used to calculate SpO2).

Sensor Operation:

  1. LED light output: the sensor emits red and infrared LED light into the subject’s skin. This light penetrates the blood cells.
  2. Light Absorption: blood cells containing hemoglobin absorb these wavelengths of light differently depending on whether they are oxygenated or not. The returning waves of the LED light are detected by the photodiode.
  3. Data Processing: the detected signal is then processed by a microcontroller (such as ESP8266) using sophisticated algorithms. Peaks in the reflective signal are identified to calculate the heart rate.
  4. SpO2 Calculation: to calculate SpO2, measurements of both LED lights are taken. The difference between the absorption of red and infrared light is used to determine oxygen saturation.
  5. Data visualization: the results are then displayed (for example on a display or transmitted to a mobile application or uploaded to a cloud service for remote monitoring).

Common Applications:

  • Real-time heart rate monitoring during exercise or sleep.
  • SpO2 measurement in resting conditions or during physical activities.
  • Use in medical devices to monitor patients.
  • Health monitoring in wearable devices such as smartwatches and fitness bracelets.
  • DIY projects for health monitoring.

The MAX30101 is an extremely versatile sensor and offers the ability to obtain important medical measurements in a compact format. Its applicability across a wide range of devices makes it a key component in health monitoring technology.

Important Notice

It is essential to underline that the project you are about to undertake, based on the use of the MAX30101 sensor for measuring heart rate and oxygen saturation (SpO2), was created mainly for educational and experimental purposes. The results obtained through this project may not be comparable to or in any way replace official medical measurements.

Using the MAX30101 sensor and related devices can offer a rough view of your heart rate and oxygen levels, but it is important to understand that this data should never replace professional medical consultations or certified medical equipment!!!

Any health monitoring project should be approached with caution and responsibility. Always be sure to consult a medical professional to properly evaluate your health status and rely on certified medical devices for precise and reliable measurements.

Please remember that this project is intended for educational and technological exploration purposes. It is an opportunity to learn and experiment with new technologies, but it should never replace qualified medical advice. Your health is a priority, and the use of monitoring devices must be accompanied by competent medical supervision.

The I2C bus

The I2C (Inter-Integrated Circuit) bus is the bus through which the MAX30101 sensor communicates with the ESP8266. It is a popular serial communication protocol used to connect various devices and sensors to microcontrollers, microprocessors and other embedded devices. Here are some key points to consider:

  1. Two-wire communication: the I2C bus uses only two wires for communication: one for data transmission (SDA, Serial Data) and one for clock synchronization (SCL, Serial Clock). This simplicity makes it ideal for connecting multiple devices on a single bus.
  2. Master and Slave: in I2C communication, there is a master device called “master” and one or more secondary devices called “slave”. The master initiates and controls the communication, while the slaves respond to the master’s requests.
  3. Addressing: each slave device has a unique address on the I2C bus. This allows the master to select the device it wishes to communicate with. Multiple devices with the same address cannot coexist on the same bus.
  4. Half-Duplex: the I2C bus supports half-duplex communication, which means that data can only flow in one direction at a time. However, it is possible to reverse the direction of communication during transmission.
  5. Variable Speed: the I2C bus supports a variety of communication speeds, allowing you to tailor the speed to specific application needs.
  6. Widely Used: the I2C bus is widely used in a wide range of devices, including sensors, memory devices, displays, microcontrollers and many more. It is a common choice in IoT devices and embedded designs.
  7. Pull-Up Resistors: to ensure reliable communication, the I2C bus requires the use of pull-up resistors on the SDA and SCL outlets. These resistors help stabilize the signal and prevent noise.
  8. High-level protocol: the I2C bus is a high-level protocol that simplifies communication between devices, allowing developers to focus on application logic rather than managing communication.

In summary, the I2C bus is a reliable and flexible communication protocol that offers a convenient way to connect devices and sensors to a microcontroller or microprocessor. Its breadth of use and ease of implementation make it a popular choice in embedded electronics and IoT.

TFT displays

Thin-Film Transistor (TFT) displays represent a category of advanced liquid crystal displays (LCDs) that have established themselves as one of the most popular and versatile display technologies. Here are some key points to consider:

Image Quality: TFT displays are known for delivering high-quality images with brilliant colors and sharp details. This makes them ideal for a wide range of applications, from viewing photos and videos to UI applications.

Backlight: most TFT displays use LED backlight, which provides a uniform light source and allows images to be viewed even in bright environments. This backlight is adjustable as needed, which helps save energy.

Resolution: TFT displays are available in various resolutions, from small 1.8-inch screens with QVGA resolution to larger screens with Full HD resolutions or higher. The choice of resolution depends on the specific needs of the application.

Touchscreen: many TFT screens feature capacitive or resistive touchscreen technology, which allows direct user interaction with the display. This is widely used in mobile, tablet, ATM and other interactive devices.

Wide Applicability: TFT displays are used in a wide range of devices, including smartphones, tablets, monitors, televisions, smart watches, medical diagnostic tools, GPS, industrial automation devices and much more.

Connections and Controls: TFT displays are designed for integration with various types of controllers and microcontrollers. This makes them ideal for implementation in DIY electronics projects and custom applications.

Power Consumption: TFT technology is designed to maintain low power consumption whenever possible, making it suitable for battery-powered devices.

Choice of type: in addition to color TFT displays, there are also grayscale, monochrome, and high-brightness TFT displays designed for specific applications.

In conclusion, TFT displays are a popular and reliable choice for many applications, thanks to their image quality, versatility and ease of integration. They have become an integral part of our daily lives, contributing to the visual experience in devices of all kinds.

Arduino Cloud

Arduino Cloud is a cloud-based platform developed by Arduino that offers a complete solution for managing and connecting Arduino-based devices, such as Arduino MKR, ESP8266 and ESP32. This platform is designed to simplify the creation, configuration and monitoring of connected devices, allowing developers to focus on developing their applications without having to worry about the underlying infrastructure.

One of the key features of Arduino Cloud is its ease of use. Developers can register their Arduino devices to the platform and quickly configure them through an intuitive user interface. Once devices are online, you can monitor them in real time, collect data, initiate remote actions and update firmware centrally.

Arduino Cloud also provides advanced device security and authentication features, ensuring that communications between devices and the cloud are secure and private. Additionally, it offers a wide range of services and integrations that allow developers to connect their Arduino devices to third-party services, such as AWS, Google Cloud, IFTTT, and many more.

The platform is particularly suitable for IoT (Internet of Things) projects where it is necessary to monitor, control and manage remote devices efficiently. With Arduino Cloud, developers can accelerate IoT application development and focus on innovation rather than infrastructure complexity.

What components do we need?

The list of components is not particularly long:

Let’s see in the following photo the type of TFT display used in this project:

The 1.8" TFT display
The 1.8″ TFT display

Project implementation

The wiring scheme

Before creating the actual circuit let’s take a look at the pinout of the board:

ESP8266 NodeMCU pinout
ESP8266 NodeMCU pinout

Let’s see the pinout of the MAX30101 sensor:

MAX30101 sensor pinout
MAX30101 sensor pinout

The MAX30101 sensor may not have a soldered connector so, if you need to solder it, I recommend you take a look at the article first Yet another tutorial on how to solder.

Let’s also see the display pinout:

1.8" TFT display pinout
1.8″ TFT display pinout

For our project we will only consider the pins on the left side (with yellow connector on this photo).

Typically this display is already sold with the connector mounted but if yours doesn’t have it and you need to solder it, I recommend you take a look at article Yet another tutorial on how to solder first to learn how to make a perfect solder.

Before moving on to the wiring diagram you will need to carry out a small operation. The display can work at both 5V and 3.3V but since the digital pins of the ESP8266 do not tolerate voltages higher than 3.3V we will have to prepare the display to work with this voltage.

To do this we will simply have to short circuit jumper J1 i.e. the one shown in the following photo:

Jumper J1 to be shorted to operate the TFT display at 3.3V
Jumper J1 to be shorted to operate the TFT display at 3.3V

As you can see, it’s a fairly simple operation: a small drop of tin is enough to short-circuit the two pads indicated in red.

Let’s now see the electrical diagram of the project, created as usual with Fritzing:

The electrical diagram of the device for measuring oxygen saturation and heart rate
The electrical diagram of the device for measuring oxygen saturation and heart rate

For greater convenience I will report below the connection table between display and ESP8266:

TFT Display ESP8266
TFT display connections

Let’s create the objects and the dashboard on the cloud

First, if you haven’t already done so, you need to create an account on Arduino Cloud.

Once you have logged in you will be faced with this page:

Arduino Cloud home page
Arduino Cloud home page

Click on the GET STARTED button and you will be sent to the page:

Second page of Arduino Cloud
Second page of Arduino Cloud

Click on the IoT Cloud button. The Things page will open.

At this point you will proceed to:

  • create the device that will map the board
  • create the object (thing) with the variables associated with the device created in the previous step
  • create the dashboard to associate with the variables

We will see the procedure just listed with this video:

In this video we created:

  • the object (thing) called health_thing with its heartRateCloud and SPO2Cloud variables (both as Name and Declaration). Both of the integer type, read only and with a periodicity of 1 second;
  • the device named healt_device and type ESP8266 NodeMCU 1.0 (ESP-12E Module)
  • the dashboard named healt_dashboard with two chart-type diagrams, one named Heart Rate which displays the heartRateCloud variable and the other named SPO2 which displays the SPO2Cloud variable (both variables belong to the health_thing).

PLEASE NOTE: it is important to note that in the video, at minute 3:18, I downloaded a pdf that contains the connection parameters to the device. They are called Device ID and Secret Key and we will need them later. So download this pdf too and keep it somewhere.

Obviously the Device ID and Secret Key parameters are specific to each device, therefore they change as the device changes.

The sketch

Let’s create the PlatformIO project

We have already seen the procedure for creating a PlatformIO project in the article How to create a project for NodeMCU ESP8266 with PlatformIO.

Do not install the libraries indicated in that article but install the Arduino_ConnectionHandler library, always following the usual procedure :

Installing the Arduino_ConnectionHandler library
Installing the Arduino_ConnectionHandler library

and the ArduinoIoTCloud library:

Installing the ArduinoIoTCloud library
Installing the ArduinoIoTCloud library

Then install the Adafruit GFX Library by Adafruit library:

Install the Adafruit GFX Library
Install the Adafruit GFX Library

and the Adafruit ST7735 and ST7789 Library by Adafruit library:

Install the Adafruit ST7735 and ST7789 Library
Install the Adafruit ST7735 and ST7789 Library

Finally you need to install the SparkFun MAX3010x Pulse and Proximity Sensor Library by SparkFun Electronics library:

Install SparkFun MAX3010x Pulse and Proximity Sensor Library by SparkFun Electronics
Install SparkFun MAX3010x Pulse and Proximity Sensor Library by SparkFun Electronics

Now edit the platformio.ini file to add these two lines:

monitor_speed = 115200
upload_speed = 921600

so that it looks like this:

platform = espressif8266
board = nodemcuv2
framework = arduino
monitor_speed = 115200
upload_speed = 921600
lib_deps = sparkfun/SparkFun MAX3010x Pulse and Proximity Sensor Library@^1.1.2
           adafruit/Adafruit GFX Library@^1.11.9
	       adafruit/Adafruit ST7735 and ST7789 Library@^1.10.3

Now download the project from the link below:

Unzip the project you just downloaded and overwrite the main.cpp file in the src folder of the project created before.

Then, from the include folder of the project you just downloaded and unzipped, take the thingProperties.h file and copy it to the include folder of the project created before.

At this point you will have to edit this file in order to connect it to the cloud:


const char SSID[]               = SECRET_SSID;    // Network SSID (name)
const char PASS[]               = SECRET_OPTIONAL_PASS;    // Network password (use for WPA, or use as key for WEP)
const char DEVICE_KEY[]  = SECRET_DEVICE_KEY;    // Secret device password

As you can see, in this section there are some parameters to set.

The first parameter (DEVICE_LOGIN_NAME) can be found in the pdf you downloaded when you created the object (you can find the reference in the previous video). In the pdf the parameter is called Device ID.

The SECRET_SSID and SECRET_OPTIONAL_PASS parameters are, respectively, the name and password of your WiFi network. So, instead of SECRET_SSID you will put the name of your network in double quotes (“) and instead of SECRET_OPTIONAL_PASS you will put your network password (always in double quotes).

The last parameter, SECRET_DEVICE_KEY, can always be found in the pdf you downloaded from the platform with the name Secret Key.

So the section in question should look like this:

const char DEVICE_LOGIN_NAME[]  = "Device ID from the downloaded pdf";
const char SSID[]               = "my_wifi_ssid";    // Network SSID (name)
const char PASS[]               = "my_wifi_password";    // Network password (use for WPA, or use as key for WEP)
const char DEVICE_KEY[]  = "Secret Key from the downloaded pdf";    // Secret device password

As you can see, I put the value taken from the PDF under Device ID in the DEVICE_LOGIN_NAME field, in the SSID field the SSID of my WiFi network (in this case my_wifi_ssid), in the PASS field the password of my WiFi network (in this case my_wifi_password ) and in the DEVICE_KEY field the value taken from the PDF under Secret Key.

This sketch is mainly inspired by an example present in the Sparkfun library. To it I added the management part of the TFT display and the connection with Arduino Cloud.

Let’s look at it in more detail.

Initially, the thingProperties.h file is included, the Wire library necessary for the I2C communication of the sensor, the sensor management library (even if the sensor is called MAX30101 it is managed by the MAX30105 library), the spo2_algorithm file which contains the SPO2 calculation and Adafruit libraries for display management:

#include "thingProperties.h"

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

#include <Adafruit_GFX.h>      // include Adafruit graphics library
#include <Adafruit_ST7735.h>   // include Adafruit ST7735 TFT library

The connections with the display, the tft object that manages the display, the particleSensor object that manages the MAX30101 sensor and a series of buffers and variables used by the sketch are defined below:

// ST7735 TFT module connections
#define TFT_RST   D4     // TFT RST pin is connected to NodeMCU pin D4 (GPIO2)
#define TFT_CS    D3     // TFT CS  pin is connected to NodeMCU pin D3 (GPIO0)
#define TFT_DC    D0     // TFT DC  pin is connected to NodeMCU pin D0 (GPIO16)

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

MAX30105 particleSensor;

#define MAX_BRIGHTNESS 255

#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)
//Arduino Uno doesn't have enough SRAM to store 100 samples of IR led data and red led data in 32-bit format
//To solve this problem, 16-bit MSB of the sampled data will be truncated. Samples become 16-bit data.
uint16_t irBuffer[100]; //infrared LED sensor data
uint16_t redBuffer[100];  //red LED sensor data
uint32_t irBuffer[100]; //infrared LED sensor data
uint32_t redBuffer[100];  //red LED sensor data

int32_t bufferLength; //data length
int8_t validSPO2; //indicator to show if the SPO2 calculation is valid
int8_t validHeartRate; //indicator to show if the heart rate calculation is valid

String heartRateToShow = "";
String SPO2ToShow = "";

The variables that set the reading time of the values ​​of interest (heart rate and blood oxygen) for the display and for Arduino Cloud are then defined (we take a reading per second):

unsigned long lastTimeRanCycle;
unsigned long measureDelayCycle = 1000;  // 1s

Below we find the setup function.

Initially the serial port is initialized:

Serial.begin(115200); // initialize serial communication at 115200 bits per second:
Serial.println("Serial port ready!");

The initialization part of the connection with Arduino Cloud is then defined:

  // Defined in thingProperties.h

  // Connect to Arduino IoT Cloud
     The following function allows you to obtain more information
     related to the state of network and IoT Cloud connection and errors
     the higher number the more granular information you’ll get.
     The default is 0 (only errors).
     Maximum is 4

and the display is initialized:


The initialization of our sensor follows:

  // Initialize sensor
  if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) //Use default I2C port, 400kHz speed
    Serial.println(F("MAX30101 was not found. Please check wiring/power."));
    while (1);

Now comes a very important part. The part dedicated to the algorithm parameters:

byte ledBrightness = 60; //Options: 0=Off to 255=50mA
byte sampleAverage = 1; //Options: 1, 2, 4, 8, 16, 32
byte ledMode = 2; //Options: 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green
byte sampleRate = 400; //Options: 50, 100, 200, 400, 800, 1000, 1600, 3200
int pulseWidth = 411; //Options: 69, 118, 215, 411
int adcRange = 16384; //Options: 2048, 4096, 8192, 16384

particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange); //Configure sensor with these settings

The optimal values ​​for each of these parameters vary for each different sensor application. Sparkfun gives values ​​that I tried to adapt to my case to make the algorithm work better. You can start optimizing them by better understanding their meaning:

  • adcRange: the sensor transmits red and infrared (IR) light and measures the reflected light using an ADC. This parameter sets the resolution of the ADC. Try starting with 2048 when calibrating and move higher as needed. It is possible to view the raw IR and RED light values ​​(in the sketch the prints on the Serial Monitor are commented out for convenience, to see these values ​​you must uncomment them. They are under the name PRINTS BLOCK). The signals should not be saturated.
  • ledBrightness determines the intensity of red and IR light. You can lower the brightness to save power in battery-powered applications (but you will have a terrible SNR value). Or you can increase the brightness to get good SNR.
  • pulseWidth sets the activation time of the RED and IR light. Smaller pulse widths with a low sampling rate are preferable, but noise will be high.
  • sampleAverage sets the sampling rate for the internal ADC. You will have to tweak it by trial and error to get the most stable readings possible from the ADC
  • ledMode MAX30101 supports Red, Red and IR, Red + IR + Green
  • sampleRate ADC sampling rate. Increase/decrease it based on LED brightness, sampleAverage and pulse width.

In short, you will have to do quite a bit of experimentation to find a set of values ​​that will give you the most accurate and stable readings possible.

I calibrated these parameters by comparing the heart rate and blood oxygen with the values ​​detected by commercial devices. I noticed that, no matter how hard I try to carry out the calibration, the heart rate value does not remain stable enough (the oxygen one does). When measuring heart rate with the MAX30101 sensor, you will get much more reliable and stable readings if you secure the sensor to your fingertip with a piece of tape or a rubber band so that the contact between your finger and the sensor is as stable as possible.

Now let’s look at the loop function.

Initially the connection to the Arduino Cloud is updated. Then a buffer is defined to contain the samples detected by the sensor, finally a message is printed on the display inviting the user to wait for the conclusion of the preliminary measurements:


bufferLength = 100; //buffer length of 100 stores 4 seconds of samples running at 25sps

tft.setCursor(0, 0);
tft.println("Measurements in progress: wait a moment");

Then follows a block that collects the first 100 samples of the signal received from the sensor in order to determine its excursion:

//read the first 100 samples, and determine the signal range
for (byte i = 0 ; i < bufferLength ; i++)
while (particleSensor.available() == false) //do we have new data?
    particleSensor.check(); //Check the sensor for new data

redBuffer[i] = particleSensor.getRed();
irBuffer[i] = particleSensor.getIR();
particleSensor.nextSample(); //We're finished with this sample so move to next sample

    Serial.print(redBuffer[i], DEC);
    Serial.print(F(", ir="));
    Serial.println(irBuffer[i], DEC);

Immediately afterwards a message is printed on the display warning the user that this preliminary phase has been completed. After which the calculation algorithm is launched on the 100 samples collected to determine the heart rate and saturation:

tft.setCursor(0, 0);
tft.println("Measurements completed");

//calculate heart rate and SpO2 after first 100 samples (first 4 seconds of samples)
maxim_heart_rate_and_oxygen_saturation(irBuffer, bufferLength, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate);

Subsequently, an infinite loop (while(1)) begins which performs various operations cyclically. In this first block the connection to the Arduino Cloud is updated and a message is printed on the display in case of failure:

if(!ArduinoCloud.connected()) {      
    tft.setCursor(0, 0);
    tft.println("Trying to connect to Arduino Cloud");
} else {
    Serial.println("Connected to the cloud");

The following code continues to calculate heart rate and saturation values:

//dumping the first 25 sets of samples in the memory and shift the last 75 sets of samples to the top
for (byte i = 25; i < 100; i++)
    redBuffer[i - 25] = redBuffer[i];
    irBuffer[i - 25] = irBuffer[i];

//take 25 sets of samples before calculating the heart rate.
for (byte i = 75; i < 100; i++)
    while (particleSensor.available() == false) //do we have new data?
    particleSensor.check(); //Check the sensor for new data

    // digitalWrite(readLED, !digitalRead(readLED)); //Blink onboard LED with every data read

    redBuffer[i] = particleSensor.getRed();
    irBuffer[i] = particleSensor.getIR();
    particleSensor.nextSample(); //We're finished with this sample so move to next sample
    //send samples and calculation result to terminal program through UART
    Serial.print(redBuffer[i], DEC);
    Serial.print(F(", ir="));
    Serial.print(irBuffer[i], DEC);

    Serial.print(F(", HR="));
    Serial.print(heartRate, DEC);

    Serial.print(F(", HRvalid="));
    Serial.print(validHeartRate, DEC);

    Serial.print(F(", SPO2="));
    Serial.print(spo2, DEC);

    Serial.print(F(", SPO2Valid="));
    Serial.println(validSPO2, DEC);

//After gathering 25 new samples recalculate HR and SP02
maxim_heart_rate_and_oxygen_saturation(irBuffer, bufferLength, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate);

In particular, the ones that interest us most are spo2, validSPO2, heartRate, validHeartRate.

The validSPO2 and validHeartRate values ​​tell us whether the values ​​contained in spo2 and heartRate respectively are valid and therefore to be taken into consideration or discarded. If spo2 and heartRate are to be taken into consideration we will have validSPO2 and validHeartRate equal to 1.

The following block is the usual if block which times the operations carried out within it (in this case the period is equal to 1 second).

if (millis() > lastTimeRanCycle + measureDelayCycle)  
if(validHeartRate == 1) {
    heartRateToShow =  String(heartRate);
    heartRateCloud = heartRate;
} else {
    heartRateToShow = "0";
    heartRateCloud = 0;

if (validSPO2 == 1) {
    SPO2ToShow =  String(spo2);
    SPO2Cloud = spo2;
} else {
    SPO2ToShow = "0"; 
    SPO2Cloud = 0;

/*       Serial.print(F("HR="));
    Serial.print(heartRate, DEC); */
    tft.setCursor(0, 0);
    tft.println("HR: " + heartRateToShow);

/*       Serial.print(F(", SPO2="));
    Serial.print(spo2, DEC); */
    tft.println("\n\n\nSPO2: " + SPO2ToShow);


lastTimeRanCycle = millis();

Only if validHeartRate is equal to 1, updates the heartRateToShow variable (whose value will be shown on the display) and the heartRateCloud variable (whose value will be shown on the cloud). In the same way, only if validSPO2 is equal to 1 does it update the SPO2ToShow variable (whose value will be shown on the display) and the SPO2Cloud variable (whose value will be shown on the cloud).

If the values ​​are invalid, both on the display and on the Cloud dashboard they will conventionally be indicated with the value 0.

The final part of the block deals with updating the TFT display while the Arduino Cloud will be automatically updated from its library every time it reads the heartRateCloud and SPO2Cloud variables (approximately every second).

How to use the device for measuring oxygen saturation and heart rate

With the device connected to the PC via USB, place the tip of your index finger on the sensor. Upload the sketch and wait for preliminary measurements to be made. You will be notified by a message on the display. At this point the sketch applies the algorithm on the values ​​coming from the sensor and begins to show the measurements on the display and on the Arduino Cloud dashboard. If you try to move your finger away from the sensor, after a few seconds the measurement should show the value 0 for both parameters (indicating their invalidity).

By trial and error, try modifying the values ​​of the algorithm parameters as explained above in order to have values ​​that are as reliable and stable as possible (you will need reference measuring instruments such as a heart rate monitor and a pulse oximeter).

Video of the operation of the oxygen saturation and heart rate measurement system

To have stable measurements, the fingertip should exert constant pressure on the sensor and remain firmly in place. It would also be advisable to assume a stable position (for example sitting).

So what are you still waiting for? Sign-up to Arduino Cloud for free:


If you want to be informed about the release of new articles, subscribe to the newsletter. Before subscribing to the newsletter read the page Privacy Policy (UE)

If you want to unsubscribe from the newsletter, click on the link that you will find in the newsletter email.

Enter your name
Enter your email
0 0 votes
Article Rating
Inline Feedbacks
View all comments
Would love your thoughts, please comment.x
Scroll to Top