ห้องสมุด 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 มิลลิวินาที) ด้วยช่วงเวลาที่ได้รับ ลบมิลลิวินาทีปัจจุบัน (ของค่าปัจจุบัน) ในช่วงเวลาก่อนหน้านี้
Para evitar medidas falsas (como el dispositivo midiendo en vacío, por ejemplo) se verifica que el resultado se encuentra entre unos valores máximos y mínimos antes de darlo por cierto. Aunque se considera como media que un valor normal para un adulto sano en reposo se encuentra entre 60 y 100 ppm, hay valores admisibles por debajo, es fácil encontrar 40 ppm en un atleta en reposo, hasta 200 ppm sometido a un ejercicio intenso y más de 100 ppm en adultos sedentarios en estados de excitación, precisamente un factor interesante para el proyecto de gestión del sueño que me lleva a desarrollar este อุปกรณ์วัดชีพจร. ด้วยเหตุนี้จึงแนะนำให้ผ่อนคลายค่านิยมเหล่านี้ให้มากเพื่อไม่ให้ความสุดขั้วหายไปซึ่งสามารถแสดงแง่มุมที่เกี่ยวข้องได้อย่างแม่นยำ
ค่าเฉลี่ยใหม่คำนวณโดยการลดความเกี่ยวข้องของค่าเฉลี่ยปัจจุบันตามจำนวนค่าที่สุ่มตัวอย่างและเพิ่มค่าสุดท้าย นอกจากนี้ ยังถ่วงน้ำหนักด้วยค่าสัมประสิทธิ์ที่ช่วยลดค่าที่วัดได้มากขึ้น .
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); } } } |
แสดงความคิดเห็น