Libreria Arduino per il monitoraggio della frequenza cardiaca con pulsossimetro
Uno dei parametri monitorati nel mio progetto di gestione del sonno
E' il polso. per misurarlo Ho sviluppato un dispositivo basato sul comportamento dell'emoglobina e dell'ossiemoglobina rispetto a diverse lunghezze d'onda della luce. Fondamentalmente si tratta di misurare quanta luce di un certo tipo è in grado di attraversare o di essere riflessa in una zona del corpo ben irrigata. La frequenza con cui si verifica un ciclo completo di questo fenomeno permette di misurare la Pulso.Nella fase di progettazione e test del dispositivo di misurazione del polso Ho sviluppato alcuni piccoli programmi per aiutarmi a verificare che l'assemblaggio fosse corretto. Per prima cosa ho scritto il codice qui sotto, che prendeva di volta in volta i valori misurati (almeno ogni e al massimo ciascuno ) quando variavano un minimo tra uno e il precedente (il valore che corrisponde a ) e il monitorato da un computer con un'applicazione Python per poterli analizzare in seguito.
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 volta regolati i valori (iniziando con misurazioni molto dense) ho ottenuto una raccolta di valori dal file pulsossimetro nel tempo ho potuto rappresentare graficamente utilizzando un foglio di calcolo, Libre Office Calc de LibreOffice, specifica.
Con i dati raccolti, come rappresentati nell'immagine sopra, l'operazione successiva è stata quella di determinare se la densità di valori consentisse di calcolare in modo affidabile ma "economico" (non campionando più dei dati necessari) il valore del Pulso; Come si può vedere nel grafico sottostante, le misure adottate sembrano servire ad ottenere i risultati che è ragionevole aspettarsi.
.
Successivamente, con le informazioni provenienti dal campionamento dei dati, è stato necessario sviluppare un algoritmo in grado di misurare la frequenza del polso. Attenendoci al grafico che, per semplicità, si assume rappresenti un layout simile a quello Complesso QRS, la cosa più semplice sembra essere misurare i tempi tra le parti più prominenti, con valori più alti (che corrisponde alla zona qRs di depolarizzazione dei ventricoli), scartando la zona più piatta e "rumorosa", quindi più difficile misurare. La soluzione adottata, che corrisponde al codice di test riportato di seguito, funziona secondo la seguente procedura:
-
Rilevare rispettivamente l'area da misurare per occuparsi solo dei picchi di valore qRs e buttare via la valle. Per fare questo si potrebbero misurare valori superiori ad una certa costante, ma c’è il rischio che un individuo e/o le circostanze possano, seppur proporzionalmente, alzare o abbassare i valori. Per evitare ciò, un valore nell'area viene considerato maggiore di quello che supera il valore medio di un certo coefficiente. In questo modo la misura è sensibilmente autocalibrata e può essere ulteriormente aggiustata mettendo a punto il coefficiente, che nel mio caso ho ottenuto sperimentalmente durante le prove.
Scegliere i valori della zona discendente per la misurazione (Rs) del picco qRs, il più vicino possibile al massimo della curva. Per sapere che si sta abbandonando la zona ascendente è sufficiente verificare che un nuovo valore sia inferiore al precedente e verificare che il valore cercato non sia stato ancora trovato poiché, in generale, ci sono più valori nella zona discendente zona di qRs a seconda della densità di campionamento. Per cronometrare l'impulso viene memorizzato il valore dell'istante in cui è stato trovato il punto (i millisecondi restituiti da millis ()) e lo confronta con quello successivo.
Per garantire che il valore misurato sia maggiore nella zona discendente della curva più alta, viene utilizzata una variabile booleano ( in questo esempio e nella libreria) che si attiva quando si entra nella zona ascendente della curva maggiore e si disattiva una volta trovato il primo valore discendente che è quello temporizzato.
Poiché si è soliti rappresentare la durata dell'impulso in battiti al minuto (ppm), il valore del tempo tra gli impulsi ottenuto viene corretto calcolando dividendo il tempo totale della rappresentazione (un minuto, 60000 millisecondi) per l'intervallo ottenuto da sottraendo i millisecondi attuali (del valore corrente) tra quelli precedentemente cronometrati.
Per evitare false misurazioni (come ad esempio il dispositivo che misura nel vuoto), si verifica che il risultato sia compreso tra i valori massimo e minimo prima di darlo per scontato. Sebbene si consideri mediamente che un valore normale per un adulto sano a riposo sia compreso tra 60 e 100 ppm, di seguito sono riportati i valori ammissibili, è facile trovare 40 ppm in un atleta a riposo, fino a 200 ppm durante esercizio fisico intenso e superiore a 100 ppm in adulti sedentari in stati di eccitazione, proprio un fattore interessante per il progetto di gestione del sonno il che mi porta a sviluppare questo dispositivo di misurazione del polso. Per questo motivo è opportuno allentare molto questi valori affinché non si perdano gli estremi, che potrebbero appunto mostrare aspetti rilevanti.
Il nuovo valore medio viene calcolato diminuendo la rilevanza della media attuale in base al numero di valori campionati e viene aggiunto l'ultimo valore, anch'esso ponderato con un coefficiente che lo riduce tanto più quanti sono i valori misurati finora .
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;
}
}
|
Infine, utilizzando l'algoritmo descritto prima, ho sviluppato la libreria per calcolare l'impulso rilevando la presenza del emoglobina o alla ossiemoglobina (a seconda della lunghezza d'onda della luce utilizzata) dal codice seguente.
La libreria prevede che la funzione di campionamento venga chiamata periodicamente Pulso nel mio progetto di gestione del sonno
. In ogni caso, dalle prove che ho fatto, non sembra essere necessario; dal dispositivo o dal comportamento del per calcolare la pulsazione, consultabile con la funzione o con la funzione il polso medio. Oltre ad essere una risorsa limitata, ho escluso di ricorrere alle interruzioni perché non avevo bisogno di valori immediati ma piuttosto sostenuti nel tempo per monitorare l'andamento Pulso, il campionamento ad una certa frequenza offre informazioni sufficienti e non si ottiene molto di più (rilevante) aumentandola, né è possibile diminuirla molto senza perdere dati rilevanti per il calcolo; nelle prime versioni del codice per monitorare la lettura del file pulsossimetro Scoprii che non era necessario attenersi ad un tempo massimo di misurazione poiché, se si consideravano correttamente le variazioni dei valori successivi, era molto vicino al minimo.
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;
}
|
Il seguente programma di esempio mostra come utilizzare la libreria precedente per misurare il file Pulso con uno pulsossimetro. Oltre a istanziare la classe monitoraggio del livello di ossiemoglobina/emoglobina e con una periodicità minore il valore del Pulso calcolato e medio.
Per garantire che le misurazioni siano rilevanti, viene programmata un'attesa prima di visualizzare qualsiasi valore. Poiché il valore potrebbe essere errato (ad esempio se l'utente rimuove il dispositivo), i valori vengono visualizzati solo se rientrano nell'intervallo di quelli considerati validi.
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);
}
}
}
|
Invia commento