Βιβλιοθήκη Arduino για παρακολούθηση καρδιακών παλμών με παλμικό οξύμετρο
Μία από τις παραμέτρους που παρακολουθούνται στο έργο διαχείρισης ύπνου μου
Είναι ο παλμός. να το μετρήσω Ανέπτυξα μια συσκευή βασισμένη στη συμπεριφορά της αιμοσφαιρίνης και της οξυαιμοσφαιρίνης σε διαφορετικά μήκη κύματος φωτός. Ουσιαστικά πρόκειται για τη μέτρηση του πόσο φως ενός συγκεκριμένου τύπου μπορεί να περάσει ή να αντανακλάται σε μια καλά ποτισμένη περιοχή του σώματος. Η συχνότητα με την οποία εμφανίζεται ένας πλήρης κύκλος αυτού του φαινομένου επιτρέπει τη μέτρηση του παλμός.Στη φάση σχεδιασμού και δοκιμής του συσκευή μέτρησης παλμών Ανέπτυξα μερικά μικρά προγράμματα για να με βοηθήσουν να επαληθεύσω ότι η συναρμολόγηση ήταν σωστή. Πρώτα έγραψα τον παρακάτω κώδικα, ο οποίος έπαιρνε τις μετρούμενες τιμές από καιρό σε καιρό (τουλάχιστον κάθε και το πολύ το καθένα ) όταν διέφεραν ένα ελάχιστο μεταξύ του ενός και του προηγούμενου (την τιμή που αντιστοιχεί ) και το παρακολουθείται από υπολογιστή με εφαρμογή Python για να μπορέσουμε να τα αναλύσουμε αργότερα.
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));
}
}
}
|
Μόλις οι τιμές προσαρμόστηκαν (ξεκινώντας με πολύ πυκνές μετρήσεις) έλαβα μια συλλογή τιμών από το παλμικό οξύμετρο με την πάροδο του χρόνου μπορούσα να γράψω γραφικά χρησιμοποιώντας ένα υπολογιστικό φύλλο, LibreOffice Υπολογισμός de LibreOffice, συγκεκριμένος.
Με τα δεδομένα που συλλέχθηκαν, όπως απεικονίζονται στην παραπάνω εικόνα, η επόμενη ενέργεια ήταν να προσδιοριστεί εάν η πυκνότητα των τιμών μας επέτρεπε να υπολογίσουμε με αξιόπιστο αλλά «οικονομικό» τρόπο (χωρίς δειγματοληψία περισσότερα από τα απαραίτητα δεδομένα) την τιμή του παλμός; Όπως φαίνεται στο παρακάτω γράφημα, τα μέτρα που ελήφθησαν φάνηκε να επιτυγχάνουν τα αποτελέσματα που είναι λογικά αναμενόμενα.
.
Στη συνέχεια, με τις πληροφορίες από τη δειγματοληψία δεδομένων, ήταν απαραίτητο να αναπτυχθεί ένας αλγόριθμος που θα μετρούσε τον ρυθμό παλμού. Επιμένοντας στο γράφημα ότι, για λόγους απλότητας, θεωρείται ότι αντιπροσωπεύει μια διάταξη παρόμοια με το Σύμπλεγμα QRS, το πιο απλό πράγμα φαίνεται να είναι να μετρήσουμε τους χρόνους μεταξύ των πιο εμφανών τμημάτων, με υψηλότερες τιμές (που αντιστοιχεί στη ζώνη qRs εκπόλωσης των κοιλιών), απορρίπτοντας την πιο επίπεδη και πιο «θορυβώδη» ζώνη, που είναι επομένως πιο δύσκολη να μετρήσετε. Η λύση που υιοθετήθηκε, η οποία αντιστοιχεί στον παρακάτω κώδικα δοκιμής, λειτουργεί σύμφωνα με την ακόλουθη διαδικασία:
-
Εντοπίστε την περιοχή που μετράται σε κάθε περίπτωση για να παρακολουθείτε μόνο τις κορυφές τιμών qRs και πετάξτε την κοιλάδα. Για να γίνει αυτό, θα μπορούσαν να μετρηθούν τιμές υψηλότερες από μια ορισμένη σταθερά, αλλά υπάρχει ο κίνδυνος ένα άτομο ή/και περιστάσεις, αν και αναλογικά, να αυξήσουν ή να μειώσουν τις τιμές. Για να αποφευχθεί αυτό, μια τιμή στην περιοχή θεωρείται μεγαλύτερη από αυτή που υπερβαίνει τη μέση τιμή κατά ένα συγκεκριμένο συντελεστή. Με αυτόν τον τρόπο, η μέτρηση αυτο-βαθμονομείται με ευαισθησία και θα μπορούσε να προσαρμοστεί ακόμη περισσότερο με μικρορύθμιση του συντελεστή, τον οποίο στην περίπτωσή μου έχω πετύχει πειραματικά κατά τη διάρκεια των δοκιμών.
Επιλέξτε τις τιμές της φθίνουσας ζώνης για τη μέτρηση (Rs) της κορυφής qRs, όσο το δυνατόν πιο κοντά στο μέγιστο της καμπύλης. Για να γνωρίζετε ότι η αύξουσα ζώνη εγκαταλείπεται, αρκεί να επαληθεύσετε ότι μια νέα τιμή είναι μικρότερη από την προηγούμενη και να επαληθεύσετε ότι η τιμή που αναζητήσατε δεν έχει βρεθεί ακόμη, καθώς, γενικά, υπάρχουν πολλές τιμές στην φθίνουσα ζώνη του qRs ανάλογα με την πυκνότητα δειγματοληψίας. Για να χρονομετρηθεί ο παλμός, αποθηκεύεται η τιμή της στιγμής στην οποία βρέθηκε το σημείο (τα χιλιοστά του δευτερολέπτου που επιστρέφονται από χιλιοστά ()) και το συγκρίνει με το επόμενο.
Για να διασφαλιστεί ότι η τιμή που μετρήθηκε είναι η μεγαλύτερη στην φθίνουσα ζώνη της υψηλότερης καμπύλης, χρησιμοποιείται μια μεταβλητή boolean ( σε αυτό το παράδειγμα και στη βιβλιοθήκη) που ενεργοποιείται κατά την είσοδο στην αύξουσα ζώνη της κύριας καμπύλης και απενεργοποιείται μόλις βρεθεί η πρώτη φθίνουσα τιμή, που είναι η χρονομετρημένη.
Όπως είναι σύνηθες να αντιπροσωπεύεται η διάρκεια του παλμού ως παλμοί ανά λεπτό (ppm), η τιμή του χρόνου μεταξύ των παλμών που λαμβάνονται διορθώνεται με υπολογισμό διαιρώντας τον συνολικό χρόνο της αναπαράστασης (ένα λεπτό, 60000 χιλιοστά του δευτερολέπτου) με το διάστημα που προκύπτει από αφαιρώντας τα τρέχοντα χιλιοστά του δευτερολέπτου (της τρέχουσας τιμής) από αυτά που είχαν προηγουμένως χρονομετρηθεί.
Για την αποφυγή λανθασμένων μετρήσεων (όπως η συσκευή που μετράει σε κενό, για παράδειγμα), επαληθεύεται ότι το αποτέλεσμα είναι μεταξύ μέγιστων και ελάχιστων τιμών πριν θεωρηθεί δεδομένο. Αν και θεωρείται ως μέσος όρος ότι μια κανονική τιμή για έναν υγιή ενήλικα σε κατάσταση ηρεμίας είναι μεταξύ 60 και 100 ppm, υπάρχουν αποδεκτές τιμές παρακάτω, είναι εύκολο να βρεθούν 40 ppm σε έναν αθλητή σε κατάσταση ηρεμίας, έως και 200 ppm κατά τη διάρκεια έντονη άσκηση και περισσότερα 100 ppm σε καθιστικούς ενήλικες σε καταστάσεις ενθουσιασμού, ακριβώς ένας ενδιαφέρον παράγοντας για το έργο διαχείρισης ύπνου που με οδηγεί να το αναπτύξω αυτό συσκευή μέτρησης παλμών. Για το λόγο αυτό, καλό είναι να χαλαρώσετε πολύ αυτές τις τιμές για να μην χαθούν τα άκρα, τα οποία θα μπορούσαν να δείξουν με ακρίβεια σχετικές πτυχές.
Η νέα μέση τιμή υπολογίζεται μειώνοντας τη συνάφεια του τρέχοντος μέσου όρου με βάση τον αριθμό των τιμών που δειγματοληπτήθηκαν και προστίθεται η τελευταία τιμή, επίσης σταθμισμένη με έναν συντελεστή που τη μειώνει περισσότερο όσο περισσότερες τιμές έχουν μετρηθεί μέχρι τώρα .
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;
}
}
|
Τέλος, χρησιμοποιώντας τον αλγόριθμο που περιγράφηκε προηγουμένως, ανέπτυξα τη βιβλιοθήκη για τον υπολογισμό του παλμού ανιχνεύοντας την παρουσία του αιμοσφαιρίνη o οξυαιμοσφαιρίνη (ανάλογα με το μήκος κύματος του φωτός που χρησιμοποιείται) από τον παρακάτω κώδικα.
Η βιβλιοθήκη αναμένει ότι η συνάρτηση δειγματοληψίας θα καλείται περιοδικά παλμός στο έργο μου διαχείρισης ύπνου
. Σε κάθε περίπτωση από τα τεστ που έχω κάνει δεν φαίνεται να χρειάζεται? είτε από τη συσκευή είτε από τη συμπεριφορά του για τον υπολογισμό του παλμού, τον οποίο μπορείτε να συμβουλευτείτε με τη συνάρτηση ή με τη συνάρτηση ο μέσος παλμός. Εκτός από περιορισμένος πόρος, απέκλεισα τη χρήση διακοπών επειδή δεν χρειαζόμουν άμεσες τιμές αλλά μάλλον σταθερές με την πάροδο του χρόνου για να παρακολουθώ τις παλμός, η δειγματοληψία σε μια συγκεκριμένη συχνότητα προσφέρει αρκετές πληροφορίες και δεν λαμβάνονται πολύ περισσότερες (σχετικές) αυξάνοντάς τις, ούτε είναι δυνατόν να μειωθούν πολύ χωρίς να χαθούν τα σχετικά δεδομένα για τον υπολογισμό. σε πρώιμες εκδόσεις του κώδικα για την παρακολούθηση της ανάγνωσης του παλμικό οξύμετρο Ανακάλυψα ότι δεν ήταν απαραίτητο να επιμείνω σε έναν μέγιστο χρόνο μέτρησης, καθώς, αν ληφθούν σωστά υπόψη οι διακυμάνσεις των διαδοχικών τιμών, ήταν πολύ κοντά στο ελάχιστο.
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;
}
|
Το ακόλουθο παράδειγμα προγράμματος δείχνει πώς να χρησιμοποιήσετε την προηγούμενη βιβλιοθήκη για τη μέτρηση του παλμός με ένα παλμικό οξύμετρο. Εκτός από τη δημιουργία της τάξης παρακολούθηση του επιπέδου των οξυαιμοσφαιρίνη/αιμοσφαιρίνη και με μικρότερη περιοδικότητα η τιμή του παλμός υπολογισμένο και μέσο όρο.
Για να διασφαλιστεί ότι οι μετρήσεις είναι σχετικές, προγραμματίζεται μια αναμονή πριν εμφανιστεί οποιαδήποτε τιμή. Καθώς η τιμή μπορεί να είναι λανθασμένη (για παράδειγμα, εάν ο χρήστης αφαιρέσει τη συσκευή), οι τιμές εμφανίζονται μόνο εάν βρίσκονται εντός του εύρους εκείνων που θεωρούνται έγκυρες.
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);
}
}
}
|
Δημοσίευση σχολίου