- ما هو سيمافور؟
- كيفية استخدام سيمافور في FreeRTOS؟
- شرح كود سيمافور
- مخطط الرسم البياني
- ما هو موتكس؟
- كيفية استخدام Mutex في FreeRTOS؟
- شرح كود موتكس
في الدروس السابقة ، قمنا بتغطية أساسيات FreeRTOS مع Arduino وكائن Queue kernel في FreeRTOS Arduino. الآن ، في هذا البرنامج التعليمي الثالث لـ FreeRTOS ، سنتعلم المزيد عن FreeRTOS وواجهات برمجة التطبيقات المتقدمة ، والتي يمكن أن تجعلك تفهم النظام الأساسي متعدد المهام بعمق أكبر.
Semaphore و Mutex (الاستبعاد المتبادل) هي كائنات kernel التي يتم استخدامها للمزامنة وإدارة الموارد وحماية الموارد من الفساد. في النصف الأول من هذا البرنامج التعليمي ، سنرى فكرة سيمافور ، كيف وأين نستخدمها. في الشوط الثاني ، سنستمر مع Mutex.
ما هو سيمافور؟
في البرامج التعليمية السابقة ، ناقشنا أولويات المهام وتعرفنا أيضًا على أن المهمة ذات الأولوية الأعلى تستبق مهمة ذات أولوية أقل ، لذلك أثناء تنفيذ مهمة ذات أولوية عالية ، قد يكون هناك احتمال أن يحدث تلف في البيانات في مهمة ذات أولوية أقل لأنه لم يتم تنفيذ بعد والبيانات تأتي باستمرار إلى هذه المهمة من جهاز استشعار يتسبب في فقد البيانات وتعطل التطبيق بأكمله.
لذلك ، هناك حاجة لحماية الموارد من فقدان البيانات وهنا تلعب Semaphore دورًا مهمًا.
سيمافور هي آلية للإشارة يتم فيها الإشارة إلى مهمة في حالة انتظار من خلال مهمة أخرى للتنفيذ. بعبارة أخرى ، عندما تنتهي المهمة 1 من عملها ، فإنها ستُظهر علامة أو تزيد علامة بمقدار 1 ثم يتم استلام هذه العلامة بواسطة مهمة أخرى (المهمة 2) توضح أنها تستطيع أداء عملها الآن. عندما تنتهي المهمة 2 من عملها ، سيتم تقليل العلامة بمقدار 1.
لذلك ، فهي في الأساس آلية "منح" و "خذ" وسيمافور هو متغير عدد صحيح يستخدم لمزامنة الوصول إلى الموارد.
أنواع السيمافور في FreeRTOS:
سيمافور من نوعين.
- ثنائي سيمافور
- عد سيمافور
1. إشارة ثنائية: لها قيمتان صحيحتان 0 و 1. وهي تشبه إلى حد ما قائمة انتظار الطول 1. على سبيل المثال ، لدينا مهمتان ، مهمة 1 و مهمة 2. يرسل Task1 البيانات إلى Task2 حتى يتحقق Task2 باستمرار من عنصر قائمة الانتظار إذا كان هناك 1 ، ثم يمكنه قراءة البيانات وإلا فإنه يتعين عليه الانتظار حتى يصبح 1. بعد أخذ البيانات ، يقلل task2 من قائمة الانتظار ويجعله 0 وهذا يعني المهمة 1 مرة أخرى يمكن إرسال البيانات إلى Task2.
من المثال أعلاه ، يمكن القول أن الإشارة الثنائية تستخدم للتزامن بين المهام أو بين المهام والمقاطعة.
2. عد الإشارة: تحتوي على قيم أكبر من 0 ويمكن التفكير في أن طولها يزيد عن 1. وتستخدم هذه الإشارة لعد الأحداث. في سيناريو الاستخدام هذا ، سيعطي معالج الحدث إشارة في كل مرة يحدث فيها حدث (بزيادة قيمة عدد الإشارات) ، وستأخذ مهمة المعالج إشارة في كل مرة يعالج فيها حدثًا (إنقاص قيمة عدد الإشارات).
وبالتالي ، فإن قيمة الجرد هي الفرق بين عدد الأحداث التي حدثت والعدد الذي تمت معالجته.
الآن ، دعنا نرى كيفية استخدام سيمافور في كود FreeRTOS الخاص بنا.
كيفية استخدام سيمافور في FreeRTOS؟
يدعم FreeRTOS واجهات برمجة تطبيقات مختلفة لإنشاء إشارة وأخذ إشارة وإعطاء إشارة.
الآن ، يمكن أن يكون هناك نوعان من واجهات برمجة التطبيقات لنفس كائن kernel. إذا كان علينا إعطاء إشارة من ISR ، فلا يمكن استخدام API للإشارة العادية. يجب عليك استخدام واجهات برمجة التطبيقات المحمية من المقاطعة.
في هذا البرنامج التعليمي ، سوف نستخدم الإشارات الثنائية لأنها سهلة الفهم والتنفيذ. نظرًا لاستخدام وظيفة المقاطعة هنا ، فأنت بحاجة إلى استخدام واجهات برمجة التطبيقات المحمية من المقاطعة في وظيفة ISR. عندما نقول مزامنة مهمة مع مقاطعة ، فهذا يعني وضع المهمة في حالة التشغيل مباشرة بعد ISR.
تكوين سيمافور:
لاستخدام أي كائن kernel ، علينا أولاً إنشاؤه. لإنشاء إشارة ثنائية ، استخدم vSemaphoreCreateBinary ().
لا تأخذ واجهة برمجة التطبيقات هذه أي معلمة وتقوم بإرجاع متغير من النوع SemaphoreHandle_t. يتم إنشاء اسم المتغير الشامل sema_v لتخزين الإشارة.
SemaphoreHandle_t sema_v؛ sema_v = xSemaphoreCreateBinary () ،
إعطاء إشارة:
لإعطاء إشارة ، هناك نسختان - أحدهما للمقاطعة والآخر للمهمة العادية.
- xSemaphoreGive (): تأخذ واجهة برمجة التطبيقات هذه وسيطة واحدة فقط وهي الاسم المتغير للسيمافور مثل sema_v كما هو مذكور أعلاه أثناء إنشاء إشارة. يمكن استدعاؤها من أي مهمة عادية تريد مزامنتها.
- xSemaphoreGiveFromISR (): هذا هو إصدار API المحمي بالمقاطعة من xSemaphoreGive (). عندما نحتاج إلى مزامنة ISR والمهمة العادية ، فيجب استخدام xSemaphoreGiveFromISR () من وظيفة ISR.
أخذ إشارة:
لأخذ إشارة ، استخدم دالة API xSemaphoreTake (). تأخذ واجهة برمجة التطبيقات هذه معلمتين.
xSemaphoreTake (SemaphoreHandle_t xSemaphore ، TickType_t xTicksToWait) ؛
xSemaphore: اسم الإشارة التي سيتم أخذها في حالتنا sema_v.
xTicksToWait: هذا هو الحد الأقصى لمقدار الوقت الذي تنتظره المهمة في حالة الحظر حتى تصبح الإشارة متاحة. في مشروعنا ، سنقوم بتعيين xTicksToWait إلى portMAX_DELAY لجعل المهمة_1 تنتظر إلى أجل غير مسمى في حالة الحظر حتى يتوفر sema_v.
الآن ، دعنا نستخدم واجهات برمجة التطبيقات هذه ونكتب رمزًا لأداء بعض المهام.
هنا يتم توصيل زر ضغط واحد واثنين من مصابيح LED. سيعمل زر الضغط كزر مقاطعة متصل بالدبوس 2 من Arduino Uno. عند الضغط على هذا الزر ، سيتم إنشاء مقاطعة وسيتم تشغيل مؤشر LED المتصل بالطرف 8 وعندما تضغط عليه مرة أخرى سيتم إيقاف تشغيله.
لذلك ، عند الضغط على الزر ، سيتم استدعاء xSemaphoreGiveFromISR () من وظيفة ISR وسيتم استدعاء وظيفة xSemaphoreTake () من وظيفة TaskLED.
لجعل النظام يبدو متعدد المهام ، قم بتوصيل مصابيح LED الأخرى بالطرف 7 الذي سيكون في حالة وميض دائمًا.
شرح كود سيمافور
لنبدأ في كتابة الكود عن طريق فتح Arduino IDE
1. أولاً ، قم بتضمين ملف الرأس Arduino_FreeRTOS.h . الآن ، إذا تم استخدام أي كائن kernel مثل queue semaphore ، فيجب أيضًا تضمين ملف رأس له.
# تضمين # تضمين
2. قم بتعريف متغير من نوع SemaphoreHandle_t لتخزين قيم semaphore.
SemaphoreHandle_t interruptSemaphore؛
3. في الإعداد الباطل () ، قم بإنشاء مهمتين (TaskLED و TaskBlink) باستخدام واجهة برمجة تطبيقات xTaskCreate () ثم قم بإنشاء إشارة باستخدام xSemaphoreCreateBinary (). أيضًا ، قم بتكوين الدبوس 2 كمدخل وتمكين المقاوم الداخلي للسحب وإرفاق دبوس المقاطعة. أخيرًا ، ابدأ المجدول كما هو موضح أدناه.
إعداد باطل () { pinMode (2، INPUT_PULLUP) ؛ xTaskCreate (TaskLed، "Led"، 128، NULL، 0، NULL) ؛ xTaskCreate (TaskBlink ، "LedBlink" ، 128 ، NULL ، 0 ، NULL) ؛ interruptSemaphore = xSemaphoreCreateBinary () ، if (interruptSemaphore! = NULL) { attachInterrupt (digitalPinToInterrupt (2)، debounceInterrupt، LOW)؛ } }
4. الآن ، قم بتنفيذ وظيفة ISR. قم بإنشاء دالة وقم بتسميتها بنفس الوسيطة الثانية للدالة attachInterrupt () . لجعل المقاطعة تعمل بشكل صحيح ، تحتاج إلى إزالة مشكلة التنبيه للزر الانضغاطي باستخدام وظيفة المللي أو الميكرو وعن طريق ضبط وقت التصحيح. من هذه الوظيفة ، قم باستدعاء وظيفة interruptHandler () كما هو موضح أدناه.
debouncing_time طويلة = 150 ؛ متقلبة غير موقعة طويلة last_micros ؛ باطل debounceInterrupt () { if ((long) (micros () - last_micros)> = debouncing_time * 1000) { interruptHandler () ؛ last_micros = ميكرو () ، } }
في دالة interruptHandler () ، قم باستدعاء xSemaphoreGiveFromISR () API.
void interruptHandler () { xSemaphoreGiveFromISR (interruptSemaphore، NULL) ، }
ستعطي هذه الوظيفة إشارة إلى TaskLed لتشغيل مؤشر LED.
5. قم بإنشاء دالة TaskLed وداخل حلقة while ، قم باستدعاء xSemaphoreTake () API وتحقق مما إذا تم أخذ الإشارة بنجاح أم لا. إذا كانت تساوي pdPASS (أي 1) ، فقم بتبديل مؤشر LED كما هو موضح أدناه.
باطل TaskLed (باطل * pvParameters) { (باطل) pvParameters ؛ pinMode (8 ، الإخراج) ؛ while (1) { if (xSemaphoreTake (interruptSemaphore، portMAX_DELAY) == pdPASS) { digitalWrite (8،! digitalRead (8)) ؛ } } }
6. أيضًا ، قم بإنشاء وظيفة وميض LED آخر متصل بالدبوس 7.
باطل TaskLed1 (باطل * pvParameters) { (باطل) pvParameters ؛ pinMode (7 ، الإخراج) ؛ while (1) { digitalWrite (7، HIGH) ؛ vTaskDelay (200 / portTICK_PERIOD_MS) ، digitalWrite (7 ، منخفض) ؛ vTaskDelay (200 / portTICK_PERIOD_MS) ، } }
7. ستظل وظيفة الحلقة الفارغة فارغة. لا تنسى ذلك.
حلقة فارغة() {}
هذا كل شيء ، يمكن العثور على الكود الكامل في نهاية هذا البرنامج التعليمي. الآن ، قم بتحميل هذا الرمز وقم بتوصيل مصابيح LED وزر الضغط بـ Arduino UNO وفقًا لمخطط الدائرة.
مخطط الرسم البياني
بعد تحميل الكود ، سترى أن مؤشر LED يومض بعد 200 مللي ثانية وعندما يتم الضغط على الزر ، سيتوهج مؤشر LED الثاني على الفور كما هو موضح في الفيديو الوارد في النهاية.
بهذه الطريقة ، يمكن استخدام الإشارات في FreeRTOS مع Arduino حيث تحتاج إلى تمرير البيانات من مهمة إلى أخرى دون أي خسارة.
الآن ، دعنا نرى ما هو Mutex وكيفية استخدامه FreeRTOS.
ما هو موتكس؟
كما هو موضح أعلاه ، فإن semaphore هي آلية إشارات ، وبالمثل ، فإن Mutex عبارة عن آلية قفل على عكس السيمافور التي لها وظائف منفصلة للزيادة والتناقص ولكن في Mutex ، تأخذ الوظيفة وتعطي نفسها. إنها تقنية لتجنب فساد الموارد المشتركة.
لحماية المورد المشترك ، يقوم المرء بتعيين بطاقة رمز (كائن المزامنة) للمورد. يمكن لأي شخص لديه هذه البطاقة الوصول إلى المورد الآخر. يجب على الآخرين الانتظار حتى يتم إرجاع البطاقة. بهذه الطريقة ، يمكن لمورد واحد فقط الوصول إلى المهمة بينما ينتظر الآخرون فرصتهم.
دعنا نفهم لغة Mutex في FreeRTOS بمساعدة مثال.
لدينا هنا ثلاث مهام ، واحدة لطباعة البيانات على شاشة LCD ، والثانية لإرسال بيانات LDR إلى مهمة LCD والمهمة الأخيرة لإرسال بيانات درجة الحرارة على شاشة LCD. لذلك هناك مهمتان تشتركان في نفس المورد ، أي LCD. إذا كانت مهمة LDR ومهمة درجة الحرارة ترسل البيانات في وقت واحد ، فقد تكون إحدى البيانات تالفة أو مفقودة.
لذلك لحماية فقدان البيانات ، نحتاج إلى قفل مورد شاشة LCD للمهمة 1 حتى تنتهي من مهمة العرض. ثم سيتم فتح مهمة LCD ومن ثم يمكن لـ Task2 أداء عملها.
يمكنك ملاحظة عمل Mutex و semaphores في الرسم البياني أدناه.
كيفية استخدام Mutex في FreeRTOS؟
تستخدم كائنات المزامنة أيضًا بنفس طريقة استخدام الإشارات. أولاً ، قم بإنشائه ، ثم أعطه وخذه باستخدام واجهات برمجة التطبيقات ذات الصلة.
إنشاء كائن المزامنة:
لإنشاء Mutex ، استخدم xSemaphoreCreateMutex () API . كما يوحي اسمها ، Mutex هو نوع من الإشارات الثنائية. يتم استخدامها في سياقات وأغراض مختلفة. الإشارة الثنائية هي لمزامنة المهام بينما يتم استخدام Mutex لحماية مورد مشترك.
لا تأخذ واجهة برمجة التطبيقات هذه أي وسيطة وتقوم بإرجاع متغير من النوع SemaphoreHandle_t . إذا تعذر إنشاء كائن المزامنة (mutex) ، فإن xSemaphoreCreateMutex () ترجع NULL.
SemaphoreHandle_t mutex_v؛ mutex_v = xSemaphoreCreateMutex () ،
أخذ Mutex:
عندما تريد مهمة الوصول إلى مورد ، فإنها ستستغرق كائن Mutex باستخدام واجهة برمجة تطبيقات xSemaphoreTake () . إنه نفس الإشارة الثنائية. يأخذ أيضا اثنين من المعلمات.
xSemaphore: اسم Mutex الذي سيتم أخذه في حالتنا mutex_v .
xTicksToWait: هذا هو الحد الأقصى لمقدار الوقت الذي ستنتظره المهمة في حالة الحظر حتى يصبح Mutex متاحًا. في مشروعنا ، سنقوم بتعيين xTicksToWait إلى portMAX_DELAY لجعل المهمة_1 تنتظر إلى أجل غير مسمى في حالة الحظر حتى يتوفر mutex_v .
إعطاء Mutex:
بعد الوصول إلى المورد المشترك ، يجب أن تعيد المهمة كائن المزامنة حتى تتمكن المهام الأخرى من الوصول إليه. يتم استخدام واجهة برمجة تطبيقات xSemaphoreGive () لإعادة Mutex.
تأخذ الدالة xSemaphoreGive () وسيطة واحدة فقط وهي Mutex التي يجب تقديمها في حالتنا mutex_v.
باستخدام واجهات برمجة التطبيقات المذكورة أعلاه ، دعنا ننفذ Mutex في كود FreeRTOS باستخدام Arduino IDE.
شرح كود موتكس
الهدف هنا من هذا الجزء هو استخدام جهاز العرض التسلسلي كمورد مشترك ومهمتين مختلفتين للوصول إلى الشاشة التسلسلية لطباعة بعض الرسائل.
1. ستبقى ملفات الرأس كما هي للإشارة.
# تضمين # تضمين
2. قم بتعريف متغير من نوع SemaphoreHandle_t لتخزين قيم Mutex.
SemaphoreHandle_t mutex_v؛
3. في الإعداد الباطل () ، قم بتهيئة الشاشة التسلسلية بمعدل باود 9600 وأنشئ مهمتين (Task1 و Task2) باستخدام xTaskCreate () API. ثم قم بإنشاء Mutex باستخدام xSemaphoreCreateMutex (). قم بإنشاء مهمة ذات أولويات متساوية ثم حاول لاحقًا اللعب بهذا الرقم.
إعداد باطل () { Serial.begin (9600) ؛ mutex_v = xSemaphoreCreateMutex () ، if (mutex_v == NULL) { Serial.println ("لا يمكن إنشاء Mutex") ؛ } xTaskCreate (Task1، "Task 1"، 128، NULL، 1، NULL)؛ xTaskCreate (Task2، "Task 2"، 128، NULL، 1، NULL) ؛ }
4. الآن ، قم بعمل وظائف مهمة لـ Task1 و Task2. في حين حلقة وظيفة مهمة، قبل طباعة رسالة على رصد المسلسل علينا أن نلقي المزامنة باستخدام xSemaphoreTake () ثم طباعة الرسالة ثم إعادة المزامنة باستخدام xSemaphoreGive (). ثم أعط بعض التأخير.
Task1 باطل (باطل * pvParameters) { while (1) { xSemaphoreTake (mutex_v، portMAX_DELAY) ؛ Serial.println ("مرحبًا من Task1") ؛ xSemaphoreGive (mutex_v) ؛ vTaskDelay (pdMS_TO_TICKS (1000)) ، } }
وبالمثل ، قم بتنفيذ وظيفة Task2 بتأخير قدره 500 مللي ثانية.
5. الحلقة الفارغة () ستبقى فارغة.
الآن ، قم بتحميل هذا الرمز على Arduino UNO وافتح الشاشة التسلسلية.
سترى الرسائل تطبع من المهمة 1 والمهمة 2.
لاختبار عمل Mutex ، ما عليك سوى التعليق على xSemaphoreGive (mutex_v) ؛ من أي مهمة. يمكنك أن ترى أن البرنامج معلق على آخر رسالة مطبوعة .
هذه هي الطريقة التي يمكن بها تنفيذ Semaphore و Mutex في FreeRTOS مع Arduino. لمزيد من المعلومات حول Semaphore و Mutex ، يمكنك زيارة الوثائق الرسمية لـ FreeRTOS.
فيما يلي الرموز الكاملة والفيديو الخاص بشركة Semaphore و Mutes.