Biblioteca Arduino para monitoramento de frequência cardíaca com oxímetro de pulso
Um dos parâmetros monitorados no meu projeto de gerenciamento do sono
É o pulso. para medi-lo Desenvolvi um dispositivo baseado no comportamento da hemoglobina e da oxiemoglobina contra diferentes comprimentos de onda de luz. Basicamente, trata-se de medir quanta luz de um determinado tipo é capaz de passar ou ser refletida em uma área bem irrigada do corpo. A frequência com que ocorre um ciclo completo deste fenômeno permite medir a pulso.Na fase de projeto e testes do dispositivo de medição de pulso Desenvolvi alguns pequenos programas para me ajudar a verificar se a montagem estava correta. Primeiramente escrevi o código abaixo, que pegava os valores medidos de tempos em tempos (pelo menos a cada e no máximo cada ) quando variaram um mínimo entre um e o anterior (o valor que corresponde a ) e os monitorado a partir de um computador com um aplicativo Python para poder analisá-los mais tarde.
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));
}
}
}
|
Depois que os valores foram ajustados (começando com medições muito densas), obtive uma coleção de valores do oxímetro de pulso ao longo do tempo que eu poderia representar graficamente usando uma planilha, LibreOffice CalcName de LibreOffice, em concreto.
Com os dados recolhidos, conforme representado na imagem acima, a próxima operação foi determinar se a densidade de valores nos permitia calcular de forma fiável mas "económica" (não amostrando mais do que os dados necessários) o valor do pulso; Como se pode verificar no gráfico abaixo, as medidas tomadas pareceram servir para obter os resultados que são razoáveis de esperar.
.
A seguir, com as informações da amostragem dos dados, foi necessário desenvolver um algoritmo que medisse a frequência do pulso. Atendendo ao gráfico que, para simplificar, assume-se que ele representa um layout semelhante ao Complexo QRS, o mais simples parece ser medir os tempos entre as partes mais proeminentes, com valores mais elevados (que corresponde à zona qRs de despolarização dos ventrículos), descartando a zona mais plana e "ruidosa", portanto mais difícil medir. A solução adotada, que corresponde ao código de teste abaixo, funciona conforme o seguinte procedimento:
-
Detectar a área que está sendo medida em cada caso para atender apenas aos picos de valor qRs e jogue fora o vale. Para isso, poderiam ser medidos valores superiores a uma determinada constante, mas existe o risco de que um indivíduo e/ou circunstâncias possam, ainda que proporcionalmente, aumentar ou diminuir os valores. Para evitar isso, considera-se um valor na área maior que aquele que ultrapassa o valor médio por um determinado coeficiente. Desta forma, a medição é autocalibrada com sensibilidade e pode ser ajustada ainda mais através do ajuste fino do coeficiente, que no meu caso consegui experimentalmente durante os testes.
Escolha os valores da zona descendente para a medição (Rs) do pico qRs, o mais próximo possível do máximo da curva. Para saber que a zona ascendente está sendo abandonada basta verificar se um novo valor é menor que o anterior e verificar se o valor pesquisado ainda não foi encontrado pois, em geral, existem vários valores na zona descendente zona de qRs dependendo da densidade de amostragem. Para cronometrar o pulso, é armazenado o valor do instante em que o ponto foi encontrado (os milissegundos retornados por millis ()) e compara-o com o próximo.
Para garantir que o valor medido seja o maior na zona descendente da curva mais alta, é utilizada uma variável boleano ( neste exemplo e na biblioteca) que é ativado ao entrar na zona ascendente da curva maior e desativado quando é encontrado o primeiro valor descendente, que é o cronometrado.
Como é usual representar a duração do pulso em batimentos por minuto (ppm), o valor do tempo entre os pulsos obtido é corrigido calculando-se a divisão do tempo total de representação (um minuto, 60000 milissegundos) pelo intervalo obtido por subtraindo os milissegundos atuais (do valor atual) entre aqueles cronometrados anteriormente.
Para evitar medições falsas (como o aparelho medindo no vácuo, por exemplo), verifica-se se o resultado está entre os valores máximo e mínimo antes de tomá-lo como certo. Embora se considere como média que um valor normal para um adulto saudável em repouso esteja entre 60 e 100 ppm, abaixo existem valores admissíveis, é fácil encontrar 40 ppm num atleta em repouso, até 200 ppm durante exercício intenso e mais de 100 ppm em adultos sedentários em estados de excitação, justamente um fator interessante para o projeto de gerenciamento do sono o que me leva a desenvolver este dispositivo de medição de pulso. Por isso, é aconselhável flexibilizar bastante esses valores para que não se percam os extremos, que poderiam justamente mostrar aspectos relevantes.
O novo valor médio é calculado diminuindo a relevância da média atual com base no número de valores amostrados e o último valor é somado, também ponderado com um coeficiente que o reduz tanto quanto mais valores foram medidos até agora .
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 fim, utilizando o algoritmo descrito anteriormente, desenvolvi a biblioteca para calcular o pulso detectando a presença do hemoglobina ou o oxihemoglobina (dependendo do comprimento de onda da luz usada) do código abaixo.
A biblioteca espera que a função de amostragem seja chamada periodicamente pulso no meu projeto de gerenciamento do sono
. Em todo caso, pelos testes que fiz, não parece necessário; seja pelo dispositivo ou pelo comportamento do para calcular o pulso, que pode ser consultado com a função ou com a função o pulso médio. Além de ser um recurso limitado, descartei o uso de interrupções porque não precisava de valores imediatos, mas sim sustentados ao longo do tempo para monitorar o pulso, a amostragem em uma determinada frequência oferece informações suficientes e não se obtém muito mais (relevante) aumentando-a, nem é possível diminuí-la muito sem perder dados relevantes para o cálculo; nas primeiras versões do código para monitorar a leitura do oxímetro de pulso Descobri que não era necessário limitar-se a um tempo máximo de medição, pois, se fossem consideradas corretamente as variações dos valores sucessivos, ele estava muito próximo do 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;
}
|
O programa de exemplo a seguir mostra como usar a biblioteca anterior para medir o pulso com um oxímetro de pulso. Além de instanciar a classe monitoramento do nível de oxihemoglobina/hemoglobina e com menor periodicidade o valor do pulso calculado e médio.
Para garantir que as medições sejam relevantes, é programada uma espera antes de exibir qualquer valor. Como o valor pode estar incorreto (por exemplo se o usuário retirar o dispositivo), os valores só são mostrados se estiverem dentro da faixa daqueles 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);
}
}
}
|
Postar Comentário