- Published on
- Fall 2020
Pulse Oximeter
A homemade pulse plethysmograph and pulse oximeter using an Arduino and sensor
Role
Engineering Lead
Skills Used
- Arduino C++
- Circuitry
Collaborators
none
Background
In my Medical Instrumentation class, our final project was to create a pulse oximeter using the very limited supplies we had in our kits because we were partaking in remote learning due to COVID-19. I designed the circuit, the Arduino code, and all of the testing criteria myself.
Block Diagrams
Hardware
Software
Final Design
Arduino Code
#define BUZZER 10
#define BUTTONDURATION 6
#define BUTTONSOUND 7
#include <Wire.h>
#include "MAX30105.h"
#include <PeakDetection.h>
MAX30105 particleSensor;
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET 4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
PeakDetection peakDetection;
double starttime;
void setup()
{
Serial.begin(115200); //Initialize serial monitor/plotter
// Initialize button and buzzer pins
pinMode(BUZZER, OUTPUT);
pinMode(BUTTONDURATION, INPUT);
pinMode(BUTTONSOUND, INPUT);
// Initialize sensor
if (!particleSensor.begin(Wire, 200)) //Use default I2C port, 400kHz speed
{
//Serial.println("MAX30105 was not found. Please check wiring/power. ");
while (1);
}
//Setup to sense a nice looking saw tooth on the plotter
byte ledBrightness = 0x1F; //Options: 0=Off to 255=50mA 1F=31
byte sampleAverage = 8; //Options: 1, 2, 4, 8, 16, 32
byte ledMode = 3; //Options: 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green
int sampleRate = 100; //Options: 50, 100, 200, 400, 800, 1000, 1600, 3200
int pulseWidth = 411; //Options: 69, 118, 215, 411
int adcRange = 4096; //Options: 2048, 4096, 8192, 16384
particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange);
//Set up OLED
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.setTextSize(1);
display.setTextColor(WHITE,BLACK);
display.clearDisplay();
display.setCursor(0,24);
peakDetection.begin(10, 1, 0.6); //Peak Detection presets: lag=32, threshold=2, influence=0.5
}
int count = 0;
double firsttime = 0;
double lasttime = 0;
bool BPMbelow = true;
int bpm1 = 0;
double redarray[100];
double irarray[100];
double peakarray[100];
int maximumred = 0;
int minimumred = 150000;
int maximumir = 0;
int minimumir = 150000;
float acquisitionduration = 10;
float difference = 0;
int k = 0;
double R = 0;
void loop()
{
//Collect infrared and red light data, use peak detection on ir data
double ir = particleSensor.getIR();
double red = particleSensor.getRed();
peakDetection.add(ir);
double peak = peakDetection.getPeak();
double peakprint = 1000 * peak + 120000;
/*
//Plots IR and red data as well as peaks
Serial.print(ir); //Send raw data to plotter
Serial.print(","); //Send raw data to plotter
Serial.print(red); //Send raw data to plotter
Serial.print(",");
Serial.println(peakprint);
*/
////////////////////////////////////////////////////////////////////////////////////////////
//Change acquisition duration to 5 seconds if button pressed
if (digitalRead(BUTTONDURATION) == HIGH){
acquisitionduration = 5;
}
else if (digitalRead(BUTTONDURATION) == LOW){
acquisitionduration = 10;
}
//Measure Heart Rate
if (peak > 0 && BPMbelow) //If detects peak, and there was not a peak before
{
BPMbelow = false; //Peak is occuring, measurement is not below threshold
if (count == 0) //Takes time at first data point
{
firsttime = millis();
}
}
if (peak <= 0 && !BPMbelow)
{
BPMbelow = true; //Peak is not occuring, measurement is below threshold
count++; //Counts at the end of each beat
lasttime = millis(); //Takes time at end of peak
difference = (lasttime - firsttime) / 1000;
if (digitalRead(BUTTONSOUND) == HIGH){
tone(BUZZER,60,100); //If button pressed, piezo buzzer makes sound at every beat
}
//particleSensor.nextSample(); //Moves to next sample
}
////////////////////////////////////////////////////////////////////////////////////////////
redarray[k] = red; //Create array for red light values
irarray[k] = ir; //Create array for IR light values
//Measure modulation ratio R
if (redarray[k] > maximumred){
maximumred = redarray[k]; //Sets maximum of red
}
if (irarray[k] > maximumir){
maximumir = irarray[k]; //Sets maximum of ir
}
if (redarray[k] < minimumred){
minimumred = redarray[k]; //Sets minimum or red
}
if (irarray[k] <details minimumir){
minimumir = irarray[k]; //Sets minimum of ir
}
if (k <div 99){
k++;
}
else if (k == 99){ //Iterates through 100 samples, then resets
k = 0;
maximumred = 0;
minimumred = 150000;
maximumir = 0;
minimumir = 150000;
}
////////////////////////////////////////////////////////////////////////////////////////////
if (difference >= acquisitionduration){ //Once time has reached acquisition duration
bpm1 = count * 60 / difference; //Calculates bpm
count = 0; //Resets count of beats to zero
double redAC = maximumred - minimumred;
double redDC = (minimumred+maximumred)/2;
double redmeasure = redAC/redDC;
double irAC = maximumir - minimumir;
double irDC = (minimumir+maximumir)/2;
double irmeasure = irAC/irDC;
R = fabs(redmeasure/irmeasure); //Calculates modulation ratio
difference = 0; //Resets time to zero
}
////////////////////////////////////////////////////////////////////////////////////////////
//Display values on OLED
display.setCursor(0,24);
display.clearDisplay();
display.print("Pulse: ");
display.print(bpm1); //Display current pulse on OLED in BPM
display.setCursor(0,34);
display.print("R: ");
display.print(R); //Display current R for oxygen saturation
display.display();
}
Testing
Heart Rate
Precision
Accuracy
Oxygen Modulation Ratio (R)
Precision
Accuracy
Limitations
- Sensitive to movement, talking, and irregular breathing
- Peak detection accuracy varies with age and skin type
- Calculates and displays in void loop without interrupts - inaccurate time collection
- Displays every 5 or 10 seconds rather than every heat beat or second
- Heart rate
- Precision specification met at 10 seconds, not 5 seconds
- Almost all accuracy specifications met at 5 seconds, some met at 10 seconds
- Modulation Ratio
- Precision and accuracy specifications met in all cases
- Calibration needed to acquire SpO2
Reflection
This project was quite challenging because we were sent a box of supplies at the beginning of the remote learning semester and any missing or broken components could not be replaced. Learning how to design and construct circuitry was also very difficult through a screen. However, given the circumstances, I am proud of the circuit and accompanying Arduino code that I created. Ever since this project, I have been a teaching assistant for this class (BME 354) for three semesters! Once remote and twice in-person! I really love the skills taught in this class and carry them with me through every project!