مكتبة اردوينو لمراقبة معدل ضربات القلب باستخدام مقياس التأكسج النبضي
إحدى المعلمات التي تم رصدها في مشروع إدارة النوم الخاص بي
إنه النبض. لقياس ذلك لقد قمت بتطوير جهاز يعتمد على سلوك الهيموجلوبين والأوكسيهيموجلوبين ضد أطوال موجية مختلفة من الضوء. يتعلق الأمر في الأساس بقياس مقدار الضوء من نوع معين الذي يمكن أن يمر عبره أو ينعكس في منطقة مروية جيدًا من الجسم. التردد الذي تحدث به دورة كاملة من هذه الظاهرة يسمح بقياس صحيفة Pulso.في مرحلة التصميم والاختبار جهاز قياس النبض لقد قمت بتطوير بعض البرامج الصغيرة لمساعدتي في التحقق من صحة التجميع. أولاً، قمت بكتابة الكود أدناه، والذي يأخذ القيم المقاسة من وقت لآخر (على الأقل كل وعلى الأكثر لكل منهما ) عندما تباينت كحد أدنى بين واحد والسابق (القيمة التي تقابل ) و ال تتم مراقبتها من جهاز كمبيوتر باستخدام تطبيق 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 احسب de LibreOffice، محدد.
مع البيانات التي تم جمعها، كما هي ممثلة في الصورة أعلاه، كانت العملية التالية هي تحديد ما إذا كانت كثافة القيم تسمح بحساب قيمة القيمة بطريقة موثوقة ولكن "اقتصادية" (عدم أخذ عينات أكثر من البيانات الضرورية) صحيفة Pulso; وكما يتبين في الرسم البياني أدناه، يبدو أن التدابير المتخذة تعمل على الحصول على النتائج التي يمكن توقعها بشكل معقول.
.
بعد ذلك، باستخدام المعلومات المستمدة من عينات البيانات، كان من الضروري تطوير خوارزمية يمكنها قياس معدل النبض. الالتزام بالرسم البياني الذي يُفترض، من أجل البساطة، أنه يمثل تخطيطًا مشابهًا لـ مجمع QRS، يبدو أن أبسط شيء هو قياس الأوقات بين الأجزاء الأكثر بروزًا، مع التخلص من القيم الأعلى (التي تتوافق مع منطقة 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; } } |
أخيرًا، باستخدام الخوارزمية الموصوفة سابقًا، قمت بتطوير مكتبة لحساب النبض من خلال الكشف عن وجود الهيموغلوبين أو أوكسي هيموغلوبين (حسب الطول الموجي للضوء المستخدم) من الكود أدناه.
تتوقع المكتبة أن يتم استدعاء وظيفة أخذ العينات بشكل دوري صحيفة Pulso في مشروعي لإدارة النوم
. على أية حال، من خلال الاختبارات التي أجريتها، لا يبدو أن ذلك ضروري؛ سواء عن طريق الجهاز أو عن طريق سلوك لحساب النبض، والتي يمكن استشارتها مع الدالة أو مع الوظيفة متوسط النبض. بالإضافة إلى كوني موردًا محدودًا، فقد استبعدت استخدام المقاطعات لأنني لم أكن بحاجة إلى قيم فورية بل إلى قيم مستدامة مع مرور الوقت لمراقبة صحيفة Pulso، فإن أخذ العينات عند تردد معين يوفر معلومات كافية ولا يتم الحصول على المزيد (ذات الصلة) عن طريق زيادتها، ولا يمكن تقليلها كثيرًا دون فقدان البيانات ذات الصلة بالحساب؛ في الإصدارات المبكرة من التعليمات البرمجية لمراقبة قراءة ملف مقياس النبض لقد اكتشفت أنه ليس من الضروري الالتزام بالحد الأقصى لوقت القياس، لأنه إذا تم أخذ الاختلافات في القيم المتعاقبة في الاعتبار بشكل صحيح، فسيكون قريبًا جدًا من الحد الأدنى.
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; } |
يوضح برنامج المثال التالي كيفية استخدام المكتبة السابقة لقياس صحيفة Pulso مع مقياس النبض. بالإضافة إلى إنشاء مثيل للفصل مراقبة مستوى أوكسي هيموغلوبين/الهيموغلوبين ومع دورية أصغر قيمة صحيفة Pulso محسوبة ومتوسطة.
للتأكد من أن القياسات ذات صلة، تتم برمجة الانتظار قبل عرض أي قيمة. نظرًا لأن القيمة قد تكون غير صحيحة (على سبيل المثال، إذا قام المستخدم بإزالة الجهاز)، يتم عرض القيم فقط إذا كانت ضمن نطاق القيم التي تعتبر صالحة.
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); } } } |
أكتب تعليق