パルスオキシメーターで心拍数をモニタリングするための Arduino ライブラリ
私の睡眠管理プロジェクトで監視されているパラメータの XNUMX つ
それは脈拍です。それを測定する 異なる波長の光に対するヘモグロビンとオキシヘモグロビンの挙動に基づいたデバイスを開発しました。基本的には、特定の種類の光が体の十分に灌漑された領域をどれだけ通過または反射できるかを測定することです。この現象の完全なサイクルが発生する頻度により、 脈拍.の設計およびテスト段階では、 脈拍測定装置 アセンブリが正しいことを確認するために、いくつかの小さなプログラムを開発しました。まず、時々(少なくとも毎回)測定値を取得する以下のコードを書きました。 そしてせいぜいそれぞれ ) ある値とその前の値 (に対応する値) の間で最小値が変化したとき。 ) そしてその 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 Calc de LibreOfficeの、 特定の。
上の画像に示されているように、収集されたデータを使用して、次の操作は、値の密度により、信頼性がありながら「経済的な」方法 (必要以上のデータをサンプリングしない) で値を計算できるかどうかを判断することでした。 脈拍;以下のグラフからわかるように、講じられた対策により、期待に値する結果が得られたようです。
.
次に、データ サンプリングからの情報を使用して、脈拍数を測定するアルゴリズムを開発する必要がありました。簡単にするために、グラフは次のようなレイアウトを表すものと仮定します。 QRSコンプレックス、最も単純なことは、最も顕著な部分の間の時間を測定することのようです。より高い値(心室の脱分極の qRs ゾーンに対応します)では、より平坦で「ノイズの多い」ゾーンが無視されるため、測定がより困難になります。測定。以下のテスト コードに対応する採用されたソリューションは、次の手順に従って動作します。
-
それぞれのケースで測定されている領域を検出して、値のピークのみに注目します qR そして谷を捨てる。これを行うには、特定の定数よりも高い値を測定できますが、個人および/または状況によって、比例的ではあるものの、値が上下するリスクがあります。これを避けるために、その領域の値は、平均値を一定の係数だけ超えた値よりも大きいとみなします。このようにして、測定値は高感度に自己校正され、係数を微調整することでさらに調整することができます。私の場合、テスト中に実験的にこれを達成しました。
測定の下降ゾーンの値を選択します(Rs) ピークの qR、曲線の最大値にできるだけ近づけます。昇順ゾーンが放棄されていることを知るには、新しい値が前の値より小さいことを確認し、一般に降順ゾーンにはいくつかの値が存在するため、検索された値がまだ見つかっていないことを確認するだけで十分です。のゾーン qR サンプリング密度によって異なります。パルスのタイミングを計るために、ポイントが見つかった瞬間の値が保存されます (ミリ秒は、 ミリス()) そして次のものと比較します。
測定値が最も高い曲線の下降ゾーンで最大であることを確認するために、変数が使用されます。 ブール値 ( この例では、そして これは、メジャー カーブの上昇ゾーンに入ったときにアクティブになり、最初の下降値 (時間指定された値) が見つかると非アクティブになります。
通常、パルスの持続時間は 60000 分あたりの拍数 (ppm) として表されるため、得られたパルス間の時間の値は、表示の合計時間 (XNUMX 分、XNUMX ミリ秒) を次の式で得られた間隔で割ることによって計算することによって補正されます。以前に計測したミリ秒から現在のミリ秒 (現在の値) を減算します。
誤った測定 (例えば、真空中でのデバイスの測定など) を避けるために、結果を当然のものとみなす前に、結果が最大値と最小値の間にあることが検証されます。健康な成人の安静時の正常値は60から100ppmであると平均的に考えられていますが、以下の許容値があり、アスリートでは安静時に40ppm、運動中に最大200ppmであることは簡単に見つかります。激しい運動など。興奮状態で座りがちな成人では 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);
}
}
}
|
コメントを投稿