Arduino Heart Rate Monitor Using MAX30102 and Pulse Oximetry
“As an Amazon Associates Program member, clicking on links may result in Maker Portal receiving a small commission that helps support future projects.”
Pulse oximetry monitors the oxygen saturation in blood by measuring the magnitude of reflected red and infrared light [read more about pulse oximetry here and here]. Pulse oximeteters can also approximate heart rate by analyzing the time series response of the reflected red and infrared light . The MAX30102 pulse oximeter is an Arduino-compatible and inexpensive sensor that permits calculation of heart rate using the method described above. In this tutorial, the MAX30102 sensor will be introduced along with several in-depth analyses of the red and infrared reflection data that will be used to calculate parameters such as heart rate and oxygen saturation in blood.
The primary components needed for this tutorial are the MAX30102 pulse oximeter and an Arduino microcontroller. I will also be using a Raspberry Pi to read high-speed serial data printed out by the Arduino. I’m using the Raspberry Pi because I will be analyzing the pulse data with robust Python programs and libraries that are not available on the Arduino platform. The parts list for the experiments is shown below:
The MAX30102 uses two-wire I2C communication to interface with Arduino Uno board. I use the I2C ports on the A4/A5 ports on the Arduino board. The wiring is shown below:
Sparkfun has a library that handles the communication between Arduino and MAX30102. We will be using the Sparkfun library to handle high-speed readout of the red and IR reflectance data.
In the Arduino IDE:
Go to Sketch -> Include Library -> Manage Libraries
Type in “max30” into the search bar
Download the “Sparkfun MAX3010x Pulse and Proximity Sensor Library”
A simple high-speed setup based around the Arduino Uno is shown below. It samples the MAX30102 at 400 Hz and prints to the serial port. At 400 Hz and a serial baud rate of 115200, the Raspberry Pi is capable of reading each data point without issues.
With a finger attached to the MAX30102 (either by rubber band, tape, or encapulation), the printout to the Arduino serial plotter should look as follows:
We don’t need to worry about the inability to track the shape of each plot, as we will read these into Python using the serial reader and fully analyze the red and IR data from the MAX30102 sensor. In the next section, we will be reading the real-time 400 Hz data into Python and analyzing the data using several complex algorithms ranging from frequency domain analysis and wavelet analysis.
We can read the Arduino serial output data on the Raspberry Pi using Python’s serial library. In the Arduino code above, the only change we need to make is to add a printout of the ‘micros()’ function to attach a timestamp to the data readings of red and IR reflectivity values. The Arduino code is shown below:
A high-speed serial readout algorithm for Python is shown below for reading the Arduino serial printout values. The Python code will acquire the data and save it into a .csv file for later analysis. The reason why we save them right away is that the data is coming in at such high speeds that we want to minimize the amount of processing done in-between the serial acquisitions. This code for saving the Arduino printout data in Python is shown below:
The final plot produced by the Python code above should look as follows:
In the next section, I will explore several methods for analyzing the time series data shown in the plot above. I will also discuss some frequency domain and wavelet analyses for determining periodicity of pulses for heart rate approximation and blood oxygenation.
Below are a few links to scientific research that has been conducted on pulse oximetry and the relationship to oxygenation in the circulatory system:
In this section, I will introduce the basic relationship between red and infrared reflectance values measured by the MAX30102 pulse oximeter and heart rate. In the publication entitled “Calibration-Free Pulse Oximetry Based on Two Wavelengths in the Infrared — A Preliminary Study,” the following figure is presented as a PPG pulse where the light transmitted through tissue is shown to decreases during an event called the systole (the heart contracts and pumps blood from its chambers to the arteries), and increases during diastole (heart relaxes and its chambers fill with blood).
If we were to zoom in on one of our MAX30102 pulses, we would see nearly the exact same profile in the red and IR responses:
We can use this cyclic behavior to approximate the interval between heart ‘beats’ to determine the rough heart rate of an individual. The simplest way to calculate the heart rate is to record a few seconds of red or infrared reflectance data and calculate the dominant frequency content of the signal. If we use Python’s Fast Fourier Transform (FFT in Numpy), the peak of the FFT approximates the frequency of the heart’s contraction and relaxation cycle - what we call the heart rate.
The simplest way to calculate the heart rate is to record a few seconds of red or infrared reflectance data and calculate the dominant frequency content of the signal.
The code and plot below show the FFT method for approximating heart rate for a 9 second sample of MAX30102 data.
From my own experience with this method, I recommend at least 8 seconds of reflectivity data for calculating a true heart rate. Below this, the FFT will start seeing different frequency content in the signal. This could possibly be avoided by filtering, but I haven’t included that here.
The difficulty in using the FFT for calculation of heart rate is the required number of cycles. Several cycles are needed for accurate frequency approximation. Therefore, another method is introduced here which uses a second order gradient function to approximate the rate of change of the pulse. Since the steepest point during the circulatory cycle is the systolic point (heart contraction), we can use this fact to develop a peak-finding algorithm that looks for each systolic gradient peak.
The gradient code in Python is shown below. It does the following:
Record 4 seconds of MAX30102 data
Smooth the data with a short convolution
Calculate the gradient with Numpy’s ‘gradient()’ function
Look for peaks where the systolic gradient is maximum
Approximate heart rate from period between peaks
The code above plots the 4s section of heart rate data and places black dots over the systolic gradient peak. The period between each successive dot is used to calculate the heart rate using the sample rate. An example output plot is shown below with the BPM approximation.
In this tutorial, the MAX30102 pulse oximeter sensor was introduced along with its Arduino-compatible library and basic functionality. The MAX30102 data was then analyzed using Python to approximate the cyclic behavior of the heart’s contraction and relaxation period to measure heart rate. Then, a more accurate and stable gradient analysis of the reflectivity data was used to find peaks in the systolic behavior of the heart to calculate the heart’s periodic frequency. The goal of this tutorial was to demonstrate the power of an inexpensive pulse oximeter for health monitoring, while also exploring advanced methods in data analysis for real-world application.
See More in Arduino and Python: