Uno de los parámetros que se monitorizan en mi proyecto de gestión del sueño es el pulso. Para medirlo desarrollé un dispositivo basado en el comportamiento de la hemoglobina y la oxihemoglobina frente a las diferentes longitudes de onda de la luz. Básicamente se trata de medir cuánta luz de determinado tipo es capaz de atravesar o es reflejada en una zona del cuerpo bien irrigada. La frecuencia con la que ocurre un ciclo completo de este fenómeno permite medir el pulso.
En la fase de diseño y prueba del dispositivo para medir el pulso desarrollé algunos pequeños programas para ayudarme a verificar que el montaje era correcto. En primer lugar escribí el código de abajo, que iba tomando los valores medidos cada cierto tiempo (como mínimo cada y como máximo cada ) cuando variaban un mínimo entre uno y el anterior (el valor que corresponde con ) y los monitorizaba desde un ordenador con una aplicación en Python para poder analizarlos posteriormente.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
#define PIN_OXIMETRO 0 // Pin analógico 0 #define MEDIDA_MINIMA 10 // Cambio menor monitorizado #define TIEMPO_MAXIMO_MEDIDA 200 // Cambio menor entre medidas (determina la resolución vertical) #define TIEMPO_MINIMO_MEDIDA 100 // Milisegundos entre medidas (determina la resolución horizontal) int lectura_anterior_oximetro=0; int lectura_oximetro; unsigned long cronometro_minimo=0; unsigned long cronometro_maximo=0; void setup() { Serial.begin(9600); //pinMode(PIN_OXIMETRO,INPUT); // Ya es entrada por defecto } void loop() { if(millis()>cronometro_minimo) { lectura_oximetro=analogRead(PIN_OXIMETRO); if(abs(lectura_oximetro-lectura_anterior_oximetro)>MEDIDA_MINIMA||millis()>cronometro_maximo) { cronometro_minimo=millis()+TIEMPO_MINIMO_MEDIDA; cronometro_maximo=millis()+TIEMPO_MAXIMO_MEDIDA; lectura_anterior_oximetro=lectura_oximetro; Serial.println(String(millis(),DEC)+","+String(lectura_oximetro,DEC)); } } } |
Una vez ajustados los valores (empezando en medidas muy densas) conseguí una colección de valores del oxímetro de pulso a lo largo del tiempo que podía representar gráficamente utilizando una hoja de cálculo, LibreOffice Calc de LibreOffice, en concreto.
Con los datos recabados, como representa la imagen de arriba, la siguiente operación era determinar si la densidad de valores permitía calcular de manera fiable pero «económica» (no muestrear más de los datos necesarios) el valor del pulso; como puede verse en el gráfico de abajo, las medidas tomadas parecían servir para obtener los resultados que es razonable esperar.
.
A continuación, con la información del muestreo de datos, quedaba elaborar un algoritmo que midiera la frecuencia del pulso. Ateniéndose a la gráfica que, por simplificar, se asume que representa un trazado similar al complejo QRS, lo más sencillo parece medir los tiempos entre las partes más destacadas, con valores mayores (que corresponde con la zona qRs de despolarización de los ventrículos) descartando la zona más plana y «ruidosa», más difícil de medir, por tanto. La solución adoptada, que corresponde con el código de prueba de abajo, funciona conforme al siguiente procedimiento:
-
Detectar la zona que se está midiendo en cada caso para atender sólo a los picos de valor qRs y desechar el valle. Para hacerlo se podrían medir valores superiores a cierta constante pero existe el riesgo de que un individuo y/o unas circunstancias que, aunque proporcionalmente, subieran o bajaran los valores. Para evitarlo, se considera un valor en la zona mayor al que supera en cierto coeficiente al valor medio. De esta forma, la medida se auto-calibra sensiblemente y se podría ajustar aún más afinando el coeficiente, que en mi caso he conseguido experimentalmente durante las pruebas.
Elegir para la medida los valores de la zona descendente (Rs) del pico qRs, lo más cercanos posible al máximo de la curva. Para saber que se abandona la zona ascendente basta con comprobar que un nuevo valor es menor que el anterior y verificar que no se ha encontrado todavía el valor buscado puesto que, en general, hay varios valores en la zona descendente de qRs dependiendo de la densidad de muestreo. Para cronometrar el pulso se almacena el valor del instante en el que se encontró el punto (los milisegundos devueltos por millis()) y se compara con el siguiente.
Para asegurarse de que el valor que se mide es el mayor de la zona descendente de la curva más alta se usa una variable booleana ( en este ejemplo y en la librería) que se activa al entrar en la zona ascendente de la curva mayor y se desactiva una vez encontrado el primer valor descendente, que es el cronometrado.
Como lo habitual es representar la duración del pulso como pulsaciones por minuto (ppm) se corrige el valor de tiempo entre pulsaciones obtenido calculando dividiendo el tiempo total de la representación (un minuto, 60000 milisegundos) entre el intervalo obtenido al restar los milisegundos actuales (del valor actual) entre los anteriormente cronometrados.
Para evitar medidas falsas (como el dispositivo midiendo en vacío, por ejemplo) se verifica que el resultado se encuentra entre unos valores máximos y mínimos antes de darlo por cierto. Aunque se considera como media que un valor normal para un adulto sano en reposo se encuentra entre 60 y 100 ppm, hay valores admisibles por debajo, es fácil encontrar 40 ppm en un atleta en reposo, hasta 200 ppm sometido a un ejercicio intenso y más de 100 ppm en adultos sedentarios en estados de excitación, precisamente un factor interesante para el proyecto de gestión del sueño que me lleva a desarrollar este dispositivo de medición del pulso. Por esto, es recomendable relajar mucho estos valores de manera que no se pierdan los extremos, que precisamente podrían mostrar aspectos relevantes.
El nuevo valor medio se calcula disminuyendo la relevancia de la media actual en función del número de valores muestreados y se añade el último valor, ponderado también con un coeficiente que lo reduce más cuanto más valores se hayan medido hasta el momento.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
#define PIN_OXIMETRO 0 // Pin analógico 0 #define TIEMPO_MINIMO_MEDIDA 20 // Milisegundos entre medidas (determina la resolución horizontal) #define COEFICIENTE_PULSO 1.25 // Coeficiente que determina la zona de valores en la que medir el pulso #define PULSO_MENOR 30 // Ignorar valores menores (Es infrecuente un pulso más lento aún en reposo) #define PULSO_MAYOR 180 // Ignorar valores mayores (Es infrecuente un pulso mayor en reposo) #define MINUTO 60000.0 // Milisegundos en un minuto float velocidad_pulso; float lectura_media_oximetro=511.5; unsigned long valores_contados=0; int lectura_anterior_oximetro=0; int lectura_oximetro; boolean medir_pulso=false; unsigned long cronometro_pulso=0; unsigned long cronometro_oximetro=0; void setup() { Serial.begin(9600); //pinMode(PIN_OXIMETRO,INPUT); // Ya es entrada por defecto } void loop() { if(millis()>cronometro_oximetro) { cronometro_oximetro=millis()+TIEMPO_MINIMO_MEDIDA; lectura_oximetro=analogRead(PIN_OXIMETRO); valores_contados++; lectura_media_oximetro=lectura_media_oximetro*(float(valores_contados-1)/valores_contados); lectura_media_oximetro+=lectura_oximetro*(1.0/valores_contados); if(lectura_oximetro>lectura_media_oximetro*COEFICIENTE_PULSO) { if(lectura_anterior_oximetro<lectura_oximetro) { medir_pulso=true; } else { if(medir_pulso) { velocidad_pulso=MINUTO/float(millis()-cronometro_pulso); medir_pulso=false; if(velocidad_pulso>PULSO_MENOR&&velocidad_pulso<PULSO_MAYOR) { Serial.println("Pulso "+String(velocidad_pulso,DEC)); } cronometro_pulso=millis(); } } } lectura_anterior_oximetro=lectura_oximetro; } } |
Por último, utilizando el algoritmo descrito antes, desarrollé la librería para calcular el pulso detectando la presencia de la hemoglobina o la oxihemoglobina (según la longitud de onda de la luz usada) del código de abajo.
La librería espera que se llame periódicamente a la función de muestreo para ir calculando el pulso, que puede consultarse con la función o con la función el pulso medio. Además de por ser un recurso limitado, descarté utilizar interrupciones por no necesitar valores inmediatos sino sostenidos en el tiempo para monitorizar el pulso en mi proyecto de gestión del sueño . En cualquier caso, por las pruebas que he hecho, no parece ser necesario; ya sea por el dispositivo o por el comportamiento del pulso, un muestreo a determinada frecuencia ofrece suficiente información y no se obtiene mucha más (relevante) por aumentarlo ni es posible disminuirla mucho sin perder datos relevantes para el cálculo; en las primeras versiones del código para monitorizar la lectura del oxímetro de pulso descubrí que no era necesario atenerse a un tiempo de medida máximo ya que, si se consideraban correctamente las variaciones de valores sucesivos, era muy cercano al mínimo.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
//pulso.h #if defined(ARDUINO) && ARDUINO>=100 #include "Arduino.h" #else #include "WProgram.h" #endif #define PULSO_MINIMO 5 // Pulso mínimo. Un pulso menor se considera un error. #define PULSO_MAXIMO 200 // Pulso máximo. Un pulso mayor se considera un error. #define COEFICIENTE_PULSO 1.25 // Coeficiente que multiplica el pulso medio para determinar si el valor medido está en la zona que se cronometra. Puede usarse para calibra el dispositivo #define OXIMETRIA_MEDIA 511.5 // Medida media inicial del pulso (1023/2) Puede usarse para calibrar el dispositivo #define PULSO_MEDIO 80.0 // Pulso medio de un adulto en reposo (sólo para referencia) class Pulso { private: byte pin_oximetro; // Pin analógico al que se conecta el sensor de pulso int lectura_oximetro; // Último valor medido en el sensor de pulso int lectura_anterior_oximetro; // Penúltimo valor medido en el sensor de pulso para compararlo con el último y establecer la zona de valores en la que se encuentra unsigned long numero_lecturas_oximetro; // Número de medidas del oxímetro tomadas. Usado para calcular la media unsigned long numero_lecturas_pulso; // Número de medidas de pulso tomadas. Usado para calcular la media float lectura_media_oximetro; // Medida media del sensor de pulso. Usado para saber si un valor se encuentra en la zona de medida (que se cronometra) del pulso boolean medicion_de_pulso_activa; // Verdadero si se ha entrado de la zona del pulso (para no confundir si aún se encuentra en una zona ya medida) float velocidad_pulso; // Último valor de pulso calculado float ultima_velocidad_pulso; // Último valor de pulso correcto float velocidad_media_pulso; // Media de los pulsos calculados durante la última sesión (creación del objeto Pulso) unsigned long cronometro_pulso; // Tiempo entre medidas de pulso consecutivas protected: public: Pulso(byte pin_oximetro_solicitado); ~Pulso(); void monitorizar_pulso(); // Se llama periódicamente (entre 10 y 50 ms) para calcular el pulso byte ultimo_pulso(); // Devuelve el último valor correcto de pulso muestreado byte pulso_medio(); // Devuelve el pulso medio de la sesión }; |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
//pulso.cpp #include "pulso.h" Pulso::Pulso(byte pin_oximetro_solicitado) { pin_oximetro=pin_oximetro_solicitado; //pinMode(pin_oximetro,INPUT); // Ya es entrada por defecto numero_lecturas_oximetro=0; numero_lecturas_pulso=0; lectura_media_oximetro=OXIMETRIA_MEDIA; velocidad_media_pulso=PULSO_MEDIO; medicion_de_pulso_activa=false; lectura_anterior_oximetro=0; ultima_velocidad_pulso=0; // Para indicar que es un valor incorrecto por el momento cronometro_pulso=0; } Pulso::~Pulso() { } byte Pulso::ultimo_pulso() { return round(ultima_velocidad_pulso); } byte Pulso::pulso_medio() { return round(velocidad_media_pulso); } void Pulso::monitorizar_pulso() { lectura_oximetro=analogRead(pin_oximetro); numero_lecturas_oximetro++; lectura_media_oximetro=lectura_media_oximetro*(float(numero_lecturas_oximetro-1)/numero_lecturas_oximetro); // Cambiar la representatividad de la parte actual de la media de pulso lectura_media_oximetro+=lectura_oximetro*(1.0/numero_lecturas_oximetro); // Añadir la nueva lectura a la media if(lectura_oximetro>lectura_media_oximetro*COEFICIENTE_PULSO) { if(lectura_anterior_oximetro<lectura_oximetro) // Medida de valores creciente { medicion_de_pulso_activa=true; } else { if(medicion_de_pulso_activa) { velocidad_pulso=60000.0/float(millis()-cronometro_pulso); // Cálculo de las pulsaciones por minuto (representación habitual del pulso) medicion_de_pulso_activa=false; // Ya se ha medido el pulso, no medir hasta entrar en una nueva zona ascendente if(velocidad_pulso>PULSO_MINIMO&&velocidad_pulso<PULSO_MAXIMO) { numero_lecturas_pulso++; ultima_velocidad_pulso=velocidad_pulso; velocidad_media_pulso=velocidad_media_pulso*(float(numero_lecturas_pulso-1)/numero_lecturas_pulso); // Calcular la representatividad de la media actual en la nueva media velocidad_media_pulso+=velocidad_pulso*(1.0/numero_lecturas_pulso); // Añadir la nueva lectura a la media } cronometro_pulso=millis(); } } } lectura_anterior_oximetro=lectura_oximetro; } |
En el siguiente programa de ejemplo se muestra cómo utilizar la librería anterior para medir el pulso con un oxímetro de pulso. Además de instanciar la clase se llama periódicamente a la monitorización del nivel de oxihemoglobina/hemoglobina y con una periodicidad menor se muestra el valor del pulso calculado y de la media.
Para asegurar que las medidas son relevantes se programa una espera antes de mostrar ningún valor. Como el valor puede ser incorrecto (por ejemplo si el usuario se retira el dispositivo) sólo se muestran valores si están dentro del rango de los considerados válidos.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
#define PIN_PULSOMETRO 0 #define TIEMPO_MONITORIZACION_PULSO 20 #define TIEMPO_PRESENTACION_PULSO 5000 #include "pulso.h" Pulso pulso(PIN_PULSOMETRO); unsigned long cronometro_monitorizacion_pulso; unsigned long cronometro_presentacion_pulso; byte velocidad_pulso; byte velocidad_media_pulso; void setup() { Serial.begin(9600); cronometro_monitorizacion_pulso=0; cronometro_presentacion_pulso=TIEMPO_PRESENTACION_PULSO*2; } void loop() { if(millis()>cronometro_monitorizacion_pulso) { cronometro_monitorizacion_pulso=millis()+TIEMPO_MONITORIZACION_PULSO; pulso.monitorizar_pulso(); } if(millis()>cronometro_presentacion_pulso) { cronometro_presentacion_pulso=millis()+TIEMPO_PRESENTACION_PULSO; velocidad_pulso=pulso.ultimo_pulso(); velocidad_media_pulso=pulso.pulso_medio(); if(velocidad_pulso) { Serial.print("Pulso "); Serial.print(velocidad_pulso,DEC); Serial.print(" | Pulso medio "); Serial.println(velocidad_media_pulso,DEC); } } } |



DARIO MEJIA
hola que tal me intereso la informacion me gustaria tener el esquematico del pulso oximetro para la utilisacion de esa libreria
gracias
Víctor Ventura
Hola, Darío.
Como explico en el artículo sobre el principio de funcionamiento del oxímetro para monitorización del pulso, puede hacerse de varias formas dependiendo de qué componentes elijas (fotodiodo, fototransistor o ambos) y cómo decidas amplificar y filtrar la señal.
Si necesitas partir de cero puedes usar los esquemáticos de algún proyecto comercial que funcione (abajo te indico algunos) y cuando esté en marcha, para no acumular problemas, hacerte tu propia versión; no es especialmente complicado.
Monitor de pulso de SeekIC
Monitor de pulso de EL-PRO-CUS basado en un 8051
Suerte y gracias por visitarnos.
c
donde encuantro esa libreria
Víctor Ventura
¡Hola!
En polaridad.es nos gusta más explicar las cosas que darlas hechas. En cualquier caso, esperaba que no fuera difícil copiar el código para hacer la librería. Como también nos gusta ayudar a nuestros lectores, la he grabado para ti: descargar la librería Arduino para monitorización de la frecuencia cardíaca (el pulso) por oximetría.
Gracias por visitar el blog polaridad.es ¡vuelve pronto!
Jeisson Ochoa
hola me gustaría saber cual es la periodicidad con la que se debe encender el led rojo y el infrarrojo con el arduino para la medición de oxihemoglobina/hemoglobina gracias
Víctor Ventura
Hola, Jeisson.
Los LED de cada color se encienden cuando va a leerse cada uno de ellos, no es buena idea mantener ambos encendidos a la vez, ya que se puede falsear la lectura porque, salvo parejas emisores-receptores muy bien limitadas en longitud de onda, los receptores de infrarrojos detectan algo de la luz roja y al contrario.
El uso de la librería prevé que se tomen muestras cada vez que sea posible, es decir, a lo largo del programa en el que la uses, debes incluir
monitorizar_pulso()cada vez que sea posible.Si el teorema de muestreo de Nyquist-Shannon es cierto 🙂 debes muestrear a una frecuencia superior al doble de la que esperas medir, pero la forma (muy simple) de calcular el pulso que hace la librería necesita encontrar puntos en los que ascienda y descienda el nivel, digamos cuatro. Dependiendo del pulso máximo que esperes, digamos 150 ppm (2.5 Hz) sería necesario tomar, para tener un mínimo de seguridad, 2×4×2.5=20 muestras cada segundo, es decir, una muestra cada 50 ms. Si te fijas en el código de ejemplo, tomo muestras cada 20 ms y funciona bien. La verdad, no he probado a 50 ms, solamente teorizo para ayudarte a entender cómo lo he planteado.
Sería fenomenal que nos contaras el resultado de tus pruebas. Y por supuesto, se admiten sugerencias.
Gracias por visitar polaridad.es y un saludo.
Javier
Buenas tardes.
Gracias por el artículo, me ha servido de gran utilidad. Lo único, es una tontería pero hay una pequeña errata en el segundo gráfico del pulso (el de color verde), donde pone «IrrigacióN (Y) a lo largo del tiempo (X en ms) |—-| pulso ¬ 91 ms ¬ 66 ppm», haciendo referencia a la detección entre pulso y pulso, donde el gráfico indica que hay 91 ms cuando debería ser 910 ms
Gracias por el artículo, un saludo.
Víctor Ventura
Hola, Javier.
Tienes razón. Que fuera verde (y no «color polaridad.es») tendría que haberme dado una pista de que el gráfico no era el bueno.
Ya está corregido para no confundir a los lectores.
Gracias por avisar 🙂
Gabriel Lascano
hola que tal estoy desarrollando un proyecto de meidor de pulsos cardiacos y presentarlos en una lcd deseo saber si me pudiese ayudar con el codigo de arduino si me podria facilitarlo gracias
Víctor Ventura
Hola, Gabriel.
No sé si te entiendo ¿Quieres que te haga yo el programa de tu proyecto? Si te refieres a si puedes usar el código del artículo, la respuesta es sí, conforme a lo establecido en la licencia Creative Commons Reconocimiento-CompartirIgual 3.0, como el resto del contenido.
Gracias por visitar polaridad.es
G
Hola, fijate qye estoy haciendo un oximetro y lo que quiero es que el valor de la saturacion de oxigeno y el pulso cardiaco se vea reflejado en una pantalla lcd, como podria hacer eso?
Gracias!!
Alfredo
Buen trabajo
Víctor Ventura
🙂
camilo
Buen trabajo , estoy interesado en este proyecto porque necesito salvar la materia agradecería el circuito esquemático.