DIY Pulse Oximeter

DIY Pulse Oximeter

A pulse oximeter is a vital medical device that measures oxygen saturation percentage using red and infrared light. This device operates on the fundamental principle that oxygenated blood absorbs more infrared rays compared to deoxygenated blood.

A pulse oximeter is a vital medical device that measures oxygen saturation percentage using red and infrared light. This device operates on the fundamental principle that oxygenated blood absorbs more infrared rays compared to deoxygenated blood. By precisely quantifying the light absorbed, a pulse oximeter calculates the oxygen saturation level, often referred to as the oxygen saturation index or SpO2. Explore this informative guide to gain a deeper understanding of how pulse oximeters work and their critical role in healthcare monitoring.

Working Process

Pulse oximeters, essential healthcare devices, employ two light-emitting diodes (LEDs) to shine light through the skin - one red and one infrared. These LEDs are thoughtfully placed on a probe, which can be comfortably positioned on the finger, earlobe, or toe of the individual. As this light traverses through the skin, a portion of it is absorbed by the blood. The degree of light absorption hinges on the blood's oxygen saturation. Additionally, the device incorporates a photodetector, situated on the same probe as the LEDs, to gauge the quantity of light that successfully penetrates the skin.

By leveraging the data from the photodetector, the device meticulously calculates the oxygen saturation of the blood. This calculation involves a comparison between the light absorbed by the blood and the light transmitted through the skin. The resulting oxygen saturation level is then presented as a percentage on the device's screen. Typically, a healthy individual exhibits an average oxygen saturation level ranging from 95% to 100%. Should the readings fall below this range, it may signal insufficient oxygen intake, possibly indicating underlying medical conditions like COPD or asthma.

Project

Usually, pulse oximeters are made using a MAX 30100 sensor which will cost more than the components used in my project. Therefore I have reduced the production cost by using some simple electronic components and Arduino.

Components

Resistors (10K, 4.7K*2), Photodiode, IR LED, Red LED, LM 7805, BC547 transistors, OLED display, Arduino pro mini, 9V Battery

Circuit diagrams

Schematic diagram

Diagram of the components

Code

  1. Include Libraries

    1#include <Wire.h>
    2#include <LiquidCrystal_I2C.h>

    This code includes two libraries: Wire for I2C communication and LiquidCrystal_I2C for controlling a 2x16 character LCD display.

  2. Define Constants

    1#define maxperiod_siz 80 // number of samples in a period
    2#define measures 10 // periods stored
    3#define samp_siz 4 // samples for average
    4#define rise_threshold 3 // rising measures to determine a peak

    These are constant values used for configuring the program. They determine the number of samples, periods, and thresholds used for calculations.

  3. Initialize LCD Display

    1LiquidCrystal_I2C lcd(0x3F, 16, 2);

    This initializes the LCD display with the I2C address 0x3F and specifies the display size as 16x2.

  4. Define Pin Configuration

    1int T = 20;
    2int sensorPin = A1;
    3int REDLed = 3;
    4int IRLed = 4;

    These variables define various pins used in the circuit. sensorPin is connected to a sensor, and REDLed and IRLed control LEDs emitting red and infrared light, respectively.

  5. Define Custom LCD Characters

    1byte sym[3][8] = { ... };

    This defines three custom characters for the LCD display. These characters are later used to display special symbols on the LCD.

  6. Setup Function

    1void setup() { ... }

    This function runs once when the Arduino is powered up or reset. It initializes serial communication, sets pin modes, initializes the LCD, and creates custom characters for the display.

  7. Main Loop

    1void loop() { ... }

    This is the main loop of the program that runs continuously. It contains the core logic for reading sensor data, calculating heart rate and SpO2 levels, and displaying the results on the LCD.

Now, let's break down the main loop into several key sections:

  • Sensor Data Acquisition- The loop begins by reading sensor data from both the infrared and red sensors. It calculates an average value for each sensor over a short period of time.

  • Finger Status Check- It checks if a finger is placed on the sensor. If there's no finger detected, it displays "No finger?" on the LCD.

  • Peak Detection- It looks for peaks in the sensor data to detect heartbeats. When a rising peak is detected, it calculates heart rate (BPM) and SpO2 levels based on the ratio of red and infrared sensor values. It also averages these values over a short time frame.

  • Displaying Results- The program then displays heart rate, SpO2, and the quality of the signal (R) on the LCD. If a valid signal is detected (based on c), it calculates and displays the BPM and SpO2 levels. Otherwise, it displays placeholders.

  • Plotting and Array Handling- Sensor data and calculated values are sent to the serial monitor for debugging purposes. Arrays are also managed to store historical data.

  • Loop Continues- The loop continues to run indefinitely, repeating the above steps.

Details of the Components

  • Resistors — 10K and 4.7K

There are two types of resistors used in the pulse oximeter. One is a 10kilo ohm resistor, and the other is a 4.7kilo ohm resistor. Either way, a resistor is usually employed in dropping a voltage or producing a potential divider action. It might also be used to limit current flow.

  • Photodiode

A photodiode is a p — n junction that consumes light energy to generate an electric current. When we consider the working principle of a photodiode, a photodiode is subjected to photons in the form of light, which affects the generation of electron-hole pairs. As a result of the increase in the number of electrons on the n—side and holes on the p-side, a rise in the electromotive forces are observed.

  • IR LED

An infrared light-emitting diode (LED) is used for non-visual applications and nighttime illumination. The application of an infrared LED depends on its wavelength, with the common usage being between 808nm and 940nm.

  • Red LED

The pulse rate is measured by using the wavelength of the IR LED and the red LED light. The body scatters and absorbs visible and near-infrared wavelengths to have a measurable signal. The average number of oxygen atoms attached to a hemoglobin molecule will be observed according to the wavelength.

  • BC 547 transistor

BC547 is a general-purpose BJT NPN transistor with an output current of 100mA. It is ideal to use in amplifications and as a switch due to its DC gain and saturation voltage of 90mV. Especially this transistor is used in applications like sensor circuits, amplifier circuits, and many other electronic projects.

  • LM 7805

The LM7805 is a voltage regulator that outputs +5 volts. This voltage regulator can deliver up to 1.5A of current with current limiting and thermal shutdown features.

  • Battery

The purpose of the battery in this circuit is to supply voltage for the operation of the circuit. Here we use a 9V battery to give a relatively wide operating voltage range during its life without being overly high voltage.

  • OLED display

It provides fast oxygen saturation readings and pulse measurements and displays it conveniently on a large digital bright display. The advanced dual OLED display technology helps the user get a brighter & sharper vision of the information from a pulse oximeter.

  • Arduino pro mini

Arduino Pro Mini is a small-sized microcontroller board, and it comes with an Atmega328 microcontroller incorporated into the board. This board comes with 14 Digital I/O Pins and 8 Analog Pins. Because of the small size, there are many applications of Arduino Pro Mini.

For the reference, the following is the source code used.

1#include <Wire.h>
2#include <LiquidCrystal_I2C.h>
3
4#define maxperiod_siz 80 // number of samples in a period
5#define measures 10 // periods stored
6#define samp_siz 4 // samples for average
7#define rise_threshold 3 // rising measures to determine a peak
8
9// a liquid crystal displays BPM
10LiquidCrystal_I2C lcd(0x3F, 16, 2);
11
12int T = 20;
13int sensorPin = A1;
14int REDLed = 3;
15int IRLed = 4;
16
17byte sym[3][8] = {
18 {
19 B00000,
20 B01010,
21 B11111,
22 B11111,
23 B01110,
24 B00100,
25 B00000,
26 B00000
27},{
28 B00000,
29 B00000,
30 B00000,
31 B11000,
32 B00100,
33 B01000,
34 B10000,
35 B11100
36},{
37 B00000,
38 B00000,
39 B00000,
40 B01000,
41 B10101,
42 B00010,
43 B00000,
44 B00000
45}
46};
47
48void setup() {
49 Serial.begin(9600);
50 Serial.flush();
51 pinMode(sensorPin,INPUT);
52 pinMode(REDLed,OUTPUT);
53 pinMode(IRLed,OUTPUT);
54
55 lcd.init();
56 lcd.backlight();
57
58 digitalWrite(REDLed,LOW);
59 digitalWrite(IRLed,LOW);
60
61 for(int i=0;i<8;i++) lcd.createChar(i, sym[i]);
62}
63
64void loop ()
65{
66
67 bool finger_status = true;
68 float readsIR[samp_siz], sumIR,lastIR, reader, start;
69 float readsRED[samp_siz], sumRED,lastRED;
70 int period, samples;
71 period=0; samples=0;
72 int samplesCounter = 0;
73 float readsIRMM[maxperiod_siz],readsREDMM[maxperiod_siz];
74 int ptrMM =0;
75 for (int i = 0; i < maxperiod_siz; i++) { readsIRMM[i] = 0;readsREDMM[i]=0;}
76 float IRmax=0;
77 float IRmin=0;
78 float REDmax=0;
79 float REDmin=0;
80 double R=0;
81 float measuresR[measures];
82 int measuresPeriods[measures];
83 int m = 0;
84
85 for (int i = 0; i < measures; i++) { measuresPeriods[i]=0; measuresR[i]=0; }
86
87 int ptr;
88 float beforeIR;
89 bool rising;
90 int rise_count;
91 int n;
92 long int last_beat;
93 for (int i = 0; i < samp_siz; i++) { readsIR[i] = 0; readsRED[i]=0; }
94 sumIR = 0; sumRED=0;
95 ptr = 0;
96
97 int cm=0;
98
99 Serial.flush();
100
101 while(1)
102 {
103 // turn on IR
104 digitalWrite(REDLed,LOW);
105 digitalWrite(IRLed,HIGH);
106
107 // calculate an average of the sensor
108 n = 0;
109 start = millis();
110 reader = 0.;
111 do
112 {
113 reader += analogRead (sensorPin);
114 n++;
115 }
116 while (millis() < start + T);
117 reader /= n;
118 sumIR -= readsIR[ptr];
119 sumIR += reader;
120 readsIR[ptr] = reader;
121 lastIR = sumIR / samp_siz;
122
123 digitalWrite(REDLed,HIGH);
124 digitalWrite(IRLed,LOW);
125
126 n = 0;
127 start = millis();
128 reader = 0.;
129 do
130 {
131 reader += analogRead (sensorPin);
132 n++;
133 }
134 while (millis() < start + T);
135 reader /= n;
136 sumRED -= readsRED[ptr];
137 sumRED += reader;
138 readsRED[ptr] = reader;
139 lastRED = sumRED / samp_siz;
140 readsIRMM[ptrMM]=lastIR;
141 readsREDMM[ptrMM]=lastRED;
142 ptrMM++;
143 ptrMM %= maxperiod_siz;
144 samplesCounter++;
145
146 if(samplesCounter>=samples){
147 samplesCounter =0;
148 IRmax = 0; IRmin=1023; REDmax = 0; REDmin=1023;
149 for(int i=0;i<maxperiod_siz;i++) {
150 if( readsIRMM[i]> IRmax) IRmax = readsIRMM[i];
151 if( readsIRMM[i]>0 && readsIRMM[i]< IRmin ) IRmin = readsIRMM[i];
152 readsIRMM[i] =0;
153 if( readsREDMM[i]> REDmax) REDmax = readsREDMM[i];
154 if( readsREDMM[i]>0 && readsREDMM[i]< REDmin ) REDmin = readsREDMM[i];
155 readsREDMM[i] =0;
156 }
157 R = ( (REDmax-REDmin) / REDmin) / ( (IRmax-IRmin) / IRmin ) ;
158 }
159
160 // check that the finger is placed inside the sensor
161 if (lastRED < lastIR) {
162 if(finger_status==true) {
163 finger_status = false;
164 lcd.clear();
165 lcd.setCursor(0,0);
166 lcd.print("No finger?");
167 }
168 } else {
169 if(finger_status==false) {
170 lcd.clear();
171 finger_status = true;
172 }
173 }
174
175 float avR = 0;
176 int avBPM=0;
177
178 if (finger_status==true){
179
180 if (lastIR > beforeIR)
181 {
182 rise_count++;
183 if (!rising && rise_count > rise_threshold)
184 {
185 lcd.setCursor(15,1);
186 lcd.write( 0 );
187
188 rising = true;
189
190 measuresR[m] = R;
191 measuresPeriods[m] = millis() - last_beat;
192 last_beat = millis();
193 int period = 0;
194 for(int i =0; i<measures; i++) period += measuresPeriods[i];
195
196 period = period / measures;
197 samples = period / (2*T);
198
199 int c = 0;
200 int avPeriod = 0;
201
202 for(int i =1; i<measures; i++) {
203 if ( (measuresPeriods[i] < measuresPeriods[i-1] * 1.1) &&
204 (measuresPeriods[i] > measuresPeriods[i-1] / 1.1) ) {
205
206 c++;
207 avPeriod += measuresPeriods[i];
208 avR += measuresR[i];
209
210 }
211 }
212
213 m++;
214 m %= measures;
215
216 lcd.setCursor(0,1);
217 lcd.print("c " + String(c)+" ");
218
219 avBPM = 60000 / ( avPeriod / c) ;
220 avR = avR / c ;
221
222 lcd.setCursor(0,0);
223 lcd.print("bpm ");
224 lcd.setCursor(8,0);
225 lcd.print("SpO2 ");
226 lcd.setCursor(11,0); lcd.write(1);
227 lcd.setCursor(7,1);
228 lcd.print("R ");
229
230 if(c > 4) {
231
232 int SpO2 = -64 * R + 158;
233
234 lcd.setCursor(4,0);
235 lcd.print(String(avBPM));
236 lcd.setCursor(12,0);
237 lcd.print( String(SpO2) +"% ");
238 lcd.setCursor(9,1);
239 lcd.print(String(avR) + " ");
240
241 } else {
242 lcd.setCursor(4,0);
243 lcd.print("---");
244 lcd.setCursor(12,0);
245 lcd.print("--% ");
246 lcd.setCursor(9,1);
247 lcd.print("----");
248 }
249 }
250 }
251 else
252 {
253 rising = false;
254 rise_count = 0;
255 lcd.setCursor(15,1); lcd.print(" ");
256 }
257 beforeIR = lastIR;
258 }
259
260 // Plotting
261 Serial.print(lastIR);
262 Serial.print(",");
263 Serial.print(lastRED);
264 Serial.print(",");
265 Serial.print(R);
266 Serial.print(",");
267 Serial.print(IRmax);
268 Serial.print(",");
269 Serial.print(IRmin);
270 Serial.print(",");
271 Serial.print(REDmax);
272 Serial.print(",");
273 Serial.print(REDmin);
274 Serial.print(",");
275 Serial.print(avR);
276 Serial.print(",");
277 Serial.print(avBPM);
278 Serial.println();
279
280 // Array handling
281 ptr++;
282 ptr %= samp_siz;
283
284 }
285}