This page outlines the basics of this project and should be used in conjunction with the video below;
Intro
Some of you may be aware of the handy pulse and oxygen monitors used by doctors where they simply slip a small unit over one of your fingers and they get a quick reading of heart rate and blood oxygen saturation. This project does the same thing but also adds an “ECG” graph display as well. The sensor we’re using is the MAX30100 (or MAX30102 which appears compatible).
How does it work?
These operate in a similar way to the simple green LED pulse sensors as used in this project. But they crucially use two, one red LED and one Infra-Red. These are both shone into the finger and the amount of reflected light determines amount of blood flow and amount of oxygen present. By doing some clever stuff with these two values the sensor/software library can calculate the pulse and oxygen saturation %.
The MAX30100 sensor used
Below is a picture of the sensor used.
They do need a small hack to work with Arduino which is covered in the video. But basically you need to remove the three 472 resistors shown above and add some 4K7 ones to your breadboard/prototype board. There is a version of the MAX30100 board that is supposed to not need this hack but I failed to get it to work with my set-up so I’ve ignored it for now.
The circuit
Below is the circuit diagram for the unit.
Note the 4.7K resistors, this is explained on the video, but basically it allows our 5V Arduino to talk with this 3.3V sensor.
The Code
The required libraries to install are covered in the video so this is not covered here. Below is the source code for the project as shown in the video.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
/* XTronical graphical Pulse and Oxygen monitor Displays BPM and blood oxygen saturation % as well as a trace representing the rise and fall of blood flow through your capillaries. Note that although the trace looks very ECG like that it is representing blood flow and not direct electrical activity of the heart although it is obviously directly related to the heart function. See <https://www.xtronical.com/ColourHeartMonitor> for more details or the you tube video : 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 "MAX30100.h" // Libraries required for the #include "MAX30100_PulseOximeter.h" // MAX30100 range sensors #include <Adafruit_GFX.h> // Graphics library for drawin on screen #include <Adafruit_ST7735.h> // Hardware-specific library // Recommended settings for the MAX30100, DO NOT CHANGE!!!!, refer to the datasheet for further info #define SAMPLING_RATE MAX30100_SAMPRATE_100HZ // Max sample rate #define IR_LED_CURRENT MAX30100_LED_CURR_50MA // The LEDs currents must be set to a level that #define RED_LED_CURRENT MAX30100_LED_CURR_27_1MA // avoids clipping and maximises the dynamic range #define PULSE_WIDTH MAX30100_SPC_PW_1600US_16BITS // The pulse width of the LEDs driving determines #define HIGHRES_MODE true // the resolution of the ADC // Create objects for the raw data from the sensor (used to make the trace) and the pulse and oxygen levels MAX30100 sensor; // Raw Data PulseOximeter pox; // Pulse and Oxygen // The following settings adjust various factors of the display #define SCALING 12 // Scale height of trace, reduce value to make trace height // bigger, increase to make smaller #define TRACE_SPEED 0.5 // Speed of trace across screen, higher=faster #define TRACE_MIDDLE_Y_POSITION 41 // y pos on screen of approx middle of trace #define TRACE_HEIGHT 64 // Max height of trace in pixels #define HALF_TRACE_HEIGHT TRACE_HEIGHT/2 // half Max height of trace in pixels (the trace amplitude) #define TRACE_MIN_Y TRACE_MIDDLE_Y_POSITION-HALF_TRACE_HEIGHT+1 // Min Y pos of trace, calculated from above values #define TRACE_MAX_Y TRACE_MIDDLE_Y_POSITION+HALF_TRACE_HEIGHT-1 // Max Y pos of trace, calculated from above values // Pins to use with the 7735 display #define TFT_CS 10 // Chop select #define TFT_RST 9 // Reset #define TFT_RS 8 // Register select Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_RS, TFT_RST); void onBeatDetected() { // beep the beeper } void setup() { tft.initR(INITR_144GREENTAB); // initialize a ST7735S chip, for 128x128 display tft.fillScreen(ST7735_BLACK); tft.setTextSize(1); tft.setTextColor(ST7735_WHITE); pox.setOnBeatDetectedCallback(onBeatDetected); // Initialize the sensor. Failures are generally due to an improper I2C wiring, missing power supply // or wrong target chip. Occasionally fails on startup (very rare), just press reset on Arduino if (!sensor.begin()) { tft.print("Could not initialise MAX30100"); for(;;); // End program in permanent loop } tft.setCursor(0,0); if (!pox.begin()) { tft.println("Could not initialise MAX30100"); for(;;); // End program in permanent loop } // Set up the parameters for the raw data object sensor.setMode(MAX30100_MODE_SPO2_HR); sensor.setLedsCurrent(IR_LED_CURRENT, RED_LED_CURRENT); sensor.setLedsPulseWidth(PULSE_WIDTH); sensor.setSamplingRate(SAMPLING_RATE); sensor.setHighresModeEnabled(HIGHRES_MODE); // Display BPM and O2 titles, these remain on screen, we only erase the trace and the // BPM/O2 results, otherwise we can get some flicker tft.setTextSize(2); tft.setCursor(0,86); tft.print("BPM O"); tft.setCursor(92,86); tft.print("%"); tft.setTextSize(1); tft.setCursor(84,94); tft.print("2"); // The small subscriper 2 of O2 tft.setCursor(1,0); tft.print("XTronical Health Care"); tft.setTextSize(2); tft.drawRect(0,TRACE_MIN_Y-1,128,TRACE_HEIGHT+2,ST7735_BLUE); // The border box for the trace } void loop() { int16_t Diff=0; // The difference between the Infra Red (IR) and Red LED raw results uint16_t ir, red; // raw results returned in these static float lastx=1; // Last x position of trace static int lasty=TRACE_MIDDLE_Y_POSITION; // Last y position of trace, default to middle static float x=1; // current x position of trace int32_t y; // current y position of trace uint8_t BPM,O2; // BPM and O2 values static uint32_t tsLastReport = 0; // Last time BMP/O2 were checked static int32_t SensorOffset=10000; // Offset to lowest point that raw data does not go below, default 10000 // Note that as sensors may be slightly different the code adjusts this // on the fly if the trace is off screen. The default was determined // By analysis of the raw data returned pox.update(); // Request pulse and o2 data from sensor sensor.update(); // request raw data from sensor if(sensor.getRawValues(&ir, &red)) // If raw data available for IR and Red { if(red<1000) // No pulse y=TRACE_MIDDLE_Y_POSITION; // Set Y to default flat line in middle else { // Plot our new point Diff=(ir-red); // Get raw difference between the 2 LEDS Diff=Diff-SensorOffset; // Adjust the baseline of raw values by removing the offset (moves into a good range for scaling) Diff=Diff/SCALING; // Scale the difference so that it appears at a good height on screen // If the Min or max are off screen then we need to alter the SensorOffset, this should bring it nicely on screen if(Diff<-HALF_TRACE_HEIGHT) SensorOffset+=(SCALING*(abs(Diff)-32)); if(Diff>HALF_TRACE_HEIGHT) SensorOffset+=(SCALING*(abs(Diff)-32)); y=Diff+(TRACE_MIDDLE_Y_POSITION-HALF_TRACE_HEIGHT); // These two lines move Y pos of trace to approx middle of display area y+=TRACE_HEIGHT/4; } if(y>TRACE_MAX_Y) y=TRACE_MAX_Y; // If going beyond trace box area then crop the trace if(y<TRACE_MIN_Y) y=TRACE_MIN_Y; // so it stays within tft.drawLine(lastx,lasty,x,y,ST7735_YELLOW); // Plot the next part of the trace lasty=y; // Save where the last Y pos was lastx=x; // Save where the last X pos was x+=TRACE_SPEED; // Move trace along the display if(x>126) // If reached end of display then reset to statt { tft.fillRect(1,TRACE_MIN_Y,126,TRACE_HEIGHT,ST7735_BLACK); // Blank trace display area x=1; // Back to start lastx=x; } if (millis() - tsLastReport > 1000) // If more than 1 second (1000milliseconds) has past { // since getting heart rate and O2 then get some bew values tft.fillRect(0,104,128,16,ST7735_BLACK); // Clear the old values BPM=round(pox.getHeartRate()); // Get BPM if((BPM<60)|(BPM>110)) // If too low or high for a resting heart rate then display in red tft.setTextColor(ST7735_RED); else tft.setTextColor(ST7735_GREEN); // else display in green tft.setCursor(0,104); // Put BPM at this position tft.print(BPM); // print BPM to screen O2=pox.getSpO2(); // Get the O2 if(O2<94) // If too low then display in red tft.setTextColor(ST7735_RED); else tft.setTextColor(ST7735_GREEN); // else green tft.setCursor(72,104); // Set print position for the O2 value tft.print(O2); // print it to screen tsLastReport = millis(); // Set the last time values got to current time } } } |
Alterations
If your trace is not the size you want (i.e. too small or big) then you can alter it’s height with the line that reads
1 |
#define SCALING 12 |
Making this larger will reduce the height of the trace and making it smaller will increase the height.
That’s it for now, hope this has been of interest. If you’ve really found it useful then please consider supporting on Patreon (www.patreon.com/xtronical) or by making a one off thank you gift using the form on the top right. All contributions go to supporting the projects that I do and this website. Or if you’re not subscriber on You Tube then consider subscribing (link will be under the video on
You Tube).
Hi! I really loved your videos, their are a good way to learn about this projects. I hope you can help me, I’m doing a project with this tecnologies and I want to display the ECG, but I can’t.
I’m using the ESP8266 and the MAX30100, but I only get the data that you already have (BPM and SpO2), so… my question is that if I can get the ECG graph or is imposible with this sensor?
I hope you can help me, I already aprecciate. Thanks
Sorry for late reply but I don’t get notifications for comments on the website (I’ve not idea why despite all settings saying I should). The colour graph is possible but you may need different drivers for the screen and ESP8266.