Arduino-bibliotek för pulsmätning med pulsoximeter
En av parametrarna som övervakades i mitt sömnhanteringsprojekt
Det är pulsen. att mäta det Jag utvecklade en enhet baserad på beteendet hos hemoglobin och oxyhemoglobin mot olika våglängder av ljus. I grund och botten handlar det om att mäta hur mycket ljus av en viss typ som kan passera genom eller reflekteras i ett välbevattnat område av kroppen. Frekvensen med vilken en fullständig cykel av detta fenomen inträffar gör det möjligt att mäta puls.I design- och testfasen av pulsmätare Jag utvecklade några små program för att hjälpa mig att verifiera att monteringen var korrekt. Först skrev jag koden nedan, som tog de uppmätta värdena då och då (minst varje och högst var och en ) när de varierade ett minimum mellan en och den föregående (det värde som motsvarar ) och den övervakas från en dator med en Python-applikation för att kunna analysera dem senare.
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));
}
}
}
|
När värdena väl hade justerats (med början med mycket täta mätningar) fick jag en samling värden från pulsoximeter över tid som jag kunde rita med ett kalkylblad, LibreOffice Calc de LibreOffice, specifik.
Med de insamlade uppgifterna, som visas i bilden ovan, var nästa operation att bestämma om tätheten av värden gjorde det möjligt för oss att på ett tillförlitligt men "ekonomiskt" sätt (inte ta prover mer än nödvändiga data) beräkna värdet av puls; Som framgår av grafen nedan verkade de vidtagna åtgärderna tjäna till att uppnå de resultat som är rimliga att förvänta sig.
.
Därefter, med informationen från datasamplingen, var det nödvändigt att utveckla en algoritm som skulle mäta pulsfrekvensen. Håller sig till grafen som för enkelhets skull antas att den representerar en layout som liknar QRS-komplex, det enklaste verkar vara att mäta tiderna mellan de mest framträdande delarna, med högre värden (vilket motsvarar qRs-zonen för depolarisering av kamrarna), att kassera den plattare och "bullare" zonen, vilket därför är svårare att mäta. Den antagna lösningen, som motsvarar testkoden nedan, fungerar enligt följande procedur:
-
Detektera området som mäts i varje enskilt fall för att bara ta hänsyn till värdetopparna qRs och kasta bort dalen. För att göra detta skulle värden högre än en viss konstant kunna mätas, men det finns en risk att en individ och/eller omständigheter kan, om än proportionellt, höja eller sänka värdena. För att undvika detta anses ett värde i området vara större än det som överstiger medelvärdet med en viss koefficient. På så sätt är mätningen känsligt självkalibrerad och skulle kunna justeras ytterligare genom att finjustera koefficienten, vilket jag i mitt fall har uppnått experimentellt under testerna.
Välj värdena för den fallande zonen för mätningen (Rs) av toppen qRs, så nära kurvans maximum som möjligt. För att veta att den stigande zonen överges räcker det att verifiera att ett nytt värde är mindre än det föregående och verifiera att det sökta värdet ännu inte har hittats eftersom det i allmänhet finns flera värden i fallande zon av qRs beroende på provtagningstätheten. För att tajma pulsen lagras värdet för det ögonblick då punkten hittades (de millisekunder som returneras av milis ()) och jämför den med nästa.
För att säkerställa att det uppmätta värdet är störst i den fallande zonen av den högsta kurvan, används en variabel booleskt ( i detta exempel och i biblioteket) som aktiveras när man går in i den stigande zonen för huvudkurvan och avaktiveras när det första fallande värdet hittats, vilket är det tidsinställda.
Eftersom det är vanligt att representera pulsens varaktighet som slag per minut (ppm), korrigeras värdet på tiden mellan erhållna pulser genom att den totala tiden för representationen (en minut, 60000 XNUMX millisekunder) divideras med intervallet som erhålls av subtrahera de aktuella millisekunderna (av det aktuella värdet) bland de tidigare tidsinställda.
För att undvika falska mätningar (som till exempel att enheten mäter i vakuum) verifieras det att resultatet ligger mellan max- och minimivärden innan det tas för givet. Även om det anses som ett genomsnitt att ett normalvärde för en frisk vuxen i vila är mellan 60 och 100 ppm, finns det tillåtna värden nedan, det är lätt att hitta 40 ppm hos en idrottare i vila, upp till 200 ppm under intensiv träning med mera på 100 ppm hos stillasittande vuxna i tillstånd av spänning, just en intressant faktor för sömnhanteringsprojektet vilket får mig att utveckla detta pulsmätare. Av denna anledning är det tillrådligt att slappna av dessa värden mycket så att ytterligheterna inte går förlorade, vilket exakt kan visa relevanta aspekter.
Det nya medelvärdet beräknas genom att minska relevansen av det aktuella medelvärdet baserat på antalet samplade värden och det sista värdet läggs till, även viktat med en koefficient som minskar det ytterligare ju fler värden som har uppmätts hittills .
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;
}
}
|
Slutligen, med hjälp av algoritmen som beskrivits tidigare, utvecklade jag biblioteket för att beräkna pulsen genom att detektera närvaron av hemoglobin o oxihemoglobin (beroende på ljusets våglängd) från koden nedan.
Biblioteket förväntar sig att samplingsfunktionen anropas med jämna mellanrum puls i mitt sömnhanteringsprojekt
. Av de tester jag gjort verkar det i alla fall inte vara nödvändigt; antingen av enheten eller av beteendet hos för att beräkna pulsen, som kan konsulteras med funktionen eller med funktionen medelpulsen. Förutom att vara en begränsad resurs uteslöt jag att använda avbrott eftersom jag inte behövde omedelbara värden utan snarare hållbara över tid för att övervaka puls, sampling vid en viss frekvens ger tillräckligt med information och inte mycket mer (relevant) erhålls genom att öka den, och det är inte heller möjligt att minska det mycket utan att förlora relevanta data för beräkningen; i tidiga versioner av koden för att övervaka läsningen av pulsoximeter Jag upptäckte att det inte var nödvändigt att hålla sig till en maximal mättid eftersom, om variationerna av successiva värden beaktades korrekt, var den mycket nära 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;
}
|
Följande exempelprogram visar hur man använder det föregående biblioteket för att mäta puls med en pulsoximeter. Förutom att instansiera klassen övervakning av nivån på oxihemoglobin/hemoglobin och med en mindre periodicitet värdet av puls beräknade och genomsnittliga.
För att säkerställa att mätningarna är relevanta programmeras en väntetid innan något värde visas. Eftersom värdet kan vara felaktigt (till exempel om användaren tar bort enheten), visas värden endast om de ligger inom intervallet för de som anses giltiga.
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);
}
}
}
|
Post kommentar