Project tutorial
Open Source Pulse Oximeter for COVID-19

Open Source Pulse Oximeter for COVID-19 © GPL3+

An easily constructed USB-powered pulse oximeter device that can be built for around $25 and features an OLED display.

  • 167 respects

Components and supplies

Necessary tools and machines

3D Printer (generic)
09507 01
Soldering iron (generic)

Apps and online services

About this project

The Challenges of COVID-19

COVID-19 is a disease caused by the SARS-CoV-2 virus that primarily attacks a person's respiratory system. Some milder symptoms can include fever, aches, and chills, but it can also lead to more serious conditions such as pneumonia. A person who has pneumonia or even slight shortness of breath might not know when to go to a hospital, especially as they start to get even more overwhelmed. This is why I created this open source pulse oximeter, which can assist in getting people the help they need and get accurate information about their current condition.

A Bit of a Disclaimer

This device/project is not to be used as an accurate medical diagnostic tool!

The Electronics


128x64 Pixel OLED

Arduino Nano

Building the Device Step-By-Step

Here is a step-by-step walkthrough of how to build this project.

1. Print Out and Clean Parts

Begin by downloading each piece from the attachment section on this project and loading it into your slicer of choice. I used an infill of around 70-80% and medium supports, all with PLA. After they were done printing, I removed the supports and did some light sanding to ensure they all fit together nicely.

2. Solder the Electronics

The entire device is designed around an Arduino Nano that is mounted on a piece of 44mm by 30mm perfboard. First, wires get soldered to the VIN, GND, SDA, and SCL pins of the sensor and then run underneath the bed piece to the Arduino Nano.

Next, the connector for the OLED is attached to the Nano and then run up to the display itself.

And finally, the entire electronics assembly is slid into the housing and secured with a couple of 3mm screws.

3. Assemble the Device

After the electronics have been inserted, simply attach the OLED screen to the top piece and secure it to the rest of the chassis with a couple of 3mm screws. You can test its motion by gently articulating the lid up and down.

4. Uploading the Sketch

The included sketch performs a couple of actions to display the user's current heartrate and oxygen saturation. To upload it, simply install the required libraries and select Arduino Nano from the board list in the Tools menu and click Upload.

As for the sketch itself, it first initializes the OLED and MAX30102, whilst reporting any errors that might arise. Next, it reads in 100 values to calibrate the sensor and begins displaying them. The device then enters a loop where it reads in 25 new values and computes a moving average with them. Finally, it checks if the values are valid and prints them to the screen if they are.

5. Using It

To use the pulse oximeter, place your fingertip over the sensor and gently close the top lid. Then plug in a power source and simply wait until you see data being displayed.


Pulse Oximeter CodeC/C++
  Hardware Connections (Breakoutboard to Arduino):
  -5V = 5V (3.3V is allowed)
  -GND = GND
  -SDA = A4 (or SDA)
  -SCL = A5 (or SCL)
  -INT = Not connected
  The MAX30105 Breakout can handle 5V or 3.3V I2C logic. We recommend powering the board with 5V
  but it will also run at 3.3V.

#include <Wire.h>
#include "MAX30105.h"
#include "spo2_algorithm.h"
#include "SSD1306Ascii.h"
#include "SSD1306AsciiWire.h"

MAX30105 particleSensor;
SSD1306AsciiWire oled;

#define MAX_BRIGHTNESS 255

#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)
//Arduino Uno doesn't have enough SRAM to store 50 samples of IR led data and red led data in 32-bit format
//To solve this problem, 16-bit MSB of the sampled data will be truncated. Samples become 16-bit data.
uint16_t irBuffer[50]; //infrared LED sensor data
uint16_t redBuffer[50];  //red LED sensor data
uint32_t irBuffer[50]; //infrared LED sensor data
uint32_t redBuffer[50];  //red LED sensor data

int32_t spo2; //SPO2 value
int8_t validSPO2; //indicator to show if the SPO2 calculation is valid
int32_t heartRate; //heart rate value
int8_t validHeartRate; //indicator to show if the heart rate calculation is valid

void setup()
  Serial.begin(115200); // initialize serial communication at 115200 bits per second:

  oled.begin(&Adafruit128x64, 0x3C);

  // Initialize sensor
  if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) //Use default I2C port, 400kHz speed
    Serial.println(F("MAX30105 was not found. Please check wiring/power."));
    while (1);

  particleSensor.setup(55, 4, 2, 200, 411, 4096); //Configure sensor with these settings

void loop()

  //read the first 50 samples, and determine the signal range
  for (byte i = 0 ; i < 50 ; i++)
    while (particleSensor.available() == false) //do we have new data?
      particleSensor.check(); //Check the sensor for new data

    redBuffer[i] = particleSensor.getRed();
    irBuffer[i] = particleSensor.getIR();
    particleSensor.nextSample(); //We're finished with this sample so move to next sample
    Serial.print(redBuffer[i], DEC);
    Serial.print(F(", ir="));
    Serial.println(irBuffer[i], DEC);

  //calculate heart rate and SpO2 after first 50 samples (first 4 seconds of samples)
  maxim_heart_rate_and_oxygen_saturation(irBuffer, 50, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate);

  //Continuously taking samples from MAX30102.  Heart rate and SpO2 are calculated every 1 second
  while (1)
    //dumping the first 25 sets of samples in the memory and shift the last 25 sets of samples to the top
    for (byte i = 25; i < 50; i++)
      redBuffer[i - 25] = redBuffer[i];
      irBuffer[i - 25] = irBuffer[i];

    //take 25 sets of samples before calculating the heart rate.
    for (byte i = 25; i < 50; i++)
      while (particleSensor.available() == false) //do we have new data?
        particleSensor.check(); //Check the sensor for new data

      redBuffer[i] = particleSensor.getRed();
      irBuffer[i] = particleSensor.getIR();
      particleSensor.nextSample(); //We're finished with this sample so move to next sample
      Serial.print(redBuffer[i], DEC);
      Serial.print(F(", ir="));
      Serial.print(irBuffer[i], DEC);

      Serial.print(F(", HR="));
      Serial.print(heartRate, DEC);

      Serial.print(F(", HRvalid="));
      Serial.print(validHeartRate, DEC);

      Serial.print(F(", SPO2="));
      Serial.print(spo2, DEC);

      Serial.print(F(", SPO2Valid="));
      Serial.println(validSPO2, DEC);

    //After gathering 25 new samples recalculate HR and SP02
    maxim_heart_rate_and_oxygen_saturation(irBuffer, 50, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate);

void printToScreen() {
  if(validSPO2 && validHeartRate) {
    oled.print(F("HR: ")); oled.println(heartRate, DEC);
    oled.print(F("SPO2: ")); oled.println(spo2, DEC);
  } else {
    oled.print(F("Not valid"));

Custom parts and enclosures

Bottom Shell
Front + Bed
Top Piece


Pulseoximeter schematic cvjgl1ssbh


Similar projects you might like

ATtiny85 Pulse Oximeter and Photoplethysmograph

Project in progress by Jeff Magee

  • 114 respects


Project tutorial by cerebrum_2020

  • 23 respects

Measure Heart Rate and SpO2 with MAX30102

Project tutorial by SurtrTech

  • 256 respects

Low cost vitals monitoring wearable for frontline workers

Project tutorial by Vishwas Navada

  • 35 respects

Really Homemade Oximeter Sensor

Project tutorial by Giulio Pons

  • 44 respects

Covid-19 Coronavirus Doorbell

Project showcase by Roni Bandini

  • 47 respects
Add projectSign up / Login