ห้องสมุด 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));
}
}
}
|
เมื่อปรับค่าแล้ว (เริ่มจากการวัดที่มีความหนาแน่นมาก) ฉันได้รวบรวมค่าต่างๆ จาก เครื่องวัดความอิ่มตัวของออกซิเจน เมื่อเวลาผ่านไป ผมสามารถสร้างกราฟโดยใช้สเปรดชีต Lotus Symphony Spreadsheets de LibreOffice, เฉพาะเจาะจง.
ด้วยข้อมูลที่รวบรวมดังที่แสดงในภาพด้านบน การดำเนินการต่อไปคือการพิจารณาว่าความหนาแน่นของค่าช่วยให้เราคำนวณด้วยวิธีที่เชื่อถือได้ แต่ "ประหยัด" (ไม่สุ่มตัวอย่างมากกว่าข้อมูลที่จำเป็น) ค่าของ ชีพจร; ดังที่เห็นในกราฟด้านล่าง มาตรการที่ใช้ดูเหมือนจะให้ผลลัพธ์ที่สมเหตุสมผลที่คาดหวัง
.
ต่อไปด้วยข้อมูลจากการเก็บตัวอย่างข้อมูล จำเป็นต้องพัฒนาอัลกอริธึมที่จะวัดอัตราชีพจร โดยยึดตามกราฟว่า เพื่อความง่าย จะถือว่ากราฟแสดงเค้าโครงที่คล้ายกับ คิวอาร์เอส คอมเพล็กซ์ดูเหมือนว่าสิ่งที่ง่ายที่สุดคือการวัดเวลาระหว่างส่วนที่โดดเด่นที่สุดด้วยค่าที่สูงกว่า (ซึ่งสอดคล้องกับโซน qRs ของการสลับขั้วของโพรง) ละทิ้งโซนที่ประจบประแจงและ "ดังกว่า" ซึ่งจึงยากกว่า เพื่อวัด โซลูชันที่นำมาใช้ซึ่งสอดคล้องกับโค้ดทดสอบด้านล่างนั้นทำงานตามขั้นตอนต่อไปนี้:
-
ตรวจจับพื้นที่ที่ถูกวัดในแต่ละกรณีเพื่อสนใจเฉพาะค่าสูงสุดเท่านั้น คิวอาร์เอส และโยนหุบเขาทิ้งไป ในการทำเช่นนี้ สามารถวัดค่าที่สูงกว่าค่าคงที่ที่แน่นอนได้ แต่มีความเสี่ยงที่บุคคลและ/หรือสถานการณ์สามารถเพิ่มหรือลดค่าได้ แม้ว่าจะตามสัดส่วนก็ตาม เพื่อหลีกเลี่ยงปัญหานี้ ค่าในพื้นที่จะถือว่ามากกว่าค่าที่เกินค่าเฉลี่ยด้วยค่าสัมประสิทธิ์ที่แน่นอน ด้วยวิธีนี้ การวัดจะถูกปรับเทียบด้วยตนเองอย่างละเอียดอ่อน และสามารถปรับเพิ่มเติมได้อีกโดยการปรับค่าสัมประสิทธิ์อย่างละเอียด ซึ่งในกรณีของฉัน ฉันได้ทำการทดลองในระหว่างการทดสอบ
เลือกค่าของโซนจากมากไปน้อยสำหรับการวัด (Rs) ของจุดสูงสุด คิวอาร์เอสให้ใกล้กับจุดสูงสุดของเส้นโค้งมากที่สุด หากต้องการทราบว่าโซนจากน้อยไปหามากถูกยกเลิกก็เพียงพอแล้วที่จะตรวจสอบว่าค่าใหม่น้อยกว่าค่าก่อนหน้าและตรวจสอบว่ายังไม่พบค่าที่ค้นหาเนื่องจากโดยทั่วไปมีหลายค่าในจากมากไปน้อย โซนของ คิวอาร์เอส ขึ้นอยู่กับความหนาแน่นของการสุ่มตัวอย่าง ในการจับเวลาพัลส์ ค่าของช่วงเวลาที่พบจุดนั้นจะถูกจัดเก็บไว้ (หน่วยมิลลิวินาทีที่ส่งคืน มิลลิวินาที ()) และเปรียบเทียบกับอันถัดไป
เพื่อให้แน่ใจว่าค่าที่วัดได้ใหญ่ที่สุดในโซนจากมากไปหาน้อยของเส้นโค้งสูงสุด จึงมีการใช้ตัวแปร บูลีน ( ในตัวอย่างนี้และ ในห้องสมุด) ซึ่งจะเปิดใช้งานเมื่อเข้าสู่โซนขึ้นของเส้นโค้งหลักและปิดใช้งานเมื่อพบค่าแรกจากมากไปน้อยซึ่งเป็นค่ากำหนดเวลา
ตามปกติแล้วจะแสดงระยะเวลาของพัลส์เป็นจำนวนครั้งต่อนาที (ppm) ค่าของเวลาระหว่างพัลส์ที่ได้รับจะถูกแก้ไขโดยการคำนวณโดยการหารเวลารวมของการแสดง (หนึ่งนาที 60000 มิลลิวินาที) ด้วยช่วงเวลาที่ได้รับ ลบมิลลิวินาทีปัจจุบัน (ของค่าปัจจุบัน) ในช่วงเวลาก่อนหน้านี้
เพื่อหลีกเลี่ยงการวัดที่ผิดพลาด (เช่น อุปกรณ์วัดในสุญญากาศ เป็นต้น) จะมีการตรวจสอบว่าผลลัพธ์อยู่ระหว่างค่าสูงสุดและต่ำสุดก่อนที่จะนำไปพิจารณา แม้ว่าจะถือเป็นค่าเฉลี่ยที่ค่าปกติสำหรับผู้ใหญ่ที่มีสุขภาพแข็งแรงขณะพักอยู่ระหว่าง 60 ถึง 100 ppm แต่มีค่าที่ยอมรับได้ด้านล่างนี้ แต่ก็เป็นเรื่องง่ายที่จะหาค่า 40 ppm ในนักกีฬาขณะพัก สูงถึง 200 ppm ในระหว่าง การออกกำลังกายเข้มข้นและอื่น ๆ 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;
}
}
|
ในที่สุด โดยใช้อัลกอริธึมที่อธิบายไว้ก่อนหน้านี้ ฉันพัฒนาไลบรารีเพื่อคำนวณพัลส์โดยการตรวจจับการมีอยู่ของ เฮโมโกลบิน หรือ ออกซีเฮโมโกลบิน (ขึ้นอยู่กับความยาวคลื่นของแสงที่ใช้) จากโค้ดด้านล่างนี้
ห้องสมุดคาดว่าฟังก์ชันการสุ่มตัวอย่างจะถูกเรียกเป็นระยะ ชีพจร ในโครงการจัดการการนอนหลับของฉัน
. ไม่ว่าในกรณีใด จากการทดสอบที่ฉันทำไป ดูเหมือนจะไม่จำเป็น ไม่ว่าจะโดยอุปกรณ์หรือโดยพฤติกรรมของ เพื่อคำนวณชีพจรซึ่งสามารถปรึกษากับฟังก์ชันได้ หรือด้วยฟังก์ชั่น ชีพจรเฉลี่ย นอกเหนือจากการเป็นทรัพยากรที่จำกัดแล้ว ฉันยังตัดการใช้การหยุดชะงักเพราะฉันไม่ต้องการคุณค่าในทันที แต่ต้องการค่าที่คงอยู่ตลอดเวลาเพื่อตรวจสอบ ชีพจรการสุ่มตัวอย่างที่ความถี่หนึ่งจะให้ข้อมูลที่เพียงพอและไม่ได้อะไรมาก (ที่เกี่ยวข้อง) จากการเพิ่ม และไม่สามารถลดลงได้มากโดยไม่สูญเสียข้อมูลที่เกี่ยวข้องสำหรับการคำนวณ ในโค้ดเวอร์ชันแรกๆ เพื่อตรวจสอบการอ่านของ เครื่องวัดความอิ่มตัวของออกซิเจน ฉันค้นพบว่าไม่จำเป็นต้องยึดติดกับเวลาการวัดสูงสุด เนื่องจากหากพิจารณาความแปรผันของค่าที่ต่อเนื่องกันอย่างถูกต้อง ก็จะใกล้เคียงกับค่าต่ำสุดมาก
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);
}
}
}
|
แสดงความคิดเห็น