Oxímetro de pulso DIY open-source para COVID-19 con Arduino Nano y MAX30102
Componentes y materiales
| Componente | Cantidad |
|---|---|
| Arduino Nano R3 | 1 |
| Sensor MAX30102 (frecuencia cardíaca y SpO2) | 1 |
| Pantalla OLED I2C 128x64 (DFRobot Gravity) | 1 |
| Filamento PLA, tornillos M3 | Varios |
Herramientas
- Impresora 3D (carcasa).
- Soldador.
Software
- Arduino IDE.
- Autodesk Fusion 360 (diseño carcasa).
- Librerías:
Wire,MAX30105,spo2_algorithm,SSD1306Ascii,SSD1306AsciiWire.
Sobre el proyecto
Dispositivo portátil que mide frecuencia cardíaca (HR) y saturación de oxígeno (SpO2) para monitorización casera durante la pandemia COVID-19. No es un dispositivo médico certificado; sirve como referencia orientativa.
Electrónica
- MAX30102: Sensor óptico (LED rojo + IR) con ADC integrado, comunicación I2C (dirección 0x57). Alimentación 3.3V o 5V.
- OLED 128x64: Interfaz I2C (0x3C), librería SSD1306Ascii para texto simple.
- Arduino Nano: Lee sensor cada 100ms, calcula HR y SpO2 con algoritmo de Maxim (filtrado FIR, detección de picos, ratio AC/DC).
Montaje paso a paso
- Imprimir carcasa: 2 piezas (base y tapa) en PLA, infill 70-80%, soportes medios. Lijar ligeramente.
- Soldar electrónica: Arduino Nano en perfboard 44x30mm. Cables VIN, GND, SDA (A4), SCL (A5) al MAX30102. Conector OLED a mismos bus I2C. Fijar con tornillos M3.
- Ensamblar: Insertar PCB en base, atornillar OLED en tapa, unir ambas piezas.
- Subir sketch: Seleccionar "Arduino Nano" en Tools → Board. Instalar librerías necesarias.
- Usar: Colocar dedo sobre sensor, cerrar tapa, alimentar (USB o batería). Esperar lecturas estables (HR y SpO2).
Código principal (sketch)
/* Conexiones: VCC=5V, GND=GND, SDA=A4, SCL=A5, INT=NC */
#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__)
uint16_t irBuffer[50], redBuffer[50]; // 16-bit para ahorrar RAM
#else
uint32_t irBuffer[50], redBuffer[50];
#endif
int32_t spo2; int8_t validSPO2;
int32_t heartRate; int8_t validHeartRate;
void setup() {
Serial.begin(115200);
oled.begin(&Adafruit128x64, 0x3C);
oled.setFont(Arial14);
if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) {
Serial.println(F("MAX30105 no encontrado. Revisa cableado/alimentación."));
while(1);
}
particleSensor.setup(55, 4, 2, 200, 411, 4096); // Config: ledBrightness=55, sampleAvg=4, ledMode=2, sampleRate=200, pulseWidth=411, adcRange=4096
}
void loop() {
// Primeras 50 muestras para calibrar rango
for (byte i=0; i<50; i++) {
while (!particleSensor.available()) particleSensor.check();
redBuffer[i] = particleSensor.getRed();
irBuffer[i] = particleSensor.getIR();
particleSensor.nextSample();
Serial.print("red="); Serial.print(redBuffer[i]);
Serial.print(", ir="); Serial.println(irBuffer[i]);
}
maxim_heart_rate_and_oxygen_saturation(irBuffer, 50, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate);
// Bucle continuo: ventana deslizante de 50 muestras (25 nuevas cada ciclo)
while (1) {
// Desplazar 25 muestras antiguas
for (byte i=25; i<50; i++) {
redBuffer[i-25] = redBuffer[i];
irBuffer[i-25] = irBuffer[i];
}
// Adquirir 25 nuevas
for (byte i=25; i<50; i++) {
while (!particleSensor.available()) particleSensor.check();
redBuffer[i] = particleSensor.getRed();
irBuffer[i] = particleSensor.getIR();
particleSensor.nextSample();
// Debug serie
Serial.print("red="); Serial.print(redBuffer[i]);
Serial.print(", ir="); Serial.print(irBuffer[i]);
Serial.print(", HR="); Serial.print(heartRate);
Serial.print(", HRvalid="); Serial.print(validHeartRate);
Serial.print(", SPO2="); Serial.print(spo2);
Serial.print(", SPO2Valid="); Serial.println(validSPO2);
}
maxim_heart_rate_and_oxygen_saturation(irBuffer, 50, redBuffer, &spo2, &validSPO2, &heartRate, &validHeartRate);
printToScreen();
}
}
void printToScreen() {
oled.clear(); oled.setCursor(0,0);
if (validSPO2 && validHeartRate) {
oled.print(F("HR: ")); oled.println(heartRate);
oled.print(F("SPO2: ")); oled.println(spo2);
} else {
oled.print(F("No válido"));
}
}
Piezas 3D (carcasa)
Archivos STL disponibles en la sección "Custom parts and enclosures" del proyecto original. Incluyen base, tapa y clip para dedo.
Esquemático
Diagrama de conexiones I2C compartido: Arduino A4/SDA → ambos dispositivos SDA; A5/SCL → ambos SCL; 5V y GND comunes. El MAX30102 tolera 5V en I2C.
Nota importante
Este proyecto es educativo y de referencia. La precisión depende de contacto dedo-sensor, luz ambiente, movimiento, calibración del algoritmo. No sustituye a un oxímetro médico homologado.
Proceso de manufactura
- Registro del Proyecto FirePick Delta: Construyendo una MicroFactory Open‑Source con OpenPnP
- Proyecto Dados Digitales Arduino: Construye tu Propio Generador de Números Aleatorios con LCD
- Construye un Iron Man animado con Arduino: componentes, sensores y guía paso a paso
- Find Me: Localizador Inteligente de Objetos con Arduino y Bluetooth
- Construye un Brazo Robótico Impreso en 3D Controlado por Arduino – Guía Paso a Paso
- Oxímetro de Pulso Portátil para Emergencias COVID-19 - Arduino Nano + MAX30100
- Controlador de Humidificador DIY con Arduino y Relé: Configuración Segura de Alto Voltaje
- Pong Neón en Matriz NeoPixel con Arduino Nano
- Construye un volante joystick personalizado con Arduino para gaming
- Health Band: Asistente Inteligente Vestible para Personas Mayores
- Por Qué el Open Source Impulsa la Innovación en el Edge: eBook Esencial