Вы находитесь на странице: 1из 15

MAX30100 sensor de latido de corazón y oxímetro de

pulso con comunicaciones I2C para wearables de salud


Como explico en el artículo sobre el principio de funcionamiento del oxímetro para
monitorización del pulso, una forma sencilla de estimar el pulso es infiriendo la cantidad
de oxígeno en sangre por su color. Cuando la sangre pasa por los pulmones
la hemoglobina (Hb) se oxigena convirtiéndose en oxihemoglobina (HbO2). En zonas
del cuerpo bien irrigadas y con piel fina se puede detectar la absorción diferencial de
luz de diferente color (longitud de onda), especialmente el rojo entorno a 650 nm, y el
infrarrojo entorno a 950 nm. De manera mucho más cómoda y equiparable en eficacia,
también se puede estimar el ritmo cardíaco (el pulso) midiendo la luz reflejada en cierta
parte del cuerpo (un dedo, típicamente) en lugar de la luz que atraviesa esa zona.

Si bien no es especialmente difícil encontrar componentes electrónicos convencionales


que se adapten bien tanto a la medida de la luz que atraviesa como a la que refleja,
la popularización de los wearables con sistemas de monitorización de la salud ha
animado a los fabricantes a integrar en un único componente LED, fotodiodos,
circuitería de corrección y amplificación, conversión digital (ADC) de la señal obtenida
y comunicaciones, lo que simplifica notablemente la medida del ritmo cardíaco desde
un MCU. La serie MAX3010X de Maxim Integrated es un ejemplo bastante
representativo de este tipo de productos, tanto para medir el pulso en un dedo
(MAX30100) como para aplicarlo a muñequeras (MAX30101 y MAX30102).

Si la longitud de onda de la luz emitida es suficientemente exacta, es decir, se ciñe a la


correspondiente del rojo (650 nm) y del infrarrojo (950 nm) y el sensor que detecta la
luz es suficientemente preciso, también es posible determinar la saturación (porcentual)
arterial de oxígeno SpO2 (pulsioximetría).

La saturación de hemoglobina (SaO2) se suele expresar como un coeficiente que


relaciona la proporción de Hbfrente a la de HbO2 y se calcula dividiendo la cantidad
estimada de HbO2 entre la suma de la misma y la de Hb. La SpO2 se obtiene
multiplicando por 100 la SaO2 para expresarla como un porcentaje.

Este objetivo, determinar la presencia de oxígeno en sangre, es mucho más ambicioso


que el explicado en el artículo sobre la monitorización del ritmo cardíaco usando un
oxímetro mediante una librería Arduino para medir el pulso, que solamente busca
detectar el latido del corazón. Aunque, como otros dispositivos similares,
el MAX30100 está diseñado para ser implementado en wearables de salud y no en el
ámbito clínico, añadir la posibilidad de estimar la SpO2 supone un paso adelante en
prestaciones.
Funcionamiento del oxímetro de pulso MAX30100
El MAX30100 dispone de un LED rojo y otro infrarrojo (el MAX30101 cuenta además
con un tercer LED verde) que quedan arriba en las siguientes imágenes de detalle y
que iluminan alternativamente durante cierto tiempo (ancho de pulso) la zona expuesta
(un dedo, por ejemplo). La luz reflejada se detecta con un fotodiodo, que queda en la
parte de abajo de las siguientes imágenes de detalle. La corriente de estos LED se
puede configurar a un valor entre cero y 50 mA.

A la intensidad de luz detectada por el fotodiodo ① se le corrige la desviación que


produce la iluminación ambiental (ALC, cancelación de la luz ambiental), ② el ruido
eléctrico de baja frecuencia (los 50 Hz o 60 Hz de la línea eléctrica dependiendo del
lugar) y ③ la influencia de la temperatura, para lo que cuenta con un termómetro
interno. La señal obtenida se convierte en valores digitales con un ADC por modulación
Sigma-Delta y se almacena en un buffer al que se puede acceder desde
un microcontrolador mediante el bus I2C con que cuenta el MAX30100.

La precisión del valor digital obtenido depende de tres parámetros que están
interrelacionados: ① la resolución (configurable hasta 16 bits), ② el ancho del pulso
que determina el tiempo de iluminación de los LED (entre 200 µs y 1600 µs) y ③ la
velocidad de muestreo del fotodiodo (entre 50 y 1000 muestras por segundo). Para
obtener una mayor resolución debe ser mayor la duración del pulso y se reduce el
número de muestras que pueden tomarse. Los valores de más resolución (16 bits)
corresponden con el pulso de 1600 µs y un máximo de 100 lecturas por segundo.

El oxímetro de pulso MAX30100 guarda la configuración en un conjunto de registros de


8 bits y utiliza una memoria FIFO de 64 bytes para almacenar hasta 16 lecturas
completas formadas por 16 bits para el canal rojo y otros 16 bits para el canal infrarrojo.
Como es posible disminuir la resolución para aumentar la velocidad de conversión
analógica a digital o leer el valor del pulso en lugar de la intensidad de luz, los bits que
sobran al usarlo de este modo se ignoran (se rellenan con ceros) almacenando siempre
el mismo número de lecturas en la memoria FIFO.

Se pueden configurar dos modos de funcionamiento en el MAX30100, el modo «latido»,


que consiste en utilizar solamente la luz infrarroja, y el modo «SpO 2», que considera
tanto la luz roja como la infrarroja, por lo que almacena el doble de datos en la
memoria FIFO.
Circuito de aplicación del MAX30100
La inclusión del MAX30100 en un circuito es muy sencilla. Del lado del MCU solamente
es necesario conectar elbus I2C y una entrada para las interrupciones utilizando, para
las tres líneas, resistencias de 4K7. El lado de la alimentación necesita dos tensiones
diferentes, la que alimenta los LED, de 3V3 (y hasta 50 mA) aunque es tolerante a 5 V
y la que alimenta el resto de componentes del MAX30100 de 1V8. Por su uso,
normalmente portátil, es frecuente que la alimentación principal sea de 3V3 y solamente
será necesario un pequeño regulador de tensión; si la tensión base fuera diferentes
(5 V, por ejemplo) el circuito debería de disponer de dos reguladores, uno para cada
nivel de tensión.

En el esquema de ejemplo anterior se han utilizado reguladores 6206 (hasta 250 mA),
como los LM6206 o los XC6206, pensando en el bajo consumo del MAX30100. Si se
necesitara una corriente mayor para otros componentes del circuito, con mínimas
modificaciones (condensadores electrolíticos de 10 µF a la entrada y 22 µF a la salida),
se puede sustituir, por ejemplo, por algún 1117 (algo más de 1 A) como los LM1117 o
los AMS1117, que también son muy económicos y sencillos de encontrar.

Para el esquema, solamente se ha utilizado una conexión para ambos LED pero
el MAX30100 permite conectarlos por separado, incluyendo una conexión para cada
ánodo. Los ánodos están internamente conectados a GND a través del driver de
los LED. Típicamente, los ánodos de ambos LED se dejan sin conectar, por lo que no
están indicados en el diagrama del ejemplo.

Gestión por software del oxímetro de pulso MAX30100


Atendiendo a método de uso propuesto en la hoja de datos del MAX30100, una vez
configurado el modo de funcionamiento, la lectura del pulso o de la intensidad de luz
detectada por los LED debe realizarse: ① consultando primero el número de lecturas
disponibles y ② repetirse tantas veces como valores haya disponibles en la FIFO. El
puntero que indica la lectura que corresponde consultar se actualiza automáticamente,
si la lectura no se hubiera podido realizar de forma correcta sería necesario
③ actualizar «manualmente» el puntero para releer el valor que quede pendiente.

El uso más habitual consiste en realizar una lectura periódica del sensor de pulso pero
también se puede optar por usar la interrupción que se activa cuando la FIFO está casi
llena y recoger los valores cuando se produzca el aviso. Una vez generada la
interrupción el proceso de lectura sería el mismo. Si se opta por la lectura periódica, no
es necesario gestionar el tiempo desde el MCU, ya que el MAX30100 generará una
interrupción cuando termine el ciclo de integración, que tiene una duración fija y
configurable.

Además del modo de lectura de HbO2oxihemoglobina, que entrega dos bytes para los
valores de intensidad de luz roja en infrarroja detectada, el MAX30100 dispone de un
«modo latido» (o pulso) que solamente almacena el valor (dos bytes) de la lectura de
la luz infrarroja, dejando a cero los dos restantes que igualmente será necesario recibir
para que se actualice el puntero de lectura.

La lectura de la temperatura no se realiza de forma sistemática como la de la luz


reflejada. El protocolo habitual para leer la temperatura consiste en activar la
interrupción correspondiente y el modo de lectura de temperatura. Cuando se ha
completado el proceso (el MAX30100 tarda unos 30 ms en completar la lectura de la
temperatura) se activa la interrupción y se desactiva el modo de lectura de la
temperatura, por lo que será necesario activarlo para volver a medirla.

La temperatura se almacena en dos registros de 8 bits como una parte entera


en complemento a dos (con signo) y otra fraccionaria en incrementos de dieciseisavos
de grado Celsius.

Registros del MAX30100


Para explicar la finalidad de los registros del MAX30100 y el significado de los valores
que representan, se puede utilizar como referencia el encabezado con el que se
definen para usarlos en la programación con el conocido entorno de desarrollo
de Arduino.
1
#define MAX30100_DIRECCION 0x57 // Dirección del MAX30100 en el formato que espera la librería Wire de Arduino
2
3
#define MAX30100_INTERRUPCION_ESTADO 0x00 // Descripción del estado que ha producido la interrupción
4
#define MAX30100_INTERRUPCION_ACTIVA 0x01 // Descripción de los eventos que producen una interrupción
5
6
#define MAX30100_FIFO_PUNTERO_ESCRITURA 0x02 // FIFO_WR_PTR Puntero a la dirección en la que se grabará la siguiente
7
lectura (Es recomendable inicializarlo a cero al empezar)
8
#define MAX30100_FIFO_DESBORDAMIENTO 0x03 // OVF_COUNTER Contador de las lecturas almacenadas en la FIFO que
9
no se han leído
10
#define MAX30100_FIFO_PUNTERO_LECTURA 0x04 // FIFO_RD_PTR Puntero al valor que toca leer. Se actualiza
11
automáticamente y se puede escribir, además de leerse, para releer valores
12
#define MAX30100_FIFO_DATOS 0x05 // FIFO_DATA Datos que toca leer (la dirección de la FIFO apuntada por
13
MAX30100_FIFO_PUNTERO_LECTURA)
14
15
#define MAX30100_CONFIGURACION_MODO 0x06 // Establece la manera en la que funciona el MAX30100 en sus diferentes
16
aspectos. Por medio de este registro, además de determinar el modo de medida (latido o pulso frente a SPO2) se pasa al modo de ahorro de
17
energía, se resetea y se pide la medida de la temperatura
18
#define MAX30100_CONFIGURACION_SPO2 0x07 // Determina la precisión de las medidas: activa la resolución de 16 bits,
19
configura la frecuencia de muestreo y la duración del pulso de los LED (que indirectamente determina la resolución de la conversión
20
analógica a digital)
21
22 #define MAX30100_CONFIGURACION_LED 0x09 // Almacena la corriente de los led rojo (4 bits más significativos) e infrarrojo
23 (4 bits menos significativos)
24
25 #define MAX30100_TEMPERATURA_ENTERO 0x16 // TINT Parte entera de la temperatura medida
26 #define MAX30100_TEMPERATURA_FRACCION 0x17 // TFRAC Parte fraccionaria de la la temperatura medida (dieciseisavos
27 de grado Celsius)
28 #define MAX30100_TEMPERATURA 0x16 // Como los dos registros son consecutivos, se suelen leer en una misma
29 operación, para lo que resulta más claro llamarlos simplemente TEMPERATURA
30
31 #define MAX30100_VERSION 0xFE // REV_ID Versión del MAX30100
32 #define MAX30100_IDENTIFICADOR 0xFF // PART_ID Código de identificación del MAX30100
33
34
35 // Bits indicadores de los tipos de interrupción (registros MAX30100_INTERRUPCION_ESTADO y
36 MAX30100_INTERRUPCION_ACTIVA)
37
38 #define MAX30100_FIFO_CASI_LLENA 0B10000000 // A_FULL La memoria está casi llena (queda espacio para una lectura)
39 #define MAX30100_TEMPERATURA_LISTA 0B01000000 // TEMP_RDY Se puede leer la temperatura (temperatura lista)
40 #define MAX30100_PULSO_LISTO 0B00100000 // HR_RDY Se puede leer el pulso
41 #define MAX30100_LED_LISTOS 0B00010000 // SPO2_RDY Se pueden leer los LED
42 #define MAX30100_ENCENDIDO 0B00000001 // PWR_RDY El MAX30100 ha terminado el ciclo de encendido y está listo
43 para utilizarse
44
45
46 // Bits del modo de funcionamiento MAX30100_CONFIGURACION_MODO
47
48 #define MAX30100_APAGAR 0B10000000 // SHDN Apagar el MAX30100 (iniciar el modo de ahorro de energía)
49 #define MAX30100_RESETEAR 0B01000000 // RESET Resetear el MAX30100
50 #define MAX30100_TEMPERATURA_ACTIVAR 0B00001000 // TEMP_EN Iniciar la lectura de la temperatura (solamente se lee una
51 vez y cambia automáticamente el valor de este bit por cero)
52 #define MAX30100_PULSO_ACTIVAR 0B00000010 // MODE(1) Establecer el modo de lectura de pulso (informar del latido del
53 corazón en lugar de la luz de los LED reflejada)
54 #define MAX30100_LED_ACTIVAR 0B00000011 // MODE(2) Establecer el modo de lectura de la luz reflejada por los LED (en
55 lugar del latido del corazón)
56
57
58 // Bits de configuración de lectura del pulso MAX30100_CONFIGURACION_SPO2
59
60 #define MAX30100_ALTA_RESOLUCION 0B01000000 // SPO2_HI_RES_EN
61 #define MAX30100_PULSO_50_MPS 0B00000000 // 50 muestras por segundo. Leer el pulso (latido del corazón) 50 veces por
62 segundo
63 #define MAX30100_PULSO_100_MPS 0B00000100 // 100 muestras por segundo
64 #define MAX30100_PULSO_167_MPS 0B00001000 // 167 muestras por segundo
65 #define MAX30100_PULSO_200_MPS 0B00001100 // 200 muestras por segundo
66 #define MAX30100_PULSO_400_MPS 0B00010000 // 400 muestras por segundo
67 #define MAX30100_PULSO_600_MPS 0B00010100 // 600 muestras por segundo
68 #define MAX30100_PULSO_800_MPS 0B00011000 // 800 muestras por segundo
69 #define MAX30100_PULSO_1000_MPS 0B00011100 // 1000 muestras por segundo. Leer el pulso (latido del corazón) 1000 veces
70 por segundo
71
72 #define MAX30100_TIEMPO_LED_200 0B00000000 // Mantener los LED encendidos 200 µs (ancho del pulso = 200 µs)
73 #define MAX30100_TIEMPO_LED_400 0B00000001 // Mantener los LED encendidos 400 µs
74 #define MAX30100_TIEMPO_LED_800 0B00000010 // Mantener los LED encendidos 800 µs
75 #define MAX30100_TIEMPO_LED_1600 0B00000011 // Mantener los LED encendidos 1600 µs (ancho del pulso = 1600 µs)
76
77
78 // Configuración de la corriente de los LED MAX30100_CONFIGURACION_LED
79
80 #define MAX30100_ROJO_0 0B00000000 // Corriente del LED rojo = 0 µA
81 #define MAX30100_ROJO_4400 0B00000001 // Corriente del LED rojo = 4400 µA
82 #define MAX30100_ROJO_7600 0B00100000 // Corriente del LED rojo = 7600 µA
83 #define MAX30100_ROJO_11000 0B00110000 // Corriente del LED rojo = 11000 µA
84 #define MAX30100_ROJO_14200 0B01000000 // Corriente del LED rojo = 14200 µA
85 #define MAX30100_ROJO_17400 0B01010000 // Corriente del LED rojo = 17400 µA
86 #define MAX30100_ROJO_20800 0B01100000 // Corriente del LED rojo = 20800 µA
87 #define MAX30100_ROJO_24000 0B01110000 // Corriente del LED rojo = 24000 µA
88 #define MAX30100_ROJO_27100 0B10000000 // Corriente del LED rojo = 27100 µA
89 #define MAX30100_ROJO_30600 0B10010000 // Corriente del LED rojo = 30600 µA
90 #define MAX30100_ROJO_33800 0B10100000 // Corriente del LED rojo = 33800 µA
91 #define MAX30100_ROJO_37000 0B10110000 // Corriente del LED rojo = 37000 µA
92 #define MAX30100_ROJO_40200 0B11000000 // Corriente del LED rojo = 40200 µA
93 #define MAX30100_ROJO_43600 0B11010000 // Corriente del LED rojo = 43600 µA
#define MAX30100_ROJO_46800 0B11100000 // Corriente del LED rojo = 46800 µA
#define MAX30100_ROJO_50000 0B11110000 // Corriente del LED rojo = 50000 µA

#define MAX30100_INFRARROJO_0 0B00000000 // Corriente del LED infrarrojo = 0 µA


#define MAX30100_INFRARROJO_4400 0B00000001 // Corriente del LED infrarrojo = 4400 µA
#define MAX30100_INFRARROJO_7600 0B00000010 // Corriente del LED infrarrojo = 7600 µA
#define MAX30100_INFRARROJO_11000 0B00000011 // Corriente del LED infrarrojo = 11000 µA
#define MAX30100_INFRARROJO_14200 0B00000100 // Corriente del LED infrarrojo = 14200 µA
#define MAX30100_INFRARROJO_17400 0B00000101 // Corriente del LED infrarrojo = 17400 µA
#define MAX30100_INFRARROJO_20800 0B00000110 // Corriente del LED infrarrojo = 20800 µA
#define MAX30100_INFRARROJO_24000 0B00000111 // Corriente del LED infrarrojo = 24000 µA
#define MAX30100_INFRARROJO_27100 0B00001000 // Corriente del LED infrarrojo = 27100 µA
#define MAX30100_INFRARROJO_30600 0B00001001 // Corriente del LED infrarrojo = 30600 µA
#define MAX30100_INFRARROJO_33800 0B00001010 // Corriente del LED infrarrojo = 33800 µA
#define MAX30100_INFRARROJO_37000 0B00001011 // Corriente del LED infrarrojo = 37000 µA
#define MAX30100_INFRARROJO_40200 0B00001100 // Corriente del LED infrarrojo = 40200 µA
#define MAX30100_INFRARROJO_43600 0B00001101 // Corriente del LED infrarrojo = 43600 µA
#define MAX30100_INFRARROJO_46800 0B00001110 // Corriente del LED infrarrojo = 46800 µA
#define MAX30100_INFRARROJO_50000 0B00001111 // Corriente del LED infrarrojo = 50000 µA
Como el proceso de lectura de datos y envío de información para la configuración
sigue siempre el mismo protocolo, se pueden usar desde funciones para simplificar su
uso y no distraer en la explicación del proceso de explotación del MAX30100.
1 void enviar_i2c(byte direccion_i2c,byte registro,byte valor)
2 {
3 Wire.beginTransmission(direccion_i2c);
4 Wire.write(registro);
5 Wire.write(valor);
6 Wire.endTransmission();
7 }
8
9 boolean recibir_i2c(byte direccion_i2c,byte registro,byte cantidad,byte *destino)
10 {
11 boolean lectura_correcta=false;
12 const unsigned long TIMEOUT=50;
13 Wire.beginTransmission(direccion_i2c);
14 Wire.write(registro);
15 Wire.endTransmission(false);
16 Wire.requestFrom(direccion_i2c,cantidad);
17 unsigned long cronometro=millis();
18 while(Wire.available()<cantidad&&(unsigned long)(millis()-cronometro)<TIMEOUT);
19 if(Wire.available()==cantidad)
20 {
21 lectura_correcta=true;
22 for(byte contador=0;contador<cantidad;destino[contador]=Wire.read(),contador++);
23 }
24 return lectura_correcta;
25 }

Lectura de la temperatura interna del MAX30100


Como se ha dicho, la temperatura se utiliza para corregir su influencia en la medida
obtenida al iluminar con el LED infrarrojo. Lo habitual es leer la temperatura a
intervalos fijos y promediar algunas lecturas para obtener el valor que se utiliza como
referencia. Una frecuencia razonable puede ser tomar una medida cada segundo y
promediar las de 10 segundos. Abajo puede verse el código que ilustra este proceso
y descargar el ejemplo de lectura de la temperatura del oxímetro de pulso MAX30100
desde Arduino.
1 #include <Wire.h>
2 #include "MAX30100.h"
3 #include "enviar_recibir_i2c.h"
4
5 #define INTERVALO_LECTURAS 1000
6 #define CANTIDAD_LECTURAS_MEDIA 10
7 #define PIN_INTERRUPCION 7 /* Usando una placa Arduino Leonardo */
8
9 byte max30100_buffer[4]; /* Una operación de lectura del MAX30100 devuelve como máximo 4 bytes (dos enteros de 16 bits) */
10 bool interrupcion_activa=false;
11 float temperatura_actual;
12 float temperatura_media;
13 float temperatura_total=0;
14 unsigned int cantidad_lecturas=1; /* Empieza en la lectura número 1 e incrementa después de calcular la media */
15 unsigned long cronometro;
16
17 void setup()
18 {
19 pinMode(PIN_INTERRUPCION,INPUT_PULLUP);
20 Wire.begin();
21 enviar_i2c(MAX30100_DIRECCION,MAX30100_INTERRUPCION_ACTIVA,MAX30100_TEMPERATURA_LISTA);
22 Serial.begin(9600);
23 while(!Serial); /* Usando una placa Arduino Leonardo */
24 cronometro=millis();
25 }
26
27 void loop()
28 {
29 if((unsigned long)(millis()-cronometro)>INTERVALO_LECTURAS)
30 {
31 cronometro=millis();
32 attachInterrupt(digitalPinToInterrupt(PIN_INTERRUPCION),leer_temperatura,LOW);
33 enviar_i2c(MAX30100_DIRECCION,MAX30100_CONFIGURACION_MODO,MAX30100_TEMPERATURA_ACTIVAR);
34 }
35 if(interrupcion_activa)
36 {
37 interrupcion_activa=false;
38 recibir_i2c(MAX30100_DIRECCION,MAX30100_INTERRUPCION_ESTADO,1,max30100_buffer); /* Leer la interrupción
39 (además) la desactiva */
40 if
41 (
42 max30100_buffer[0]&MAX30100_TEMPERATURA_LISTA && /* La interrupción has sido producida por el fin de la ADC de la
43 temperatura */
44 recibir_i2c(MAX30100_DIRECCION,MAX30100_TEMPERATURA,2,max30100_buffer) /* Se han recibido correctamente los datos
45 del MAX30100 */
46 )
47 {
48 temperatura_actual=max30100_buffer[0]+(float)max30100_buffer[1]/16.0; /* Parte entera más dieciseisavos de grado */
49 temperatura_total+=temperatura_actual;
50 temperatura_media=temperatura_total/cantidad_lecturas++;
51 if(cantidad_lecturas>CANTIDAD_LECTURAS_MEDIA)
52 {
53 Serial.println("La temperatura del MAX30100 es de "+String(temperatura_media,DEC)+" °C");
54 cantidad_lecturas=1;
55 temperatura_total=0;
56 }
57 }
58 }
59 }
60
61 void leer_temperatura()
62 {
detachInterrupt(digitalPinToInterrupt(PIN_INTERRUPCION));
interrupcion_activa=true;
}
El iniciar el programa se configura el MAX30100 para que atienda a la interrupción
correspondiente a la temperatura pero no se activa dicha interrupción ni en
el MAX30100 ni en Arduino hasta que no transcurre el intervalo configurado (1000 ms).
Es importante recordar que la interrupción se desactivará automáticamente al concluir
la integración de la temperatura, por eso, el programa debe volver a activarla a cada
intervalo para que se produzca una nueva lectura.

También es importante, con respecto al comportamiento general de las interrupciones


del MAX30100, resaltar que la interrupción permanece activa (y el pin estará a nivel
bajo) hasta que se desactiven expresamente o se lea el registro que informa de la causa
de la interrupción, que en código se ha definido como MAX30100_INTERRUPCION_ESTADO y
que corresponde a la dirección 0x00.

Estimación del pulso con el MAX30100 leyendo la luz infrarroja


reflejada
La nomenclatura usada al hablar de dispositivos como el MAX30100, la propia hoja de
datos es un buen ejemplo, pueden inducir a error y hacer pensar que la información
que entrega es el valor de la frecuencia cardíaca o del ritmo cardíaco pero la medida
que ofrece MAX30100 es la intensidad de luz infrarroja y/o roja reflejada.

Utilizando el valor de esas intensidades a lo largo del tiempo es posible estimar tanto
el pulso (la frecuencia cardíaca) como en nivel de oxigenación de la sangre (la relación
entre la cantidad de hemoglobina y la cantidad de oxihemoglobina)

Ya se ha dicho que el MAX30100 almacena varias lecturas en una FIFO lo que permite
leer varios valores ya preparados en lugar de ir cargando cada uno que se integra. Si
el MCU no está muy ocupado con otras tareas también es posible leer cada valor
cuando el MAX30100 informe, generando una interrupción, de que el proceso de
la ADC ha terminado.

El siguiente ejemplo, además de ilustrar este estilo de explotación del oxímetro de


pulso MAX30100, permite obtener los datos para su análisis y la determinación del
método de estimación del pulso. Es relevante tener presente que el rango de valores
dependerá del color (transparencia) de la piel y el grosor de los tejidos de cada
individuo así como de la posición sobre el sensor de latido, por lo que será importante
realizar las primeras pruebas en un entorno conocido: el mismo sujeto y un sistema
(soporte) que fije la posición del dedo en el oxímetro de pulso.
1 #include <Wire.h>
2 #include "MAX30100.h"
3 #include "enviar_recibir_i2c.h"
4
5 #define PIN_INTERRUPCION 7 /* Usando una placa Arduino Leonardo */
6
7 byte max30100_buffer[4]; /* El pulso se expresa como un entero de 16 bits (dos bytes) así que se descartan los dos últimos */
8 unsigned int intensidad;
9 boolean intensidad_preparada=false;
1
0 void setup()
1 {
1 pinMode(PIN_INTERRUPCION,INPUT_PULLUP);
1 Wire.begin();
2 enviar_i2c(MAX30100_DIRECCION,MAX30100_INTERRUPCION_ACTIVA,MAX30100_PULSO_LISTO);
1 enviar_i2c(MAX30100_DIRECCION,MAX30100_CONFIGURACION_MODO,MAX30100_PULSO_ACTIVAR);
3 enviar_i2c(MAX30100_DIRECCION,MAX30100_CONFIGURACION_SPO2,MAX30100_ALTA_RESOLUCION|MAX30100_PULS
1 O_100_MPS|MAX30100_TIEMPO_LED_1600);
4 enviar_i2c(MAX30100_DIRECCION,MAX30100_CONFIGURACION_LED,MAX30100_ROJO_0|MAX30100_INFRARROJO_5000
1 0);
5 attachInterrupt(digitalPinToInterrupt(PIN_INTERRUPCION),avisar_lectura,LOW);
1 Serial.begin(9600);
6 while(!Serial);
1 }
7
1 void loop()
8 {
1 if(intensidad_preparada)
9 {
2 intensidad_preparada=false;
0 recibir_i2c(MAX30100_DIRECCION,MAX30100_INTERRUPCION_ESTADO,1,max30100_buffer); /* Leer la interrupción (además)
2 la desactiva */
1 if
2 (
2 max30100_buffer[0]&MAX30100_PULSO_LISTO && /* El pulso está listo */
2 recibir_i2c(MAX30100_DIRECCION,MAX30100_FIFO_DATOS,4,max30100_buffer) /* Se ha leído correctamente la intensidad */
3 )
2 {
4 intensidad=max30100_buffer[0];
2 intensidad=max30100_buffer[1]|intensidad<<8;
5 Serial.println(intensidad);
2 }
6 attachInterrupt(digitalPinToInterrupt(PIN_INTERRUPCION),avisar_lectura,LOW);
2 }
7 }
2
8 void avisar_lectura()
2 {
9 detachInterrupt(digitalPinToInterrupt(PIN_INTERRUPCION));
3 intensidad_preparada=true;
0 }
3
1
3
2
3
3
3
4
3
5
3
6
3
7
3
8
3
9
4
0
4
1
4
2
4
3
4
4
4
5
4
6
4
7
4
8
En el bloque de configuración del ejemplo de arriba se le indica al oxímetro de pulso
MAX30100 que ① genere una interrupción cuando el pulso esté listo (hay que recordar
que «pulso» hace referencia a la lectura de la luz infrarroja) estableciendo el valor del
registro en 0x01 que en el código se denomina MAX30100_INTERRUPCION_ACTIVA, ② lea
y calcule solamente la ADC de la luz infrarroja reflejada
(registro MAX30100_CONFIGURACION_MODO en 0x06 con el valor «pulso»), ③ utilice alta
resolución, 100 muestras por segundo y un pulso de 1600 µs (forzado por la resolución)
para lo que se establece el valor OR de los anteriores campos en el registro de la
dirección 0x09 que en el código se ha denominado MAX30100_CONFIGURACION_LED y
④ utilice una intensidad de 0&nsb;mA para el LED rojo (lo apague) y una intensidad
de 50 mA (la máxima) para el infrarrojo.

Al contrario de cómo se hacía en el caso de la lectura de la temperatura del MAX30100,


en este ejemplo la interrupción se asigna en la configuración, pero solamente es para
iniciar el proceso, ya que cada vez que se detecta una interrupción se libera (dentro de
la función llamada) se desactiva (al leer el registro) y se vuelve a asignar cuando se
termina de leer la intensidad de luz infrarroja. En este caso, aunque se repite la
asignación de la función como respuesta a la configuración, no se vuelve a activar la
interrupción del lectura ya que, a diferencia de la temperatura, el MAX30100 no la
cambia.

En el caso de optar por leer la FIFO cuando haya varias muestras disponibles sería
necesario primero calcular las que hay disponibles y luego realizar las lecturas de los
valores dentro un bucle.
1 recibir_i2c(MAX30100_DIRECCION,MAX30100_FIFO_PUNTERO_ESCRITURA,1,&cantidad_lecturas);
2 recibir_i2c(MAX30100_DIRECCION,MAX30100_FIFO_PUNTERO_LECTURA,1,max30100_buffer);
3 cantidad_lecturas-=(max30100_buffer[0]-1);
4 for(byte numero_lectura=0;numero_lectura<cantidad_lecturas;numero_lectura++)
Para construir el valor de la intensidad se asigna a un entero sin signo (16 bits) y se
rota 8 bits el primer valor que se carga en la lectura del bus I2C del MAX30100 (ya que
son los 8 bits más significativos) luego se le suman (OR) los 8 bits (los menos
significativos) de la segunda lectura.

Desde el siguiente enlace es posible descargar el ejemplo para Arduino de lectura


desde el MAX30100 de la intensidad de luz infrarroja reflejada para estimar la
frecuencia cardíaca (pulso) del código de arriba. Con este ejemplo, dependiendo del
contexto de medida del que se hablaba antes, puede obtenerse una lectura similar a la
de la captura de pantalla de abajo.

Analizando la gráfica resultante del código de ejemplo es posible, además de acotar el


rango de valores útiles, buscar un método de cálculo del pulso basado en la medida
del oxímetro de pulso MAX30100 de la luz infrarroja reflejada. A simple vista, lo más
sencillo parece medir el tiempo entre el inicio de las crestas o los valles del gráfico, por
desgracia, hay algunos inconvenientes que impiden simplemente detectar cuándo se
cambia la dirección de la curva.
En primer lugar, la banda de valores máximos y mínimos es relativamente estrecha
comparada con el rango de posibles valores. En segundo lugar, esta franja puede ir
variando a lo largo del tiempo (lo que, en parte, puede que se minimice cuando la lectura
se estabilice) dependiendo, por ejemplo, de lo cerca que se sitúe en el sensor el objeto
medido (un dedo, normalmente). Por último, los valores no siguen una distribución
uniforme (del valle a la cima) sino que existe, en el mejor caso, la inflexión que en
un ECG correspondería con el complejo QRS y en el peor, más o menos ruido en la
lectura, dependiendo también de la resolución y la frecuencia de muestreo.

La gráfica de la captura de pantalla de arriba muestra la salida del trazador serie


de Arduino con una frecuencia de muestreo más alta. La distribución de valores puede
llegar a ser tan ruidosa que oculte los quiebros de la curva (en este caso en concreto,
por la influencia de luz ambiental). Por otro lado, es interesante apreciar que siguen
claramente destacados los máximos y los mínimos que permitirían calcular
la frecuencia cardíaca (pulso).

Dos de las formas más económicas en recursos del MCU que pueden utilizarse
consisten en ① promediar los valores obtenidos hasta que solamente resulten
relevantes los máximos y los mínimos y tomar unos u otros y determinar el tiempo
transcurrido entre ellos y ② estimar cuándo los valores dejan de subir o de bajar,
admitiendo una tolerancia (máximo de duración de valores en una dirección cuando se
atiende a la contraria) y midiendo el tiempo entre ambos eventos. Si el rango de datos
está muy bien acotado el primer método parece más sencillo pero se intuye que el
segundo será más flexible si el rango de datos es variable (normalmente cuando el
contexto físico es más susceptible de cambiar) por lo que es la opción que se toma
como referencia.

Al código de ejemplo de lectura desde el MAX30100 de la intensidad de luz infrarroja


reflejada hay que añadir tres nuevas variables para ① almacenar la intensidad medida
en la lectura anterior (para saber si aumenta o disminuye), ② el momento en el que se
detectó la última inflexión (he elegido ascendente, pero podría hacerse al contrario) y
③ el momento en el que se encontró la última referencia válida de pulso.
11 unsigned int intensidad_anterior=0;
12 unsigned long ultimo_tiempo_ascendente=0;
13 unsigned long ultimo_tiempo_pulso=0;
Para estimar la frecuencia cardíaca se comparan las intensidades, si se ha cambiado
la dirección (crece y antes decrecía o al contrario) y el tiempo transcurrido supera la
tolerancia admisible, se ha encontrado un pico de la curva de los que determinan el
pulso.
43 if(intensidad_anterior<intensidad&&millis()-ultimo_tiempo_ascendente>TOLERANCIA_DESCENSO)
44 {
45 Serial.print("La frecuencia cardíaca estimada es de ");
46 Serial.print(60000.0/(millis()-ultimo_tiempo_pulso));
47 Serial.println(" pulsaciones por minuto");
48 ultimo_tiempo_pulso=millis();
49 }
50 ultimo_tiempo_ascendente=intensidad>intensidad_anterior?millis():ultimo_tiempo_ascendente;
51 intensidad_anterior=intensidad;
La tolerancia, definida en el programa como #define TOLERANCIA_DESCENSO 120, es el
valor de cambio de dirección que se admite sin considerar que se ha alcanzado la cresta
o el valle de la curva, depende del contexto, especialmente de la resolución y la
frecuencia de muestreo, pero también del sujeto al que se le mide el pulso y del
dispositivo físico sobre el que se ubica el sensor de latidos. Los primeros parámetros
se establecen en el programa, así que se pueden coordinar con la tolerancia pero los
primeros cambian en tiempo de ejecución, así que puede ser interesante considerar la
alternativa de modificarlos mientras se usa el dispositivo (por ejemplo, tomando el valor
de un potenciómetro).

Para hacer pruebas de los valores que se obtienen con esta estimación, se
puede descargar el código de ejemplo para Arduino de cálculo del pulso con la lectura
de luz infrarroja del MAX30100. En la captura de pantalla de abajo se puede ver un
ejemplo de resultado una vez estabilizada la medida.

Estimación de la SpO2 con el MAX30100 leyendo la luz roja e


infrarroja
Como se decía más arriba, la saturación de oxígeno en sangre se suele expresar como
un porcentaje que relaciona la presencia de oxihemoglobina con la de hemoglobina.
La SpO2 se puede calcular con la fórmula: SpO2=100×HbO2÷(HbO2+Hb)
Las medidas que el MAX30100 entrega corresponden a la luz roja e infrarroja reflejada
en la zona expuesta al sensor y emitida por los correspondientes LED. Como
la hemoglobina absorbe más radiación en la longitud de onda del rojo (~650 nm) y
la oxihemoglobina del infrarrojo (~950 nm), se puede inferir la presencia
de Hb y HbO2 por la luz de cada tipo medida.

Supuesto que la longitud de onda de la luz emitida por cada LED sea la correcta, un
inconveniente al calcular la SpO2 podría consistir en la intensidad diferencial emitida
por cada uno de los LED. Para compensarla, además de un desplazamiento y un
coeficiente estimados por ensayo, sería posible suministrar diferente corriente a cada
uno de los LED. Para ilustrar esta posibilidad y para poder visualizar la distribución de
ambas medidas, en el siguiente código de ejemplo se utilizan 30600 µA para el rojo y
50000 µA para el infrarrojo de forma que en la gráfica que se obtiene con el trazador
serie de Arduino puede superponer los valores medidos para ambos LED.
1 #include <Wire.h>
2 #include "MAX30100.h"
3 #include "enviar_recibir_i2c.h"
4
5 #define PIN_INTERRUPCION 7
6
7 byte max30100_buffer[4];
8 unsigned int intensidad_rojo;
9 unsigned int intensidad_infrarrojo;
1 boolean intensidad_preparada=false;
0
1 void setup()
1 {
1 pinMode(PIN_INTERRUPCION,INPUT_PULLUP);
2 Wire.begin();
1 enviar_i2c(MAX30100_DIRECCION,MAX30100_INTERRUPCION_ACTIVA,MAX30100_LED_LISTOS);
3 enviar_i2c(MAX30100_DIRECCION,MAX30100_CONFIGURACION_MODO,MAX30100_LED_ACTIVAR);
1 enviar_i2c(MAX30100_DIRECCION,MAX30100_CONFIGURACION_SPO2,MAX30100_ALTA_RESOLUCION|MAX30100_PULS
4 O_100_MPS|MAX30100_TIEMPO_LED_1600);
1 enviar_i2c(MAX30100_DIRECCION,MAX30100_CONFIGURACION_LED,MAX30100_ROJO_30600|MAX30100_INFRARROJO_
5 50000);
1 attachInterrupt(digitalPinToInterrupt(PIN_INTERRUPCION),avisar_lectura,LOW);
6 Serial.begin(9600);
1 while(!Serial);
7 }
1
8 void loop()
1 {
9 if(intensidad_preparada)
2 {
0 intensidad_preparada=false;
2 recibir_i2c(MAX30100_DIRECCION,MAX30100_INTERRUPCION_ESTADO,1,max30100_buffer);
1 if
2 (
2 max30100_buffer[0]&MAX30100_LED_LISTOS &&
2 recibir_i2c(MAX30100_DIRECCION,MAX30100_FIFO_DATOS,4,max30100_buffer)
3 )
2 {
4 intensidad_infrarrojo=max30100_buffer[0];
2 intensidad_infrarrojo=max30100_buffer[1]|intensidad_infrarrojo<<8;
5 intensidad_rojo=max30100_buffer[2];
2 intensidad_rojo=max30100_buffer[3]|intensidad_rojo<<8;
6 Serial.print(intensidad_infrarrojo);
2 Serial.print("\t");
7 Serial.println(intensidad_rojo);
2 }
8 attachInterrupt(digitalPinToInterrupt(PIN_INTERRUPCION),avisar_lectura,LOW);
2 }
9 }
3
0 void avisar_lectura()
3 {
1 detachInterrupt(digitalPinToInterrupt(PIN_INTERRUPCION));
3 intensidad_preparada=true;
2 }
3
3
3
4
3
5
3
6
3
7
3
8
3
9
4
0
4
1
4
2
4
3
4
4
4
5
4
6
4
7
4
8
4
9
5
0
5
1
5
2
5
3

Con pruebas de este tipo se pueden conocer mejor los márgenes en los que funciona
un de sensor, el MAX30100, en este caso. Por ejemplo, he observado que hasta no
llegar a los 7600 µA no se detecta de manera estable la luz reflejada y que la luz que
emite el LED rojo a más de 40200 µA satura el fotodiodo.
Suponiendo que la calibración de la relación entre la luz roja e infrarroja sea correcta
(hay que recordar que se detecta la luz reflejada, no la absorbida), al sustituir en el
anterior código de lectura de ambas intensidades la salida (los
códigos Serial.print de las líneas 41 a 43) del valor por:
1 Serial.println(100.0*(float)intensidad_rojo/((float)intensidad_rojo+(float)intensidad_infrarrojo));
se obtiene una lectura como la de la captura de pantalla de abajo, en la que puede
verse que el porcentaje está siempre muy cerca del 100 %, como cabría esperar. De
hecho, al tratarse de un dispositivo usado como wearable de salud (es decir, un equipo
no apto para su uso clínico), el límite inferior de lectura debería estar siempre por
encima del 95 % de saturación.

Вам также может понравиться