Construcción DIY de un Oxímetro Preciso: Guía de Sensor IR, Arduino, LCD y Componentes
Componentes y Suministros
| Componente | Descripción | Cantidad |
|---|---|---|
| Emisor Infrarrojo (18°) | Longitud de onda entre 805 nm y 1000 nm | 1 |
| Fotodiodo LPT80A | Debe ser de amplio rango, capaz de recibir señales rojas (600 nm) e infrarrojas (940 nm) | 1 |
| LED Rojo 5 mm | Longitud de onda cercana a 600 nm | 1 |
| Resistencia 10 kΩ | 1 | |
| Resistencia 330 Ω | 2 | |
| Módulo LCD I2C 16x2 DFRobot para Arduino | O cualquier display LCD para mostrar valores | 1 |
| Arduino UNO | 1 | |
| Cables puente (genéricos) | 1 | |
| LED Blanco de Alto Brillo | Opcional: tercer LED, ver sección de mejoras | 1 |
Herramientas Necesarias
- Punta de soldador, tipo Gull Wing
Aplicaciones y Servicios Online
- Arduino IDE
Sobre este Proyecto
Durante este período de aislamiento, construí un oxímetro con componentes que ya tenía en casa. Al fin y al cabo, un oxímetro solo consta de dos LEDs y un fotodiodo.
No soy experto en conocimientos médicos y en esta etapa del proyecto no estoy seguro de que este trabajo tenga valor diagnóstico, pero es un buen proyecto educativo para estudiar su funcionamiento y, probablemente con algunos ajustes, podría convertirse en una herramienta médica casera.
Saturación de Oxígeno y COVID-19
En este increíble período de nuestras vidas hemos aprendido mucho sobre virus, pulmones, mascarillas quirúrgicas, jabón y lavado de manos. Todos leen sobre síntomas como tos, fiebre y dificultad para respirar. También sabemos que una forma de medir la dificultad respiratoria es leyendo la cantidad de oxígeno en nuestra sangre.
Esta medida se puede leer indirectamente con un dispositivo médico llamado oxímetro. Probablemente ya lo hayas visto: es un dispositivo no invasivo que se coloca en un dedo con luces pulsantes que hacen el trabajo.
Normalmente, cuando estás bien, tienes un porcentaje de saturación de oxígeno (SpO2) cercano o superior al 95%. Cuando la saturación baja del 90% y tienes tos y fiebre, es un problema.
Si cualquier maker pudiera construir un oxímetro, detectar una infección sería más fácil y podría ayudar a las personas a decidir ir al hospital cuando el problema realmente existe y no por un ataque de pánico.
Primero, Entender Cómo Funciona un Sensor de Latido Cardíaco
Comencé este proyecto experimentando con un sensor de latido KY-039 que encontré en un kit de sensores que muchos tenemos en casa. Como se ve en el circuito a continuación, es solo un LED infrarrojo que ilumina un fotodiodo. También hay dos resistencias para proteger el LED y leer la pequeña señal del sensor.
Así que si no tienes un sensor KY-039, puedes construir tu propio sensor con pocos componentes.

El dedo se coloca entre el sensor y el fotodiodo como en esta foto (originalmente tomada de este sitio y modificada):

La luz emitida por el LED infrarrojo es absorbida parcialmente por la uña, la piel y todas las demás partes de tu dedo, pero no es constante porque cambia siguiendo los cambios de la sangre que circula por tus venas. Cuando tu corazón late, la sangre es empujada en tus venas y la absorción de luz cambia. Podemos medir la corriente generada por el fotodiodo iluminado por la luz infrarroja que lo alcanza.
El sensor KY-039 tiene un pin S (señal) para leer ese valor cambiante.
Podemos Medir la Frecuencia Cardíaca Contando Picos de la Señal
Leer un valor de una señal variable de un sensor no es tan fácil, porque hay mucho ruido, la señal es muy baja y necesitamos hacer algunos cálculos para encontrar los buenos valores para graficar.
Agradezco este útil post de Johan Ha, que explica cómo calcular el promedio de la señal y también explica cómo eliminar el ruido producido por una lámpara doméstica (¡esa luz es ruido!).
El truco es hacer un array en el que insertamos un valor y eliminamos otro para promediar los últimos X valores leídos del sensor. También describe una forma de encontrar el ascenso de la señal, contando N valores crecientes. Es decir, cuando un valor es mayor que el anterior N veces, es un pico.
Usando la herramienta Serial Plot de Arduino u otra herramienta serial para analizar valores impresos en el puerto COM (como SerialPlot), y probando diferentes valores, podemos definir un número N correcto (constante rise_threshold en el código). Si defines un número demasiado grande o demasiado pequeño, puedes perder algunos latidos o contar una muesca dicrótica como un latido.
Una vez que entiendes cómo encontrar los picos, solo los cuentas, o calculas el tiempo entre una pequeña serie de latidos para determinar tu tasa de BPM (Beats Per Minute).
Construyendo el Oxímetro (Modificando el Sensor KY-039) para Encontrar la Saturación de Oxígeno
Nuestra sangre absorbe la luz de manera diferente con el cambio de la longitud de onda. La luz roja (~600 nm) es absorbida mejor por la sangre que contiene más oxígeno, así que podemos comparar las medidas hechas con LED infrarrojo (~950 nm) con las hechas con LED rojo y encontrar el porcentaje de oxígeno en nuestra sangre. Ese valor se llama SpO2% (saturación periférica de oxígeno en capilares).
Como tengo un sensor KY-039, decidí modificarlo. Tiene solo un LED infrarrojo, así que agregué un LED ROJO, desconecté el LED IR de Vcc y conecté ambos LEDs con resistencias de 330 ohm a dos pines diferentes de Arduino.
(Si no tienes un sensor KY-039 para modificar, puedes construirlo: son solo un par de LEDs, un fotodiodo y 3 resistencias, ¡y el esquema es muy simple!)
Este es el esquema del sensor modificado:

De esta forma podemos encender el LED IR y leer el valor del pin S del KY-039, luego apagar el LED IR y encender el LED ROJO, y leer nuevamente el pin S.

Si graficas las dos señales, verás que los valores IR son siempre menores que los valores Rojos.

Para obtener una buena señal, recuerda colocar la yema del dedo correctamente sobre el fotodiodo y los LEDs deben tocar la uña. Cuando encuentres una posición cómoda con buena lectura en el gráfico, no la cambies.
Dado que las señales son bajas y el ruido es muy problemático, para obtener medidas útiles noté que siempre se necesita una buena luz ambiental. Así que no muevas el dedo mientras mides y no cambies la luz; incluso una sombra en el sensor podría cambiarlo todo.
Cómo se Mide la Saturación SpO2%
El nivel de saturación de oxígeno (SpO2) es la fracción de hemoglobina saturada de oxígeno relativa a la hemoglobina total y es función de un parámetro llamado R (encontré esta información en un artículo académico del Politécnico de Milán), que se calcula usando los valores mínimo y máximo de las dos señales:
R = ( (REDmax - REDmin) / REDmin ) / ( (IRmax - IRmin) / IRmin )
Cada instrumento tiene su propio R y se necesita calibración para encontrar la curva (la función) que conecta R con SpO2%.
Hemos contado el número de picos, pero ahora necesitamos encontrar los valores máx y mín de las dos curvas (LED ROJO y LED IR).
Para lograr esto, evaluamos el "período" del latido (es decir, cuántos milisegundos dura un latido) y lo dividimos por la tasa de muestreo para determinar cuántas muestras forman un período. La tasa de muestreo en nuestro caso es de 40 milisegundos porque leemos el LED IR durante 20 milisegundos y luego el LED ROJO durante otros 20 milisegundos.
El período del latido es el tiempo que transcurre entre dos ascensos de la señal.
Así puedo analizar las últimas L muestras (donde L = período / 40), que he guardado en un array, para encontrar los valores REDmax, REDmin, IRmax y IRmin.
Con los valores máximo y mínimo puedo calcular R.
R, L y período se calculan cada latido, así que el cálculo de R también se hace para cada latido.
De R a SpO2%: ¿Cómo Calibrar el Oxímetro?
La función que vincula R con SpO2 se puede simplificar con una línea recta:
SpO2 = K * R + M
Así que necesitamos dos puntos (dos pares de valores de SpO2 y R) para determinar K y M. La única forma de encontrar estos 2 puntos es usando otro oxímetro y leyendo los valores de su pantalla.
El nuevo oxímetro será la referencia: leemos el valor de SpO2 mientras medimos los valores de R de nuestro oxímetro casero.
- Respira normalmente y lee el valor de SpO2 y R. Anótalo.
- Luego intenta contener la respiración y después de 10-20 segundos verás que el SpO2 en el nuevo oxímetro disminuye; también deberías ver el parámetro R de tu oxímetro aumentando. Antes de desmayarte, anota los valores de SpO2 alcanzados y el valor de tu parámetro R.
- Resuelve la ecuación de segundo grado y encuentra K y M para tu oxímetro.
Ahora es posible calcular tanto bpm como SpO2 para cada medida de R.
También agregué un display para mostrar todos los números. Muestro valores solo si he encontrado al menos 5 medidas de períodos que no cambian demasiado (±10% de la longitud del período). Así elimino valores que cambian demasiado debido a componentes pobres, cambios de luz ambiental o movimiento del dedo.
El valor c indica que los valores mostrados se calculan con c medidas estables.
Mejora del Proyecto: Eliminar la Variabilidad de la Luz Ambiental
Después de unos días experimentando con mi proyecto, encontré una forma de mejorarlo.
Noté que con estos componentes de bajo costo (¡estamos usando solo LEDs y un fotodiodo!) las medidas dependen demasiado de la luz ambiental, lo cual no es bueno si queremos leer datos correctamente en un entorno de trabajo real. Como noté que en un día soleado los resultados son mejores que con luz nublada o por la noche cuando uso una lámpara eléctrica, decidí agregar un tercer LED, que está siempre encendido y solo proporciona luz en el dedo.

Con este sensor de 3 LEDs, las medidas también se toman bajo un paño negro para excluir la luz ambiental que siempre puede cambiar.

Ahora los resultados son mejores y ya no dependen de la luz ambiental.
También tuve que recalibrar el oxímetro, como se ve en el video, después de unos segundos encuentra correctamente bpm y SpO2%.
Código
oximeter-diy-ver-0.92.ino (Arduino)
Este es el código fuente del Oxímetro DIY, hecho con pocos componentes que un maker podría tener en casa.
/*
* Oxímetro DIY v.0.92 (correcciones menores)
* Modificando un sensor de latido KY-039 o usando un LED infrarrojo,
* un LED rojo y un fotodiodo.
* https://hackaday.io/project/170752-oximeter-do-it-yourself
*/
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#define maxperiod_siz 80 // máx. número de muestras en un período
#define measures 10 // número de períodos almacenados
#define samp_siz 4 // número de muestras para promedio
#define rise_threshold 3 // número de medidas crecientes para determinar un pico
// Display LCD
LiquidCrystal_I2C lcd(0x3F, 16, 2);
int T = 20; // milisegundos para leer un valor del sensor
int sensorPin = A1;
int REDLed = 3;
int IRLed = 4;
byte sym[3][8] = {
{ B00000, B01010, B11111, B11111, B01110, B00100, B00000, B00000 },
{ B00000, B00000, B00000, B11000, B00100, B01000, B10000, B11100 },
{ B00000, B00100, B01010, B00010, B00100, B00100, B00000, B00100 }
};
void setup() {
Serial.begin(9600);
Serial.flush();
pinMode(sensorPin, INPUT);
pinMode(REDLed, OUTPUT);
pinMode(IRLed, OUTPUT);
lcd.init();
lcd.backlight();
digitalWrite(REDLed, LOW);
digitalWrite(IRLed, LOW);
for(int i=0; i<8; i++) lcd.createChar(i, sym[i]);
}
void loop() {
bool finger_status = true;
float readsIR[samp_siz], sumIR, lastIR, reader, start;
float readsRED[samp_siz], sumRED, lastRED;
int period, samples;
period = 0; samples = 0;
int samplesCounter = 0;
float readsIRMM[maxperiod_siz], readsREDMM[maxperiod_siz];
int ptrMM = 0;
for (int i = 0; i < maxperiod_siz; i++) {
readsIRMM[i] = 0; readsREDMM[i] = 0;
}
float IRmax = 0, IRmin = 0, REDmax = 0, REDmin = 0;
double R = 0;
float measuresR[measures];
int measuresPeriods[measures];
int m = 0;
for (int i = 0; i < measures; i++) {
measuresPeriods[i] = 0; measuresR[i] = 0;
}
int ptr;
float beforeIR;
bool rising;
int rise_count;
int n;
long int last_beat;
for (int i = 0; i < samp_siz; i++) {
readsIR[i] = 0; readsRED[i] = 0;
}
sumIR = 0; sumRED = 0;
ptr = 0;
while(1) {
// Encender LED IR
digitalWrite(REDLed, LOW);
digitalWrite(IRLed, HIGH);
// Calcular promedio del sensor durante 20 ms (T) para eliminar ruido de 50 Hz
n = 0; start = millis(); reader = 0.;
do {
reader += analogRead(sensorPin);
n++;
} while (millis() < start + T);
reader /= n;
// Mantener suma de últimas mediciones
sumIR -= readsIR[ptr];
sumIR += reader;
readsIR[ptr] = reader;
lastIR = sumIR / samp_siz;
// Encender LED ROJO
digitalWrite(REDLed, HIGH);
digitalWrite(IRLed, LOW);
n = 0; start = millis(); reader = 0.;
do {
reader += analogRead(sensorPin);
n++;
} while (millis() < start + T);
reader /= n;
sumRED -= readsRED[ptr];
sumRED += reader;
readsRED[ptr] = reader;
lastRED = sumRED / samp_siz;
// CÁLCULO DE R
readsIRMM[ptrMM] = lastIR;
readsREDMM[ptrMM] = lastRED;
ptrMM++; ptrMM %= maxperiod_siz;
samplesCounter++;
// Si he guardado todas las muestras de un período, buscar max/min y calcular R
if(samplesCounter >= samples) {
samplesCounter = 0;
IRmax = 0; IRmin = 1023;
REDmax = 0; REDmin = 1023;
for(int i=0; i<maxperiod_siz; i++) {
if(readsIRMM[i] > IRmax) IRmax = readsIRMM[i];
if(readsIRMM[i] > 0 && readsIRMM[i] < IRmin) IRmin = readsIRMM[i];
readsIRMM[i] = 0;
if(readsREDMM[i] > REDmax) REDmax = readsREDMM[i];
if(readsREDMM[i] > 0 && readsREDMM[i] < REDmin) REDmin = readsREDMM[i];
readsREDMM[i] = 0;
}
R = ((REDmax - REDmin) / REDmin) / ((IRmax - IRmin) / IRmin);
}
// Verificar si el dedo está en el sensor
if (lastRED < lastIR) {
if(finger_status == true) {
finger_status = false;
lcd.clear();
lcd.setCursor(0,0);
lcd.print("No finger?");
}
} else {
if(finger_status == false) {
lcd.clear();
finger_status = true;
lcd.setCursor(10,0); lcd.print("c=");
lcd.setCursor(0,0); lcd.print("bpm");
lcd.setCursor(0,1); lcd.print("SpO"); lcd.write(1);
lcd.setCursor(10,1); lcd.print("R=");
}
}
float avR = 0; int avBPM = 0;
if (finger_status == true) {
// Detectar curva ascendente (latido)
if (lastIR > beforeIR) {
rise_count++;
if (!rising && rise_count > rise_threshold) {
lcd.setCursor(3,0); lcd.write(0); // <3
rising = true;
measuresR[m] = R;
measuresPeriods[m] = millis() - last_beat;
last_beat = millis();
int period = 0;
for(int i=0; i<measures; i++) period += measuresPeriods[i];
period = period / measures;
samples = period / (2*T);
int avPeriod = 0; int c = 0;
for(int i=1; i<measures; i++) {
if((measuresPeriods[i] < measuresPeriods[i-1] * 1.1) &&
(measuresPeriods[i] > measuresPeriods[i-1] / 1.1)) {
c++; avPeriod += measuresPeriods[i]; avR += measuresR[i];
}
}
m++; m %= measures;
lcd.setCursor(12,0); lcd.print(String(c) + " ");
avBPM = 60000 / (avPeriod / c);
avR = avR / c;
lcd.setCursor(12,1);
if(c == 0) lcd.print(" "); else lcd.print(String(avR) + " ");
if(c > 4) {
// SATURACIÓN ES FUNCIÓN DE R (calibración) Y = k*x + m
int SpO2 = -19 * R + 112;
lcd.setCursor(4,0);
if(avBPM > 40 && avBPM < 220) lcd.print(String(avBPM) + " ");
lcd.setCursor(4,1);
if(SpO2 > 70 && SpO2 < 150) lcd.print(" " + String(SpO2) + "% ");
} else {
if(c < 3) {
lcd.setCursor(3,0); lcd.write(2); // bpm ?
lcd.setCursor(4,1); lcd.write(2); // SpO2 ?
}
}
}
} else {
rising = false; rise_count = 0;
lcd.setCursor(3,0); lcd.print(" ");
}
beforeIR = lastIR;
}
// PLOT todo
Serial.print(lastIR); Serial.print(",");
Serial.print(lastRED);
Serial.println();
ptr++; ptr %= samp_siz;
}
}
Esquemáticos
En mi proyecto modifiqué un KY-039, pero ese sensor no está disponible en la librería de Fritzing, así que lo construí con los pocos componentes que lo componen, pero no encontré un fotodiodo adecuado en la librería de Fritzing.

Un oxímetro simple para leer oxígeno en la sangre puede hacerse modificando el sensor KY-039, o construyendo un sensor desde cero.
Proceso de manufactura
- Construya un Sensor de Movimiento Infrarrojo DIY para Raspberry Pi – Guía Paso a Paso
- Controle un LED con Sensor PIR de Movimiento en Raspberry Pi
- Construya un Sensor de Humedad de Suelo de Bajo Coste para Riego Inteligente con Raspberry Pi
- Prueba del Sensor de Temperatura DS18B20 en Raspberry Pi
- Monitor de temperatura casero con Raspberry Pi, MCP9808, InfluxDB y Grafana
- Sensor Telemetry 2.0.1 – Monitorización Avanzada de Temperatura con Azure IoT Hub
- Sensor de Emociones y EEG: Hardware de Bajo Coste para Detección de Emociones en Tiempo Real
- TV‑B‑Gone DIY con Arduino: Mando universal para apagar cualquier TV
- Detección precisa de temperatura por infrarrojos para procesos sensibles al color
- Circuito Ahorrador de Energía con Sensor Infrarrojo Pasivo: Diseño, Operación y Aplicaciones
- ¿Qué Longitud de Onda LiDAR Dominará Aplicaciones Automotrices?