Bibliothèque Arduino pour la surveillance de la fréquence cardiaque avec oxymètre de pouls
Un des paramètres surveillés dans mon projet de gestion du sommeil
C'est le pouls. pour le mesurer J'ai développé un appareil basé sur le comportement de l'hémoglobine et de l'oxyhémoglobine face à différentes longueurs d'onde de la lumière.. Fondamentalement, il s'agit de mesurer la quantité de lumière d'un certain type qui peut traverser ou se refléter dans une zone bien irriguée du corps. La fréquence à laquelle se produit un cycle complet de ce phénomène permet de mesurer pulso.Dans la phase de conception et de test du appareil de mesure du pouls J'ai développé quelques petits programmes pour m'aider à vérifier que l'assemblage était correct. Tout d'abord, j'ai écrit le code ci-dessous, qui prenait les valeurs mesurées de temps en temps (au moins tous les et au plus chacun ) lorsqu'ils variaient un minimum entre l'un et le précédent (la valeur qui correspond à ) et les surveillé depuis un ordinateur avec une application Python pour pouvoir les analyser plus tard.
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));
}
}
}
|
Une fois les valeurs ajustées (en commençant par des mesures très denses) j'ai obtenu une collection de valeurs du oxymètre de pouls au fil du temps, j'ai pu tracer un graphique à l'aide d'une feuille de calcul, Calcul de LibreOffice de LibreOffice, spécifique.
Avec les données collectées, comme représenté dans l'image ci-dessus, l'opération suivante consistait à déterminer si la densité de valeurs permettait de calculer de manière fiable mais "économique" (en n'échantillonnant pas plus que les données nécessaires) la valeur du pulso; Comme le montre le graphique ci-dessous, les mesures prises semblent permettre d'obtenir les résultats qu'il est raisonnable d'attendre.
.
Ensuite, avec les informations issues de l’échantillonnage des données, il a été nécessaire de développer un algorithme permettant de mesurer la fréquence du pouls. S'en tenir au graphique qui, par souci de simplicité, est supposé représenter une disposition similaire à celle du Complexe QRS, le plus simple semble être de mesurer les temps entre les parties les plus saillantes, avec des valeurs plus élevées (qui correspondent à la zone qRs de dépolarisation des ventricules) en écartant la zone la plus plate et la plus "bruyante", donc plus difficile à mesure. La solution adoptée, qui correspond au code de test ci-dessous, fonctionne selon la procédure suivante :
-
Détectez la zone mesurée dans chaque cas pour ne prêter attention qu'aux pics de valeur QRS et jette la vallée. Pour ce faire, des valeurs supérieures à une certaine constante pourraient être mesurées, mais il existe un risque qu'un individu et/ou des circonstances puissent, bien que proportionnellement, augmenter ou diminuer les valeurs. Pour éviter cela, une valeur dans la zone est considérée comme supérieure à celle qui dépasse la valeur moyenne d'un certain coefficient. De cette façon, la mesure est auto-calibrée avec sensibilité et peut être encore ajustée en ajustant le coefficient, ce que j'ai obtenu expérimentalement dans mon cas lors des tests.
Choisissez les valeurs de la zone descendante pour la mesure (Rs) du sommet QRS, le plus près possible du maximum de la courbe. Pour savoir que la zone ascendante est en train d'être abandonnée, il suffit de vérifier qu'une nouvelle valeur est inférieure à la précédente et de vérifier que la valeur recherchée n'a pas encore été trouvée puisque, en général, il y a plusieurs valeurs dans la zone descendante. zone de QRS en fonction de la densité d'échantillonnage. Pour chronométrer l'impulsion, la valeur de l'instant auquel le point a été trouvé est stockée (les millisecondes renvoyées par millis ()) et le compare avec le suivant.
Pour que la valeur mesurée soit la plus grande dans la zone descendante de la courbe la plus haute, une variable est utilisée booléen ( dans cet exemple et dans la bibliothèque) qui s'active en entrant dans la zone ascendante de la courbe majeure et se désactive une fois trouvée la première valeur descendante, qui est celle temporisée.
Comme il est habituel de représenter la durée de l'impulsion en battements par minute (ppm), la valeur du temps entre les impulsions obtenue est corrigée en calculant en divisant la durée totale de la représentation (une minute, 60000 XNUMX millisecondes) par l'intervalle obtenu par en soustrayant les millisecondes actuelles (de la valeur actuelle) parmi celles précédemment chronométrées.
Pour éviter les fausses mesures (comme l'appareil mesurant dans le vide par exemple), on vérifie que le résultat est compris entre les valeurs maximales et minimales avant de le prendre pour acquis. Bien qu'on considère en moyenne qu'une valeur normale pour un adulte en bonne santé au repos se situe entre 60 et 100 ppm, il existe des valeurs admissibles ci-dessous, il est facile de trouver 40 ppm chez un sportif au repos, jusqu'à 200 ppm pendant exercice intense et plus de 100 ppm chez des adultes sédentaires en état d'excitation, justement un facteur intéressant pour le projet de gestion du sommeil ce qui m'amène à développer ceci appareil de mesure du pouls. Pour cette raison, il est conseillé d'assouplir considérablement ces valeurs afin de ne pas perdre les extrêmes, qui pourraient précisément montrer des aspects pertinents.
La nouvelle valeur moyenne est calculée en diminuant la pertinence de la moyenne actuelle en fonction du nombre de valeurs échantillonnées et la dernière valeur est ajoutée, également pondérée avec un coefficient qui la réduit d'autant plus qu'il y a de valeurs mesurées jusqu'à présent. .
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;
}
}
|
Enfin, en utilisant l'algorithme décrit précédemment, j'ai développé la bibliothèque pour calculer le pouls en détectant la présence du l'hémoglobine ou la oxyhémoglobine (en fonction de la longueur d'onde de la lumière utilisée) à partir du code ci-dessous.
La bibliothèque s'attend à ce que la fonction d'échantillonnage soit appelée périodiquement pulso dans mon projet de gestion du sommeil
. En tout cas, d'après les tests que j'ai effectués, cela ne semble pas nécessaire ; soit par l'appareil, soit par le comportement du pour calculer le pouls, consultable avec la fonction ou avec la fonction le pouls moyen. En plus d'être une ressource limitée, j'ai exclu le recours aux interruptions car je n'avais pas besoin de valeurs immédiates mais plutôt soutenues dans le temps pour suivre l'évolution de la situation. pulso, l'échantillonnage à une certaine fréquence offre suffisamment d'informations et on n'obtient pas beaucoup plus (pertinent) en l'augmentant, et il n'est pas non plus possible de la diminuer beaucoup sans perdre de données pertinentes pour le calcul ; dans les premières versions du code pour surveiller la lecture du oxymètre de pouls J'ai découvert qu'il n'était pas nécessaire de s'en tenir à un temps de mesure maximum puisque, si l'on considérait correctement les variations de valeurs successives, il était très proche du minimum.
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;
}
|
L'exemple de programme suivant montre comment utiliser la bibliothèque précédente pour mesurer le pulso avec oxymètre de pouls. En plus d'instancier la classe surveillance du niveau de oxyhémoglobine/l'hémoglobine et avec une périodicité plus petite la valeur du pulso calculé et moyen.
Pour garantir la pertinence des mesures, une attente est programmée avant d'afficher une valeur. Comme la valeur peut être incorrecte (par exemple si l'utilisateur retire l'appareil), les valeurs ne sont affichées que si elles se situent dans la plage de celles considérées comme valides.
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);
}
}
}
|
Poster un commentaire