Biblioteca Arduino pentru monitorizarea ritmului cardiac cu pulsoximetru
Unul dintre parametrii monitorizați în proiectul meu de management al somnului
Este pulsul. să-l măsoare Am dezvoltat un dispozitiv bazat pe comportamentul hemoglobinei și oxihemoglobinei față de diferite lungimi de undă de lumină. Practic, este vorba despre măsurarea câtă lumină de un anumit tip poate trece sau se reflectă într-o zonă bine irigată a corpului. Frecvența cu care are loc un ciclu complet al acestui fenomen permite măsurarea pulso.În faza de proiectare și testare a aparat de masurare a pulsului Am dezvoltat câteva mici programe care să mă ajute să verific dacă asamblarea a fost corectă. Mai întâi am scris codul de mai jos, care a luat din când în când valorile măsurate (cel puțin la fiecare si cel mult fiecare ) când au variat un minim între unu și precedentul (valoarea care îi corespunde ) si monitorizat de pe un computer cu o aplicație Python pentru a le putea analiza ulterior.
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));
}
}
}
|
Odată ce valorile au fost ajustate (începând cu măsurători foarte dense) am obținut o colecție de valori din puls oximetru de-a lungul timpului, am putut reprezenta grafic folosind o foaie de calcul, LibreOffice Calc de LibreOffice, specific.
Cu datele colectate, așa cum este reprezentat în imaginea de mai sus, următoarea operațiune a fost de a determina dacă densitatea valorilor ne permite să calculăm într-un mod fiabil, dar „economic” (fără eșantionare mai mult decât datele necesare) valoarea pulso; După cum se poate observa în graficul de mai jos, măsurile luate par să servească la obținerea unor rezultate rezonabile de așteptat.
.
În continuare, cu informațiile din eșantionarea datelor, a fost necesară dezvoltarea unui algoritm care să măsoare frecvența pulsului. Rămânând la graficul care, pentru simplitate, se presupune că reprezintă un aspect similar celui complex QRS, cel mai simplu pare să fie măsurarea timpilor dintre părțile cele mai proeminente, cu valori mai mari (care corespunde zonei qRs de depolarizare a ventriculilor), eliminând zona mai plată și „mai zgomotoasă”, care este deci mai dificilă. a masura. Soluția adoptată, care corespunde codului de testare de mai jos, funcționează conform următoarei proceduri:
-
Detectați zona care este măsurată în fiecare caz pentru a urmări doar vârfurile valorii qRs și aruncă valea. Pentru a face acest lucru, ar putea fi măsurate valori mai mari decât o anumită constantă, dar există riscul ca un individ și/sau circumstanțe să poată, deși proporțional, să crească sau să scadă valorile. Pentru a evita acest lucru, o valoare din zonă este considerată mai mare decât cea care depășește valoarea medie cu un anumit coeficient. În acest fel, măsurarea este sensibil auto-calibrată și ar putea fi ajustată și mai mult prin reglarea fină a coeficientului, ceea ce în cazul meu l-am realizat experimental în timpul testelor.
Alegeți valorile zonei descendente pentru măsurare (Rs) vârfului qRs, cât mai aproape de maximul curbei. Pentru a ști că zona ascendentă este abandonată, este suficient să verificați că o nouă valoare este mai mică decât cea anterioară și să verificați că valoarea căutată nu a fost încă găsită deoarece, în general, există mai multe valori în descendența zona de qRs în funcţie de densitatea de eşantionare. Pentru a cronometra pulsul, este stocată valoarea momentului în care a fost găsit punctul (milisecundele returnate de milis ()) și îl compară cu următorul.
Pentru a vă asigura că valoarea măsurată este cea mai mare în zona descendentă a celei mai înalte curbe, se utilizează o variabilă boolean ( în acest exemplu şi în bibliotecă) care se activează la intrarea în zona ascendentă a curbei majore și se dezactivează odată ce este găsită prima valoare descendentă, care este cea cronometrată.
Deoarece este de obicei să se reprezinte durata pulsului ca bătăi pe minut (ppm), valoarea timpului dintre impulsuri obţinute se corectează prin calculul împărţind timpul total al reprezentării (un minut, 60000 milisecunde) la intervalul obţinut prin scăzând milisecundele curente (ale valorii curente) dintre cele cronometrate anterior.
Pentru a evita măsurătorile false (cum ar fi dispozitivul care măsoară în vid, de exemplu), se verifică ca rezultatul să fie între valorile maxime și minime înainte de a se lua de la sine înțeles. Deși se consideră ca o medie că o valoare normală pentru un adult sănătos în repaus este între 60 și 100 ppm, mai jos sunt valori admisibile, este ușor de găsit 40 ppm la un sportiv în repaus, până la 200 ppm în timpul exerciții fizice intense și nu numai de 100 ppm la adulții sedentari în stări de excitare, tocmai un factor interesant pentru proiectul de management al somnului ceea ce mă face să dezvolt asta dispozitiv de măsurare a pulsului. Din acest motiv, este indicat să relaxați foarte mult aceste valori pentru a nu se pierde extremele, care ar putea arăta tocmai aspecte relevante.
Noua valoare medie se calculează prin scăderea relevanței mediei curente pe baza numărului de valori eșantionate și se adaugă ultima valoare, ponderată tot cu un coeficient care o reduce și mai mult cu cât au fost măsurate mai multe valori până acum. .
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;
}
}
|
În cele din urmă, folosind algoritmul descris anterior, am dezvoltat biblioteca pentru a calcula pulsul prin detectarea prezenței hemoglobină o oxihemoglobina (în funcție de lungimea de undă a luminii utilizate) din codul de mai jos.
Biblioteca se așteaptă ca funcția de eșantionare să fie apelată periodic pulso în proiectul meu de management al somnului
. In orice caz, din testele pe care le-am facut, nu pare a fi necesar; fie de dispozitiv, fie de comportamentul pentru a calcula pulsul, care poate fi consultat cu funcția sau cu funcția pulsul mediu. Pe lângă faptul că sunt o resursă limitată, am exclus să folosesc întreruperi pentru că nu aveam nevoie de valori imediate, ci mai degrabă susținute în timp pentru a monitoriza pulso, eșantionarea la o anumită frecvență oferă suficiente informații și nu se obține mult mai mult (relevant) prin creșterea acesteia și nici nu se poate micșora mult fără a pierde datele relevante pentru calcul; în versiunile timpurii ale codului pentru a monitoriza citirea puls oximetru Am descoperit că nu era necesar să se țină la un timp maxim de măsurare, deoarece, dacă variațiile valorilor succesive erau corect luate în considerare, acesta era foarte aproape de minim.
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;
}
|
Următorul exemplu de program arată cum să utilizați biblioteca anterioară pentru a măsura pulso cu un singur puls oximetru. Pe lângă instanțiarea clasei monitorizarea nivelului de oxihemoglobina/hemoglobină iar cu o periodicitate mai mică valoarea lui pulso calculate si medii.
Pentru a vă asigura că măsurătorile sunt relevante, este programată o așteptare înainte de afișarea oricărei valori. Deoarece valoarea poate fi incorectă (de exemplu dacă utilizatorul scoate dispozitivul), valorile sunt afișate doar dacă se încadrează în intervalul celor considerate valide.
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);
}
}
}
|
Posteaza un comentariu