Biblioteka Arduino do monitorowania tętna za pomocą pulsoksymetru
Jeden z parametrów monitorowanych w moim projekcie zarządzania snem
To puls. zmierzyć Opracowałem urządzenie oparte na zachowaniu hemoglobiny i oksyhemoglobiny pod wpływem różnych długości fal światła. Zasadniczo chodzi o pomiar, ile światła określonego rodzaju jest w stanie przejść lub zostać odbite w dobrze nawodnionym obszarze ciała. Częstotliwość z jaką zachodzi pełny cykl tego zjawiska pozwala zmierzyć pulso.Na etapie projektowania i testowania urządzenie do pomiaru tętna Opracowałem kilka małych programów, które pomogły mi zweryfikować poprawność montażu. Na początek napisałem poniższy kod, który co jakiś czas pobierał zmierzone wartości (przynajmniej co i co najwyżej każdy ), gdy różniły się minimum między jednym a poprzednim (wartość, która odpowiada ) i monitorowane z komputera z aplikacją w języku Python aby móc je później przeanalizować.
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));
}
}
}
|
Po dostosowaniu wartości (zaczynając od bardzo gęstych pomiarów) otrzymałem zbiór wartości z pulsoksymetr z biegiem czasu mogłem sporządzać wykresy za pomocą arkusza kalkulacyjnego, Kalkulator LibreOffice de LibreOffice, konkretny.
Na podstawie zebranych danych, jak pokazano na powyższym obrazku, kolejną operacją było sprawdzenie, czy gęstość wartości pozwala w rzetelny, ale „ekonomiczny” sposób obliczyć (nie pobierając więcej niż niezbędne dane) wartość pulso; Jak widać na poniższym wykresie, podjęte działania zdawały się służyć uzyskaniu wyników, których można było się spodziewać.
.
Następnie, korzystając z informacji pochodzących z pobierania próbek danych, konieczne było opracowanie algorytmu, który mierzyłby częstość tętna. Trzymając się wykresu, dla uproszczenia przyjmuje się, że reprezentuje on układ podobny do Zespół QRSnajprostszą rzeczą wydaje się być zmierzenie czasów pomiędzy najbardziej wydatnymi częściami, przy wyższych wartościach (co odpowiada strefie depolaryzacji qRs komór) z pominięciem strefy bardziej płaskiej i „głośniejszej”, przez co trudniejsza do mierzyć. Przyjęte rozwiązanie, które odpowiada poniższemu kodowi testowemu, działa według następującej procedury:
-
W każdym przypadku należy wykryć mierzony obszar, aby uwzględnić tylko szczyty wartości qR i wyrzuć dolinę. W tym celu można zmierzyć wartości wyższe od określonej stałej, ale istnieje ryzyko, że osoba i/lub okoliczności mogą, choć proporcjonalnie, podnieść lub obniżyć te wartości. Aby tego uniknąć, wartość w obszarze uważa się za większą niż ta, która przekracza wartość średnią o określony współczynnik. W ten sposób pomiar jest automatycznie kalibrowany z dużą czułością i można go jeszcze bardziej dostosować poprzez dostrojenie współczynnika, co w moim przypadku osiągnąłem eksperymentalnie podczas testów.
Wybierz wartości strefy malejącej dla pomiaru (Rs) szczytu qR, jak najbliżej maksimum krzywej. Aby wiedzieć, że strefa rosnąca jest porzucana, wystarczy sprawdzić, czy nowa wartość jest mniejsza od poprzedniej i sprawdzić, czy szukana wartość nie została jeszcze znaleziona, gdyż generalnie w strefie malejącej jest kilka wartości strefa qR w zależności od gęstości próbkowania. Do pomiaru czasu impulsu zapisywana jest wartość chwili znalezienia punktu (milisekundy zwracane przez millis ()) i porównuje go z kolejnym.
Aby mieć pewność, że zmierzona wartość będzie największa w strefie opadającej najwyższej krzywej, stosuje się zmienną logiczna ( w tym przykładzie i w bibliotece), która jest aktywowana w momencie wejścia w strefę rosnącą krzywej głównej i dezaktywowana po znalezieniu pierwszej wartości malejącej, czyli wartości czasowej.
Ponieważ czas trwania impulsu zwykle przedstawia się jako uderzenia na minutę (ppm), wartość czasu między uzyskanymi impulsami koryguje się poprzez obliczenie poprzez podzielenie całkowitego czasu reprezentacji (jedna minuta, 60000 XNUMX milisekund) przez przedział uzyskany przez odejmując bieżące milisekundy (od bieżącej wartości) od wcześniej zmierzonych.
Aby uniknąć fałszywych pomiarów (takich jak na przykład urządzenie mierzące w próżni), przed przyjęciem go za pewnik sprawdza się, czy wynik mieści się pomiędzy wartościami maksymalnymi i minimalnymi. Choć za średnią przyjmuje się, że dla zdrowego dorosłego człowieka w spoczynku wartość ta mieści się w przedziale od 60 do 100 ppm, poniżej znajdują się wartości dopuszczalne, łatwo znaleźć u sportowca w spoczynku 40 ppm, do 200 ppm w trakcie intensywne ćwiczenia i więcej 100 ppm u dorosłych prowadzących siedzący tryb życia w stanach podniecenia, to właśnie interesujący czynnik w projekcie zarządzania snem co skłania mnie do rozwinięcia tego urządzenie do pomiaru tętna. Z tego powodu wskazane jest znaczne rozluźnienie tych wartości, aby nie zgubić skrajności, które mogłyby precyzyjnie pokazać istotne aspekty.
Nowa średnia wartość jest obliczana poprzez zmniejszenie trafności bieżącej średniej na podstawie liczby pobranych wartości i dodawana jest ostatnia wartość, również ważona współczynnikiem, który zmniejsza ją tym bardziej, im więcej wartości zostało zmierzonych do tej pory .
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;
}
}
|
Na koniec, korzystając z opisanego wcześniej algorytmu, opracowałem bibliotekę do obliczania pulsu poprzez wykrywanie obecności hemoglobina o oksyhemoglobina (w zależności od użytej długości fali światła) z poniższego kodu.
Biblioteka oczekuje, że funkcja próbkowania będzie wywoływana okresowo pulso w moim projekcie zarządzania snem
. W każdym razie z przeprowadzonych przeze mnie testów wynika, że nie jest to konieczne; albo przez urządzenie, albo przez zachowanie do obliczenia pulsu, który można sprawdzić za pomocą funkcji lub z funkcją średni puls. Oprócz tego, że mam ograniczone zasoby, wykluczyłem stosowanie przerw, ponieważ nie potrzebowałem natychmiastowych wartości, ale raczej wartości trwałych w czasie, aby monitorować pulsopobieranie próbek z określoną częstotliwością zapewnia wystarczającą ilość informacji, a zwiększenie go nie daje wiele więcej (istotnego), ani też nie jest możliwe jego znaczne zmniejszenie bez utraty danych istotnych do obliczeń; we wczesnych wersjach kodu do monitorowania odczytu pliku pulsoksymetr Odkryłem, że nie trzeba trzymać się maksymalnego czasu pomiaru, gdyż przy prawidłowym uwzględnieniu zmian kolejnych wartości był on bardzo bliski 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;
}
|
Poniższy przykładowy program pokazuje, jak wykorzystać poprzednią bibliotekę do pomiaru pulso z pulsoksymetr. Oprócz tworzenia instancji klasy monitorowanie poziomu oksyhemoglobina/hemoglobina i przy mniejszej okresowości wartość pulso obliczone i średnie.
Aby mieć pewność, że pomiary są istotne, przed wyświetleniem jakiejkolwiek wartości zaprogramowane jest oczekiwanie. Ponieważ wartość może być niepoprawna (na przykład, jeśli użytkownik usunie urządzenie), wartości są wyświetlane tylko wtedy, gdy mieszczą się w zakresie uznawanych za prawidłowe.
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);
}
}
}
|
Zamieść komentarz