맥박 산소 측정기를 사용한 심박수 모니터링을 위한 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));
}
}
}
|
값이 조정되면(매우 조밀한 측정으로 시작) 맥박 산소 측정기 시간이 지나면서 스프레드시트를 사용하여 그래프를 그릴 수 있게 되었고 리브레오피스 계산기 de LibreOffice, 특정한.
위 이미지에 표시된 대로 수집된 데이터를 사용하여 다음 작업은 값의 밀도를 통해 신뢰할 수 있지만 "경제적인" 방식(필요한 데이터 이상을 샘플링하지 않음)으로 값을 계산할 수 있는지 확인하는 것이었습니다. 맥박; 아래 그래프에서 볼 수 있듯이 취해진 조치는 합리적으로 기대하는 결과를 얻는 데 도움이 되는 것으로 보입니다.
.
다음으로 데이터 샘플링을 통해 얻은 정보를 바탕으로 맥박수를 측정하는 알고리즘을 개발해야 했습니다. 단순화를 위해 그래프를 고수하면 다음과 유사한 레이아웃을 나타내는 것으로 가정됩니다. QRS 콤플렉스, 가장 간단한 것은 가장 눈에 띄는 부분 사이의 시간을 더 높은 값(심실의 탈분극 qRs 영역에 해당)으로 측정하고 더 평평하고 "잡음이 많은" 영역을 삭제하는 것 같습니다. 따라서 더 어렵습니다. 측정. 아래 테스트 코드에 해당하는 채택된 솔루션은 다음 절차에 따라 작동합니다.
-
값 피크에만 주의를 기울이기 위해 각 케이스에서 측정되는 영역을 감지합니다. qR 그리고 계곡을 버리세요. 이를 위해서는 특정 상수보다 높은 값을 측정할 수 있으나, 비례적이라 할지라도 개인 및/또는 상황에 따라 값이 높아지거나 낮아질 위험이 있습니다. 이를 방지하기 위해 해당 영역의 값은 특정 계수만큼 평균값을 초과하는 값보다 큰 것으로 간주됩니다. 이러한 방식으로 측정값은 민감하게 자체 보정되며 계수를 미세 조정하여 더욱 세부적으로 조정할 수 있습니다. 제 경우에는 테스트 중에 실험적으로 이를 달성했습니다.
측정을 위해 하강 구역의 값을 선택합니다(Rs) 피크의 qR, 곡선의 최대값에 최대한 가깝습니다. 상승 구간이 포기되고 있다는 것을 알기 위해서는 새로운 값이 이전 값보다 작은지 확인하고, 검색된 값이 아직 발견되지 않았는지 확인하면 충분하다. 일반적으로 하강 구간에는 여러 값이 존재하기 때문이다. 구역 qR 샘플링 밀도에 따라 다릅니다. 펄스의 시간을 측정하기 위해 지점이 발견된 순간의 값이 저장됩니다. 밀리 ()) 다음과 비교합니다.
측정된 값이 가장 높은 곡선의 하강 영역에서 가장 큰지 확인하기 위해 변수가 사용됩니다. 부울 ( 이 예에서는 라이브러리에서)는 주요 곡선의 상승 영역에 들어갈 때 활성화되고 첫 번째 하강 값, 즉 시간이 지정된 값이 발견되면 비활성화됩니다.
펄스의 지속 시간을 분당 심박수(ppm)로 표시하는 것이 일반적이므로, 표시되는 전체 시간(60000분, XNUMX밀리초)을 다음과 같이 얻은 간격으로 나누어 계산하여 얻은 펄스 사이의 시간 값을 수정합니다. 이전에 측정한 값에서 현재 밀리초(현재 값)를 뺍니다.
잘못된 측정(예: 진공 상태에서 측정하는 장치 등)을 방지하기 위해 결과가 최대값과 최소값 사이에 있는지 확인하고 이를 당연하게 여깁니다. 휴식 중인 건강한 성인의 정상 수치는 60~100ppm인 것을 평균으로 생각하지만, 아래에 허용 가능한 값이 있는데, 휴식 중인 운동선수의 경우 40ppm, 운동 중에는 최대 200ppm을 쉽게 찾을 수 있습니다. 격렬한 운동 및 흥분 상태에 앉아 있는 성인의 경우 100ppm 이상, 이는 바로 수면 관리 프로젝트의 흥미로운 요소입니다. 내가 이걸 개발하게 된 이유는 맥박 측정 장치. 이러한 이유로 극단을 잃지 않도록 이러한 값을 많이 완화하여 관련 측면을 정확하게 표시하는 것이 좋습니다.
샘플링된 값의 개수를 기준으로 현재 평균의 관련성을 감소시켜 마지막 값을 더하여 새로운 평균값을 계산하며, 또한 지금까지 측정된 값이 많을수록 이를 감소시키는 계수로 가중치를 부여합니다. .
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);
}
}
}
|
코멘트 남기기