-
Notifications
You must be signed in to change notification settings - Fork 20
Expand file tree
/
Copy pathESP32_MAX30102_simple-SpO2_plotter.ino
More file actions
228 lines (207 loc) · 10.4 KB
/
ESP32_MAX30102_simple-SpO2_plotter.ino
File metadata and controls
228 lines (207 loc) · 10.4 KB
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
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 <Wire.h>
#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
}