From 9cc73fec0f0f7882fbf7b20d87decdc1e3a26664 Mon Sep 17 00:00:00 2001 From: coniferconifer <32008307+coniferconifer@users.noreply.github.com> Date: Mon, 13 Jun 2022 23:01:36 +0900 Subject: [PATCH] ir_forGraph and red_forGraph --- ESP32_MAX30102_simple-SpO2_plotter.ino | 452 +++++++++++++------------ 1 file changed, 228 insertions(+), 224 deletions(-) diff --git a/ESP32_MAX30102_simple-SpO2_plotter.ino b/ESP32_MAX30102_simple-SpO2_plotter.ino index 6d59c8a..a49cd4a 100644 --- a/ESP32_MAX30102_simple-SpO2_plotter.ino +++ b/ESP32_MAX30102_simple-SpO2_plotter.ino @@ -1,224 +1,228 @@ -/* - # simple SpO2 plotter for MH-ET LIVE MAX30102 breakout board and ESP32 devkit-C - Using Sparkfun MAX3010X library - https://github.com/sparkfun/SparkFun_MAX3010x_Sensor_Library - - ESP32_MAX30102_simple-SpO2_plotter.ino - by coniferconifer Copyright 2020 - LICENSED under Apache License 2.0 - - Version 1.0 - - Shows SpO2 and the user's heart beat on Arduino's serial plotter. - No display hardware is required. - This program should not be used for medical purposes. - I wrote this to learn how SpO2 can be measured and pay tributes for the inventors. - - Pulse oximetry was developed in 1972, by Takuo Aoyagi and Michio Kishi, - bioengineers, at Nihon Kohden in Japan. - https://ethw.org/Takuo_Aoyagi - - Since MH-ET LIVE MAX30102 breakout board seems outputting IR and RED swapped. - red = particleSensor.getFIFOIR(); - ir = particleSensor.getFIFORed(); - is used in my code. If you have Sparkfun's MAX30105 breakout board , try to - use #define MAX30105 - - ## Tips: - SpO2 is calculated as R=((square root means or Red/Red average )/((square root means of IR)/IR average)) - SpO2 = -23.3 * (R - 0.4) + 100; - // taken from a graph in https://ww1.microchip.com/downloads/jp/AppNotes/00001525B_JP.pdf - // https://ww1.microchip.com/downloads/en/Appnotes/00001525B.pdf - ## Instructions: - 0) Install Sparkfun's MAX3010X library - 1) Load code onto ESP32 with MH-ET LIVE MAX30102 board - 2) Put MAX30102 board in plastic bag and insulates from your finger. - and attach sensor to your finger tip - 3) Run this program by pressing reset botton on ESP32 - 4) Wait for 3 seconds and Open Arduino IDE Tools->'Serial Plotter' - Make sure the drop down is set to 115200 baud - 5) Search the best position and presure for the sensor by watching - the blips on Arduino's serial plotter - I recommend to place LED under the backside of nail and wrap you - finger and the sensor by rubber band softly. - 6) Checkout the SpO2 and blips by seeing serial Plotter - 100%,95%,90%,85% SpO2 lines are always drawn on the plotter - - ## Hardware Connections (Breakoutboard to ESP32 Arduino): - -VIN = 3.3V - -GND = GND - -SDA = 21 (or SDA) - -SCL = 22 (or SCL) - -INT = Not connected - - this script also works on Arduino nao - ## Hardware Connections (Breakoutboard to Arduino nano): experimental - -VIN = 3.3V - -GND = GND - -SDA = A4 (or SDA) - -SCL = A5 (or SCL) - -INT = Not connected - - ## Trouble Shooting: - Make sure to solder jumper on 3V3 side. - if you forget this, I2C does not work and can not find MAX30102. - says "MAX30102 was not found. Please check wiring/power." - - -*/ - -#include -#include "MAX30105.h" //sparkfun MAX3010X library -MAX30105 particleSensor; - -//#define MAX30105 //if you have Sparkfun's MAX30105 breakout board , try #define MAX30105 - -#define USEFIFO -void setup() -{ - Serial.begin(115200); - Serial.println("Initializing..."); - - // Initialize sensor - if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) //Use default I2C port, 400kHz speed - { - Serial.println("MAX30102 was not found. Please check wiring/power/solder jumper at MH-ET LIVE MAX30102 board. "); - while (1); - } - - //Setup to sense a nice looking saw tooth on the plotter - byte ledBrightness = 0x7F; //Options: 0=Off to 255=50mA - byte sampleAverage = 4; //Options: 1, 2, 4, 8, 16, 32 - byte ledMode = 2; //Options: 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green - //Options: 1 = IR only, 2 = Red + IR on MH-ET LIVE MAX30102 board - int sampleRate = 200; //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 - // Set up the wanted parameters - particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange); //Configure sensor with these settings -} -double avered = 0; double aveir = 0; -double sumirrms = 0; -double sumredrms = 0; -int i = 0; -int Num = 100;//calculate SpO2 by this sampling interval - -double ESpO2 = 95.0;//initial value of estimated SpO2 -double FSpO2 = 0.7; //filter factor for estimated SpO2 -double frate = 0.95; //low pass filter for IR/red LED value to eliminate AC component -#define TIMETOBOOT 3000 // wait for this time(msec) to output SpO2 -#define SCALE 88.0 //adjust to display heart beat and SpO2 in the same scale -#define SAMPLING 5 //if you want to see heart beat more precisely , set SAMPLING to 1 -#define FINGER_ON 30000 // if red signal is lower than this , it indicates your finger is not on the sensor -#define MINIMUM_SPO2 80.0 -void loop() -{ - - uint32_t ir, red , green; - double fred, fir; - double SpO2 = 0; //raw SpO2 before low pass filtered - -#ifdef USEFIFO - particleSensor.check(); //Check the sensor, read up to 3 samples - - while (particleSensor.available()) {//do we have new data -#ifdef MAX30105 - red = particleSensor.getFIFORed(); //Sparkfun's MAX30105 - ir = particleSensor.getFIFOIR(); //Sparkfun's MAX30105 -#else - red = particleSensor.getFIFOIR(); //why getFOFOIR output Red data by MAX30102 on MH-ET LIVE breakout board - ir = particleSensor.getFIFORed(); //why getFIFORed output IR data by MAX30102 on MH-ET LIVE breakout board -#endif - i++; - fred = (double)red; - fir = (double)ir; - avered = avered * frate + (double)red * (1.0 - frate);//average red level by low pass filter - aveir = aveir * frate + (double)ir * (1.0 - frate); //average IR level by low pass filter - sumredrms += (fred - avered) * (fred - avered); //square sum of alternate component of red level - sumirrms += (fir - aveir) * (fir - aveir);//square sum of alternate component of IR level - if ((i % SAMPLING) == 0) {//slow down graph plotting speed for arduino Serial plotter by thin out - if ( millis() > TIMETOBOOT) { - float ir_forGraph = (2.0 * fir - aveir) / aveir * SCALE; - float red_forGraph = (2.0 * fred - avered) / avered * SCALE; - //trancation for Serial plotter's autoscaling - if ( ir_forGraph > 100.0) ir_forGraph = 100.0; - if ( ir_forGraph < 80.0) ir_forGraph = 80.0; - if ( red_forGraph > 100.0 ) red_forGraph = 100.0; - if ( red_forGraph < 80.0 ) red_forGraph = 80.0; - // Serial.print(red); Serial.print(","); Serial.print(ir);Serial.print("."); - if (ir < FINGER_ON) ESpO2 = MINIMUM_SPO2; //indicator for finger detached - Serial.print(ir_forGraph); // to display pulse wave at the same time with SpO2 data - Serial.print(","); Serial.print(red_forGraph); // to display pulse wave at the same time with SpO2 data - Serial.print(","); - Serial.print(ESpO2); //low pass filtered SpO2 - Serial.print(","); Serial.print(85.0); //reference SpO2 line - Serial.print(","); Serial.print(90.0); //warning SpO2 line - Serial.print(","); Serial.print(95.0); //safe SpO2 line - Serial.print(","); Serial.println(100.0); //max SpO2 line - } - } - if ((i % Num) == 0) { - double R = (sqrt(sumredrms) / avered) / (sqrt(sumirrms) / aveir); - // Serial.println(R); - SpO2 = -23.3 * (R - 0.4) + 100; //http://ww1.microchip.com/downloads/jp/AppNotes/00001525B_JP.pdf - ESpO2 = FSpO2 * ESpO2 + (1.0 - FSpO2) * SpO2;//low pass filter - // Serial.print(SpO2);Serial.print(",");Serial.println(ESpO2); - sumredrms = 0.0; sumirrms = 0.0; i = 0; - break; - } - particleSensor.nextSample(); //We're finished with this sample so move to next sample - //Serial.println(SpO2); - } -#else - - while (1) {//do we have new data -#ifdef MAX30105 - red = particleSensor.getRed(); //Sparkfun's MAX30105 - ir = particleSensor.getIR(); //Sparkfun's MAX30105 -#else - red = particleSensor.getIR(); //why getFOFOIR outputs Red data by MAX30102 on MH-ET LIVE breakout board - ir = particleSensor.getRed(); //why getFIFORed outputs IR data by MAX30102 on MH-ET LIVE breakout board -#endif - i++; - fred = (double)red; - fir = (double)ir; - avered = avered * frate + (double)red * (1.0 - frate);//average red level by low pass filter - aveir = aveir * frate + (double)ir * (1.0 - frate); //average IR level by low pass filter - sumredrms += (fred - avered) * (fred - avered); //square sum of alternate component of red level - sumirrms += (fir - aveir) * (fir - aveir);//square sum of alternate component of IR level - if ((i % SAMPLING) == 0) {//slow down graph plotting speed for arduino IDE toos menu by thin out - //#if 0 - if ( millis() > TIMETOBOOT) { - float ir_forGraph = (2.0 * fir - aveir) / aveir * SCALE; - float red_forGraph = (2.0 * fred - avered) / avered * SCALE; - //trancation for Serial plotter's autoscaling - if ( ir_forGraph > 100.0) ir_forGraph = 100.0; - if ( ir_forGraph < 80.0) ir_forGraph = 80.0; - if ( red_forGraph > 100.0 ) red_forGraph = 100.0; - if ( red_forGraph < 80.0 ) red_forGraph = 80.0; - // Serial.print(red); Serial.print(","); Serial.print(ir);Serial.print("."); - if (ir < FINGER_ON) ESpO2 = MINIMUM_SPO2; //indicator for finger detached - Serial.print((2.0 * fir - aveir) / aveir * SCALE); // to display pulse wave at the same time with SpO2 data - Serial.print(","); Serial.print((2.0 * fred - avered) / avered * SCALE); // to display pulse wave at the same time with SpO2 data - Serial.print(","); Serial.print(ESpO2); //low pass filtered SpO2 - Serial.print(","); Serial.print(85.0); // - Serial.print(","); Serial.print(90.0); //warning SpO2 line - Serial.print(","); Serial.print(95.0); //safe SpO2 line - Serial.print(","); Serial.println(100.0); //max SpO2 line - //#endif - } - } - if ((i % Num) == 0) { - double R = (sqrt(sumredrms) / avered) / (sqrt(sumirrms) / aveir); - // Serial.println(R); - SpO2 = -23.3 * (R - 0.4) + 100; //http://ww1.microchip.com/downloads/jp/AppNotes/00001525B_JP.pdf - ESpO2 = FSpO2 * ESpO2 + (1.0 - FSpO2) * SpO2; - // Serial.print(SpO2);Serial.print(",");Serial.println(ESpO2); - sumredrms = 0.0; sumirrms = 0.0; i = 0; - break; - } - particleSensor.nextSample(); //We're finished with this sample so move to next sample - //Serial.println(SpO2); - } -#endif -} +/* + # simple SpO2 plotter for MH-ET LIVE MAX30102 breakout board and ESP32 devkit-C + Using Sparkfun MAX3010X library + https://github.com/sparkfun/SparkFun_MAX3010x_Sensor_Library + + ESP32_MAX30102_simple-SpO2_plotter.ino + by coniferconifer Copyright 2020 + LICENSED under Apache License 2.0 + + Version 1.0 + + Shows SpO2 and the user's heart beat on Arduino's serial plotter. + No display hardware is required. + This program should not be used for medical purposes. + I wrote this to learn how SpO2 can be measured and pay tributes for the inventors. + + Pulse oximetry was developed in 1972, by Takuo Aoyagi and Michio Kishi, + bioengineers, at Nihon Kohden in Japan. + https://ethw.org/Takuo_Aoyagi + + Since MH-ET LIVE MAX30102 breakout board seems outputting IR and RED swapped. + red = particleSensor.getFIFOIR(); + ir = particleSensor.getFIFORed(); + is used in my code. If you have Sparkfun's MAX30105 breakout board , try to + use #define MAX30105 + + ## Tips: + SpO2 is calculated as R=((square root means or Red/Red average )/((square root means of IR)/IR average)) + SpO2 = -23.3 * (R - 0.4) + 100; + // taken from a graph in https://ww1.microchip.com/downloads/jp/AppNotes/00001525B_JP.pdf + // https://ww1.microchip.com/downloads/en/Appnotes/00001525B.pdf + ## Instructions: + 0) Install Sparkfun's MAX3010X library + 1) Load code onto ESP32 with MH-ET LIVE MAX30102 board + 2) Put MAX30102 board in plastic bag and insulates from your finger. + and attach sensor to your finger tip + 3) Run this program by pressing reset botton on ESP32 + 4) Wait for 3 seconds and Open Arduino IDE Tools->'Serial Plotter' + Make sure the drop down is set to 115200 baud + 5) Search the best position and presure for the sensor by watching + the blips on Arduino's serial plotter + I recommend to place LED under the backside of nail and wrap you + finger and the sensor by rubber band softly. + 6) Checkout the SpO2 and blips by seeing serial Plotter + 100%,95%,90%,85% SpO2 lines are always drawn on the plotter + + ## Hardware Connections (Breakoutboard to ESP32 Arduino): + -VIN = 3.3V + -GND = GND + -SDA = 21 (or SDA) + -SCL = 22 (or SCL) + -INT = Not connected + + this script also works on Arduino nao + ## Hardware Connections (Breakoutboard to Arduino nano): experimental + -VIN = 3.3V + -GND = GND + -SDA = A4 (or SDA) + -SCL = A5 (or SCL) + -INT = Not connected + + ## Trouble Shooting: + Make sure to solder jumper on 3V3 side. + if you forget this, I2C does not work and can not find MAX30102. + says "MAX30102 was not found. Please check wiring/power." + + +*/ + +#include +#include "MAX30105.h" //sparkfun MAX3010X library +MAX30105 particleSensor; + +//#define MAX30105 //if you have Sparkfun's MAX30105 breakout board , try #define MAX30105 + +#define USEFIFO +void setup() +{ + Serial.begin(115200); + Serial.println("Initializing..."); + + // Initialize sensor + if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) //Use default I2C port, 400kHz speed + { + Serial.println("MAX30102 was not found. Please check wiring/power/solder jumper at MH-ET LIVE MAX30102 board. "); + while (1); + } + + //Setup to sense a nice looking saw tooth on the plotter + byte ledBrightness = 0x7F; //Options: 0=Off to 255=50mA + byte sampleAverage = 4; //Options: 1, 2, 4, 8, 16, 32 + byte ledMode = 2; //Options: 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green + //Options: 1 = IR only, 2 = Red + IR on MH-ET LIVE MAX30102 board + int sampleRate = 200; //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 + // Set up the wanted parameters + particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange); //Configure sensor with these settings +} +double avered = 0; double aveir = 0; +double sumirrms = 0; +double sumredrms = 0; +int i = 0; +int Num = 100;//calculate SpO2 by this sampling interval + +double ESpO2 = 95.0;//initial value of estimated SpO2 +double FSpO2 = 0.7; //filter factor for estimated SpO2 +double frate = 0.95; //low pass filter for IR/red LED value to eliminate AC component +#define TIMETOBOOT 3000 // wait for this time(msec) to output SpO2 +#define SCALE 88.0 //adjust to display heart beat and SpO2 in the same scale +#define MAX_SPO2 100.0 +#define MIN_SPO2 80.0 +#define SAMPLING 5 //if you want to see heart beat more precisely , set SAMPLING to 1 +#define FINGER_ON 30000 // if red signal is lower than this , it indicates your finger is not on the sensor +#define MINIMUM_SPO2 80.0 +void loop() +{ + + uint32_t ir, red , green; + double fred, fir; + double SpO2 = 0; //raw SpO2 before low pass filtered + +#ifdef USEFIFO + particleSensor.check(); //Check the sensor, read up to 3 samples + + while (particleSensor.available()) {//do we have new data +#ifdef MAX30105 + red = particleSensor.getFIFORed(); //Sparkfun's MAX30105 + ir = particleSensor.getFIFOIR(); //Sparkfun's MAX30105 +#else + red = particleSensor.getFIFOIR(); //why getFOFOIR output Red data by MAX30102 on MH-ET LIVE breakout board + ir = particleSensor.getFIFORed(); //why getFIFORed output IR data by MAX30102 on MH-ET LIVE breakout board +#endif + i++; + fred = (double)red; + fir = (double)ir; + avered = avered * frate + (double)red * (1.0 - frate);//average red level by low pass filter + aveir = aveir * frate + (double)ir * (1.0 - frate); //average IR level by low pass filter + sumredrms += (fred - avered) * (fred - avered); //square sum of alternate component of red level + sumirrms += (fir - aveir) * (fir - aveir);//square sum of alternate component of IR level + if ((i % SAMPLING) == 0) {//slow down graph plotting speed for arduino Serial plotter by thin out + if ( millis() > TIMETOBOOT) { +// float ir_forGraph = (2.0 * fir - aveir) / aveir * SCALE; +// float red_forGraph = (2.0 * fred - avered) / avered * SCALE; + float ir_forGraph = 2.0 * (fir - aveir) / aveir * SCALE + (MIN_SPO2 + MAX_SPO2) / 2.0; + float red_forGraph = 2.0 * (fred - avered) / avered * SCALE + (MIN_SPO2 + MAX_SPO2) / 2.0; + //trancation for Serial plotter's autoscaling + if ( ir_forGraph > 100.0) ir_forGraph = 100.0; + if ( ir_forGraph < 80.0) ir_forGraph = 80.0; + if ( red_forGraph > 100.0 ) red_forGraph = 100.0; + if ( red_forGraph < 80.0 ) red_forGraph = 80.0; + // Serial.print(red); Serial.print(","); Serial.print(ir);Serial.print("."); + if (ir < FINGER_ON) ESpO2 = MINIMUM_SPO2; //indicator for finger detached + Serial.print(ir_forGraph); // to display pulse wave at the same time with SpO2 data + Serial.print(","); Serial.print(red_forGraph); // to display pulse wave at the same time with SpO2 data + Serial.print(","); + Serial.print(ESpO2); //low pass filtered SpO2 + Serial.print(","); Serial.print(85.0); //reference SpO2 line + Serial.print(","); Serial.print(90.0); //warning SpO2 line + Serial.print(","); Serial.print(95.0); //safe SpO2 line + Serial.print(","); Serial.println(100.0); //max SpO2 line + } + } + if ((i % Num) == 0) { + double R = (sqrt(sumredrms) / avered) / (sqrt(sumirrms) / aveir); + // Serial.println(R); + SpO2 = -23.3 * (R - 0.4) + 100; //http://ww1.microchip.com/downloads/jp/AppNotes/00001525B_JP.pdf + ESpO2 = FSpO2 * ESpO2 + (1.0 - FSpO2) * SpO2;//low pass filter + // Serial.print(SpO2);Serial.print(",");Serial.println(ESpO2); + sumredrms = 0.0; sumirrms = 0.0; i = 0; + break; + } + particleSensor.nextSample(); //We're finished with this sample so move to next sample + //Serial.println(SpO2); + } +#else + + while (1) {//do we have new data +#ifdef MAX30105 + red = particleSensor.getRed(); //Sparkfun's MAX30105 + ir = particleSensor.getIR(); //Sparkfun's MAX30105 +#else + red = particleSensor.getIR(); //why getFOFOIR outputs Red data by MAX30102 on MH-ET LIVE breakout board + ir = particleSensor.getRed(); //why getFIFORed outputs IR data by MAX30102 on MH-ET LIVE breakout board +#endif + i++; + fred = (double)red; + fir = (double)ir; + avered = avered * frate + (double)red * (1.0 - frate);//average red level by low pass filter + aveir = aveir * frate + (double)ir * (1.0 - frate); //average IR level by low pass filter + sumredrms += (fred - avered) * (fred - avered); //square sum of alternate component of red level + sumirrms += (fir - aveir) * (fir - aveir);//square sum of alternate component of IR level + if ((i % SAMPLING) == 0) {//slow down graph plotting speed for arduino IDE toos menu by thin out + //#if 0 + if ( millis() > TIMETOBOOT) { + float ir_forGraph = (2.0 * fir - aveir) / aveir * SCALE; + float red_forGraph = (2.0 * fred - avered) / avered * SCALE; + //trancation for Serial plotter's autoscaling + if ( ir_forGraph > 100.0) ir_forGraph = 100.0; + if ( ir_forGraph < 80.0) ir_forGraph = 80.0; + if ( red_forGraph > 100.0 ) red_forGraph = 100.0; + if ( red_forGraph < 80.0 ) red_forGraph = 80.0; + // Serial.print(red); Serial.print(","); Serial.print(ir);Serial.print("."); + if (ir < FINGER_ON) ESpO2 = MINIMUM_SPO2; //indicator for finger detached + Serial.print((2.0 * fir - aveir) / aveir * SCALE); // to display pulse wave at the same time with SpO2 data + Serial.print(","); Serial.print((2.0 * fred - avered) / avered * SCALE); // to display pulse wave at the same time with SpO2 data + Serial.print(","); Serial.print(ESpO2); //low pass filtered SpO2 + Serial.print(","); Serial.print(85.0); // + Serial.print(","); Serial.print(90.0); //warning SpO2 line + Serial.print(","); Serial.print(95.0); //safe SpO2 line + Serial.print(","); Serial.println(100.0); //max SpO2 line + //#endif + } + } + if ((i % Num) == 0) { + double R = (sqrt(sumredrms) / avered) / (sqrt(sumirrms) / aveir); + // Serial.println(R); + SpO2 = -23.3 * (R - 0.4) + 100; //http://ww1.microchip.com/downloads/jp/AppNotes/00001525B_JP.pdf + ESpO2 = FSpO2 * ESpO2 + (1.0 - FSpO2) * SpO2; + // Serial.print(SpO2);Serial.print(",");Serial.println(ESpO2); + sumredrms = 0.0; sumirrms = 0.0; i = 0; + break; + } + particleSensor.nextSample(); //We're finished with this sample so move to next sample + //Serial.println(SpO2); + } +#endif +}