برنامج تضمين C ++ لجميع أنظمة التشغيل في الوقت الفعلي لـ CortexM4

الصورة

لقد تحدثت بالفعل عن كيفية استخدام FreeRtos للمشاريع المكتوبة بلغة C ++ في المقالة STM32 و C ++ و FreeRTOS. التنمية من الصفر. الجزء الأول منذ ذلك الحين ، ومع مرور 3 سنوات ، فقد كبرت في العمر ، وفقدت مجموعة من الاتصالات العصبية ، لذلك قررت أن أزعزع الأيام القديمة من أجل استعادة هذه الاتصالات وإكتساح الـ RTOS الشهير "أي". هذه بالطبع نكتة ، وضعت عمدا "الجميع" بين علامتي اقتباس ، ولكن هناك بعض الحقيقة في كل نكتة.

إذن ، ما هي المهمة ولماذا هي ذات صلة على الإطلاق؟ في الوقت الحالي ، هناك مليون نظام تشغيل مختلف مكتوب بلغة C - لا أريد أن أختار لكل ذوق ، مدفوع ، مجاني ، صغير ، كبير ... ولكن بالنسبة للمشاريع التي أشارك فيها ، ليست هناك حاجة إلى كل هذه الشرائح من أنظمة التشغيل المختلفة ، والوظائف الأساسية مثل المهمة كافية والأحداث وإشعار المهمة والقسم الحرج وكائنات المزامنة والأشارات (على الرغم من أنني أحاول عدم استخدامها) والصفوف. وكل هذا مطلوب بشكل بسيط إلى حد ما ، دون أي زخرفة.

في رأيي ، فإن OSRV MAX المحلي ، المكتوب بلغة C ++ ، مناسب تمامًا لمشاريعي ويسرني استخدامه.

لكن المشكلة هي أن أجهزتنا يجب أن تتوافق مع معيار IEC_61508 ، أحد متطلباته هو تطبيق E.29 للمكتبة المستهدفة المثبتة . حسنًا ، أو بكلمات بسيطة ، إذا صنعت جهازًا يفي بمستوى SIL3 ، فيرجى استخدام مكتبات تتوافق مع هذا المستوى ويتم اختبارها بمرور الوقت.

فيما يتعلق بمهمتنا ، هذا يعني أنه من الممكن استخدام MAX MAX RTOS لمثل هذه الأجهزة ، ولكن لن يتم إضافة نقاط الموثوقية. لذلك ، يصنع مصنعو RTOS إصدارات خاصة من أنظمة التشغيل الخاصة بهم والتي تتوافق مع معايير IEC_61508 ، على سبيل المثال ، يحتوي FreeRTOS على استنساخ SafeRTOS ، ويحتوي embOs على نسخة آمنة من embos ، وبالطبع فإن الشركات المصنعة تحقق أموالًا جيدة جدًا من هذا ، لأن التراخيص لأنظمة التشغيل هذه تكلف عدة آلاف ، أو حتى عشرات الف دولار.

بالمناسبة ، مثال جيد هو مترجم IAR ، الترخيص الذي يكلف حوالي 1500 دولار ، لكن إصدارات IAR المعتمدة تكلف بالفعل حوالي 10000 دولار ، على الرغم من أنني راجعت في العديد من المشاريع - ملف الإخراج للإصدار بدون شهادة ومع شهادة متطابق تمامًا. حسنًا ، أنت تفهم أنك بحاجة إلى الدفع مقابل السلام.

لذلك ، استخدمنا أولاً نظام تشغيل واحد ، ثم بدأت باستخدام FreeRTOS لاحتياجاتي ، ثم انتقلنا إلى نظام آخر ، بشكل عام ، كان علينا باستمرار إعادة كتابة التعليمات البرمجية النهائية. بالإضافة إلى ذلك ، أود أن تبدو جميلة وبسيطة ، بحيث يمكن لأي شخص أن يفهم من خلال الرمز ما يحدث ، ثم سيكون دعم التعليمات البرمجية مهمة بسيطة للطلاب والممارسين ، وسيتمكن المعلمون من مواصلة العمل على الأجهزة المبتكرة ، بدلاً من فهم كومة المعكرونة . بشكل عام ، أريد أن أرى شيئًا مثل هذا متحجرًا:

الصورة

حسنا ، أو هكذا ...

الصورة

لذلك ، قررت أن أكتب غلافًا يناسب كل من FreeRTOS ، ويقول embOS ، حسنًا ، لكل شخص آخر أيضًا :) وبداية حددت ما احتاجه لتحقيق السعادة الكاملة:

  • المهام
  • أقسام حرجة
  • الأحداث وإخطار المهمة
  • الإشارات والموتكسات
  • قوائم الانتظار

يجب أن يكون الغلاف SIL3 أيديولوجيًا ، ويفرض هذا المستوى الكثير من جميع أنواع الأشياء الموصى بها العالية ، وإذا اتبعتها تمامًا ، فقد اتضح أنه من الأفضل عدم كتابة الرمز على الإطلاق.

لكن حقيقة أن المعيار يحكم مجموعة من القواعد ، أو بالأحرى التوصيات ، لا يعني أنه لا يمكن انتهاكها - يمكنك ذلك ، ولكن عليك اتباع أكبر عدد ممكن من التوصيات للحصول على المزيد من النقاط. لذلك ، قررت بعض القيود الهامة:

  • لا وحدات ماكرو ، حسنا ، باستثناء الحماية ضد التضمين المزدوج لملفات الرأس. وحدات الماكرو شريرة ، إذا حسبت مقدار الوقت الذي تم قضاؤه في البحث عن الأخطاء المتعلقة بوحدات الماكرو ، فقد اتضح أن الكون ليس قديمًا جدًا ، ومقدار ما يمكن فعله خلال هذا الوقت ، ربما يكون من الأفضل حظرها على المستوى التشريعي ، حيث أن السيول منعت أو احصل على مكافأة لكل ماكرو تكتبه
  • لا تستخدم المؤشرات بالطبع كلما أمكن ذلك. يمكن للمرء أن يحاول عدم استخدامها على الإطلاق ، ولكن لا تزال هناك أماكن بدونها لا توجد طريقة. على أي حال ، لا ينبغي لمستخدم الغلاف ، إن أمكن ، أن يعرف ما هو المؤشر ، لأنه لم يسمع عنها إلا من جده ، لأنه الآن يعمل فقط مع الروابط
  • عدم استخدام تخصيص الذاكرة الديناميكية - كل شيء واضح ، فقط باستخدام كومة تؤدي ، أولاً ، إلى الحاجة إلى حجز ذاكرة الوصول العشوائي لهذا الكومة ، وثانيًا ، مع الاستخدام المتكرر للكومة ، يتم إلغاء تجزئتها ويتم إنشاء كائنات جديدة عليها لفترة أطول وأطول لفترة أطول. لذلك ، في الواقع ، قمت بتكوين FreeRTOS فقط على الذاكرة المخصصة بشكل ثابت عن طريق تعيين configSUPPORT_STATIC_ALLOCATION 1 . ولكن إذا كنت ترغب في العمل في الوضع الافتراضي. وبشكل افتراضي ، يستخدم FreeRTOS الذاكرة المخصصة ديناميكيًا لإنشاء عناصر نظام التشغيل ، ثم قم فقط بضبط configSUPPORT_STATIC_ALLOCATION 0 ، و
    configSUPPORT_DYNAMIC_ALLOCATION 1 ولا تنس ربط توصيل تطبيق mallocs و calloc الخاص بك من مدير الذاكرة ، على سبيل المثال ، هذا الملف هو FreeRtos / portable / MemMang / heap_1.c. ولكن ضع في اعتبارك أنه سيتعين عليك تخصيص ذاكرة الوصول العشوائي مع احتياطي لمجموعة ، حيث لن تتمكن من حساب مقدار ذاكرة الوصول العشوائي المطلوب بالضبط ، مع جميع الإعدادات (في وضع الخمول ، ومهمة مؤقت البرنامج قيد التشغيل ، ومهمتي ، وقوائم الانتظار ، وحجم قائمة الانتظار للموقتات 10 و لذا ، دعنا نقول أنها بالتأكيد ليست الإعدادات المثلى) التي عملت عندما خصصت الذاكرة على النحو التالي:
    357 7 بايت من ذاكرة الكود للقراءة فقط
    535 بايت من ذاكرة البيانات للقراءة فقط
    6،053 بايت من ذاكرة بيانات القراءة والكتابة

    تخصيص الذاكرة الثابتة هو "صغير" أكثر إحكاما:
    7،329 بايت من ذاكرة التعليمات البرمجية للقراءة فقط
    535 بايت من ذاكرة البيانات للقراءة فقط
    3،877 بايت من ذاكرة بيانات القراءة والكتابة

    قد تعتقد ، "رائع ... نفسك" ، لكننا الآن غير مهتمين بالسؤال الذي تمت صياغته في المقالة "لقد خصصت ما يصل إلى 3 كيلوبايت لنظام التشغيل وأطلقت 3 مهام فقط مع مكدس 128B ، ولسبب ما لا توجد بالفعل ذاكرة كافية للرابع" ، في هذه الحالة ، قمت بذلك عن قصد ، للتوضيح ، لإظهار الفرق بين تخصيص الذاكرة الديناميكي والثابت مع نفس الإعدادات.
  • لا تلقي أنواع ، إن أمكن. أنواع الظلال لأنواع أخرى في حد ذاتها تعني حقيقة أن شيئًا ما خطأ في التصميم ، ولكن كالعادة ، لا يزال عليك في بعض الأحيان طرحه للراحة (على سبيل المثال ، يجب أن يلقي التعداد إلى أعداد صحيحة) ، وأحيانًا لا يمكنك الاستغناء عنه هذا ، ولكن يجب تجنب ذلك.
  • البساطة والراحة . بالنسبة لمستخدم الغلاف ، يجب إخفاء جميع الصعوبات ، حتى لا تكون حياته نفطية ، ولا يريد تعقيدها بعد - لقد أنشأ المهمة ، نفذ كل ما هو مطلوب فيه ، بدأها وتركها للاستمتاع بالحياة.

سنبدأ من هذا ، لذا وضعنا لأنفسنا مهمة إنشاء مهمة (اتضح مباشرة من سلسلة "ممنوع الحظر").

إنشاء المهام


من خلال البحث الطويل ، علماء بريطانيون ( الحقيقة الكاملة عن RTOS من Colin Walls. المادة رقم 4. المهام ، وتبديل السياق والانقطاعات ) (بالمناسبة ، إذا لم تكن تعرف ، تم اختراع مجمع ARM من قبل عالم بريطاني ، وهو أمر لم أفاجأ به أيضًا مرة واحدة :)) ، وهكذا اكتشف العلماء البريطانيون أنه بالنسبة لغالبية "كل" RTOS ، فإن المهمة لها اسم ، أو تكديس ، أو حجم تكديس ، أو "وحدة تحكم" ، أو معرف أو مؤشر إلى "وحدة تحكم" ، أو أولوية ، أو وظيفة يتم تنفيذها في المهمة . هذا كل شيء ، وكان من الممكن حشره كله في فصل دراسي ، ولكن كان من الصحيح إذا كتبنا نظام تشغيل معك ، لكننا قمنا بعمل غلاف ، لذلك لا يوجد معنى في تخزين كل هذه الأشياء في غلاف ، كل هذا سيتم من أجلك بواسطة نظام SIL3 الأيديولوجي نختتم. في الواقع ، نحن بحاجة فقط إلى وظيفة يتم تنفيذها في المهمة وهيكل يخزن "وحدة التحكم" ، والتي يتم تعبئتها عند إنشاء المهمة ومعرف المهمة . لذلك ، يمكن أن تبدو فئة المهمة ، دعنا نسميها الموضوع ، بسيطة للغاية:

class Thread { public: virtual void Execute() = 0 ; private: tTaskHandle taskHandle ; tTaskContext context ; } ; 

أريد فقط أن أعلن فئة مهمتي حيث يمكنني تنفيذ كل ما هو مطلوب ثم تمرير المؤشر إلى كائن هذه الفئة إلى الغلاف ، مما سيؤدي إلى إنشاء مهمة باستخدام RTOS API حيث سيتم تشغيل طريقة التنفيذ () :

 class MyTask : public Thread { public: virtual void Execute() override { while(true) { //do something.. } } ; using tMyTaskStack = std::array<OsWrapper::tStack, static_cast<tU16>(OsWrapper::StackDepth::minimal)> ; inline static tMyTaskStack Stack; //!C++17 } ; MyTask myDesiredTask int main() { Rtos::CreateThread(myTask, MyTask::Stack.data(), "myTask") ; } 

في "كل" RTOS ، من أجل إنشاء المهمة ، من الضروري تمرير المؤشر إلى دالة سيتم تشغيلها بواسطة المجدول. في حالتنا ، هذه هي الدالة Execute () ، لكن لا يمكنني تمرير مؤشر إلى هذه الطريقة ، لأنها ليست ثابتة. لذلك ، ننظر في كيفية إنشاء مهمة في واجهة برمجة التطبيقات لأنظمة تشغيل "جميع" ونلاحظ أنه يمكننا إنشاء مهمة بتمرير معلمة إلى وظيفة المهمة ، على سبيل المثال ، لتضمين ما يلي:

 void OS_TASK_CreateEx( OS_TASK* pTask, const char* pName, OS_PRIO Priority, void (*pRoutine)(void * pVoid ), void OS_STACKPTR *pStack, OS_UINT StackSize, OS_UINT TimeSlice, void* pContext); 

void * pContext - هذا هو مفتاح الحل. دعنا نحصل على طريقة ثابتة ، وهو مؤشر سنمرره كمؤشر إلى أسلوب يسمى بواسطة المجدول ، وكمعلمة ، سنمرر مؤشرًا إلى كائن من نوع Thread حيث يمكننا استدعاء الأسلوب Execute () مباشرة. هذه هي اللحظة التي لا توجد فيها طريقة بدون مؤشر وإلقاء للأنواع ، ولكن سيتم إخفاء هذا الرمز عن المستخدم:

 static void Run(void *pContext ) { static_cast<Thread*>(pContext)->Execute() ; } 

على سبيل المثال مثل خوارزمية العملية ، يبدأ المجدول طريقة التشغيل ، ويتم تمرير مؤشر إلى كائن من نوع مؤشر الترابط إلى أسلوب التشغيل . يستدعي الأسلوب Run مباشرة الأسلوب Execute () ، وهو كائن معين لفئة Thread ، وهو مجرد تنفيذ للمهمة.

تم حل المشكلة تقريبًا ، والآن نحن بحاجة إلى تنفيذ الأساليب. تحتوي جميع أنظمة التشغيل على واجهات برمجة تطبيقات مختلفة ، لذلك ، على سبيل المثال ، لتنفيذ وظيفة إنشاء المهام لـ embOS ، يجب عليك استدعاء طريقة OS_TASK_CreateEx (..) الباطلة ، وبالنسبة لـ FreeRTOS في وضع تخصيص الذاكرة الديناميكية ، فهذه هي xTaskCreate (..) وعلى الرغم من أن لها نفس الجوهر نفس ، ولكن بناء الجملة والمعلمات مختلفة. لكننا لا نريد تشغيل الملفات وكتابة التعليمات البرمجية لكل طريقة من طرق الفصل في كل مرة لنظام تشغيل جديد ، لذلك نحتاج إلى وضع هذا بطريقة أو بأخرى في ملف واحد و ... تنفيذه على شكل وحدات ماكرو. رائع ، ولكن توقف ، لقد منعت وحدات الماكرو لنفسي - أحتاج إلى نهج مختلف.

أبسط شيء حدث لي كان إنشاء ملف منفصل لكل نظام تشغيل مع وظائف مضمنة. إذا أردنا استخدام أي نظام تشغيل آخر ، فسنحتاج فقط إلى تنفيذ كل من هذه الوظائف باستخدام واجهة برمجة التطبيقات لنظام التشغيل هذا. تم تحويل ملف rtosFreeRtos.cpp التالي

 #include "rtos.hpp" //For FreeRTOS functions prototypes #include <FreeRTOS.h> //For xTaskCreate #include <task.h> namespace OsWrapper { void wCreateThread(Thread & thread, const char * pName, ThreadPriority prior,const tU16 stackDepth, tStack *pStack) { #if (configSUPPORT_STATIC_ALLOCATION == 1) if (pStack != nullptr) { thread.handle = xTaskCreateStatic(static_cast<TaskFunction_t>(Rtos::Run), pName, stackDepth, &thread, static_cast<uint32_t>(prior), pStack, &thread.taskControlBlock); } #else thread.handle = (xTaskCreate(static_cast<TaskFunction_t>(Rtos::Run), pName, stackDepth, &thread, static_cast<uint32_t>(prior), &thread.handle) == pdTRUE) ? thread.handle : nullptr ; #endif } 

قد يبدو ملف embOS rtosEmbOS.cpp هو نفسه تمامًا

 #include "rtos.hpp" //For embOS functions prototypes #include <rtos.h> namespace OsWrapper { void wCreateThread(Thread &thread, const char * pName, ThreadPriority prior,const tU16 stackDepth, tStack *pStack) { constexpr OS_UINT timeSliceNull = 0 ; if (pStack != nullptr) { OS_CreateTaskEx(&(thread.handle), pName, static_cast<OS_PRIO>(prior), Rtos::Run, pStack, ((stackSize == 0U) ? sizeof(pStack) : stackSize), timeSliceNull, &thread) ; } } 

تختلف أنواع أنظمة التشغيل المختلفة أيضًا ، خاصة هيكل سياق المهمة ، لذلك دعونا ننشئ ملف rtosdefs.hpp باستخدام الأسماء المستعارة المجمعة الخاصة بنا.

 #include <FreeRTOS.h> //For TaskHandle_t namespace OsWrapper { using tTaskContext = StaticTask_t; using tTaskHandle = TaskHandle_t; using tStack = StackType_t ; } 

بالنسبة إلى EmbOS ، قد يبدو كالتالي:

 #include <rtos.h> //For OS_TASK namespace OsWrapper { using tTaskContext = OS_TASK; using tTaskHandle = OS_TASK; using tStack = tU16 //   void,      tU16 ; } 

نتيجة لذلك ، لإجراء تعديلات تحت أي RTOS أخرى ، يكفي إجراء تغييرات فقط في هذين الملفين rtosdefs.cpp و rtos.cpp. الآن تبدو فصول الخيط و Rtos مثل الصور c

الصورة

تشغيل أنظمة التشغيل وإنهاء المهمة


بالنسبة إلى Cortex M4 ، تستخدم جميع أنظمة التشغيل "3" مقاطعات ، ومؤقت علامة النظام ، واستدعاء خدمة النظام عبر تعليمات SWI ، وطلب معلق لمقاطعات خدمة النظام ، والتي تم اختراعها بشكل أساسي لنظام RTOS. تستخدم بعض أنظمة RTOS أيضًا مقاطعات النظام الأخرى ، ولكنها ستكون كافية لمعظم أنظمة تشغيل "جميع". وإذا لم يكن الأمر كذلك ، فسيكون من الممكن الإضافة ، لذا حدد فقط ثلاثة معالجات لهذه المقاطعات ولكي تبدأ RTOS ، نحتاج إلى طريقة بدء أخرى:

 static void HandleSvcInterrupt() ; static void HandleSvInterrupt() ; static void HandleSysTickInterrupt() ; static void Start() ; 

أول ما أحتاجه والذي لا يمكنني العيش بدونه ، ما أحلم به هو آلية الإخطار بالمهام. بشكل عام ، أحب البرمجة التي تعتمد على الأحداث ، لذا أحتاج إلى تنفيذ غلاف سريع لإبلاغ المهام.

كل شيء تبين أنه بسيط للغاية ، أي نظام تشغيل يمكنه القيام بذلك ، حسنًا ، ربما باستثناء uc-OS-II و III ، على الرغم من أنني ربما لم أقرأه جيدًا ، ولكن في رأيي ، فإن آلية الأحداث صعبة بشكل عام ، ولكن حسنًا ، "كل شيء" هو الباقي يمكنهم بالتأكيد.

لإعلام المهمة ، ما عليك سوى إرسال الحدث ليس إلى الفراغ ، ولكن على وجه التحديد إلى المهمة ، لهذا ، يجب أن يكون لطريقة الإعلام مؤشر إلى سياق المهمة أو معرف المهمة. أقوم فقط بتخزينها في فئة Thread ، مما يعني أنه يجب أيضًا أن يكون لفئة Thread طريقة تنبيه. يجب أن تكون هناك طريقة لانتظار تنبيه. في الوقت نفسه ، نضيف طريقة السكون (..) ، التي توقف تنفيذ المهمة الاستدعاء مؤقتًا. الآن يبدو كلا الفصلين كما يلي:

الصورة

rtos.hpp
 /******************************************************************************* * Filename : Rtos.hpp * * Details : Rtos class is used to create tasks, work with special Rtos * functions and also it contains a special static method Run. In this method * the pointer on Thread should be pass. This method is input point as * the task of Rtos. In the body of the method, the method of concrete Thread * will run. *******************************************************************************/ #ifndef __RTOS_HPP #define __RTOS_HPP #include "thread.hpp" // for Thread #include "../../Common/susudefs.hpp" #include "FreeRtos/rtosdefs.hpp" namespace OsWrapper { extern void wCreateThread(Thread &, const char *, ThreadPriority, const tU16, tStack *) ; extern void wStart() ; extern void wHandleSvcInterrupt() ; extern void wHandleSvInterrupt() ; extern void wHandleSysTickInterrupt() ; extern void wEnterCriticalSection(); extern void wLeaveCriticalSection(); class Rtos { public: static void CreateThread(Thread &thread , tStack * pStack = nullptr, const char * pName = nullptr, ThreadPriority prior = ThreadPriority::normal, const tU16 stackDepth = static_cast<tU16>(StackDepth::minimal)) ; static void Start() ; static void HandleSvcInterrupt() ; static void HandleSvInterrupt() ; static void HandleSysTickInterrupt() ; friend void wCreateThread(Thread &, const char *, ThreadPriority, const tU16, tStack *); private: //cstat !MISRAC++2008-7-1-2 To prevent reinterpet_cast in the CreateTask static void Run(void *pContext ) { static_cast<Thread*>(pContext)->Execute() ; } } ; } ; #endif // __RTOS_HPP 


خيط. hpp
 /******************************************************************************* * Filename : thread.hpp * * Details : Base class for any Taskis which contains the pure virtual * method Execute(). Any active classes which will have a method for running as * a task of RTOS should inherit the Thread and override the Execute() method. * For example: * class MyTask : public OsWrapper::Thread * { * public: * virtual void Execute() override { * while(true) { * //do something.. * } * } ; * *******************************************************************************/ #ifndef __THREAD_HPP #define __THREAD_HPP #include "FreeRtos/rtosdefs.hpp" #include "../../Common/susudefs.hpp" namespace OsWrapper { extern void wSleep(const tTime) ; extern void wSleepUntil(tTime &, const tTime) ; extern tTime wGetTicks() ; extern void wSignal(tTaskHandle const &, const tTaskEventMask) ; extern tTaskEventMask wWaitForSignal(const tTaskEventMask, tTime) ; constexpr tTaskEventMask defaultTaskMaskBits = 0b010101010 ; enum class ThreadPriority { clear = 0, lowest = 10, belowNormal = 20, normal = 30, aboveNormal = 80, highest = 90, priorityMax = 255 } ; enum class StackDepth: tU16 { minimal = 128U, medium = 256U, big = 512U, biggest = 1024U }; class Thread { public: virtual void Execute() = 0 ; inline tTaskHandle GetTaskHanlde() const { return handle; } static void Sleep(const tTime timeOut = 1000ms) { wSleep(timeOut) ; }; inline void Signal(const tTaskEventMask mask = defaultTaskMaskBits) { wSignal(handle, mask); }; inline tTaskEventMask WaitForSignal(tTime timeOut = 1000ms, const tTaskEventMask mask = defaultTaskMaskBits) { return wWaitForSignal(mask, timeOut) ; } friend void wCreateThread(Thread &, const char *, ThreadPriority, const tU16, tStack *); private: tTaskHandle handle ; tTaskContext context ; } ; } ; #endif // __THREAD_HPP 


بدأت في تنفيذه ، وهنا كانت المشكلة الأولى في انتظاري ، اتضح أن نظام التشغيل "أي" يستدعي وظائفه من المقاطعات بطرق مختلفة. على سبيل المثال ، يحتوي FreeRTOS على تطبيقات خاصة لوظائف تنفيذها من المقاطعات ، على سبيل المثال ، إذا كانت هناك وظيفة xTaskNotify (..) ، فلا يمكنك استدعاءها من مقاطعة ، ولكنك بحاجة إلى استدعاء xTaskNotifyFromISR (..) .
بالنسبة إلى embos ، إذا قمت باستدعاء أي وظيفة من مقاطعة ، يرجى استخدام OS_InInterrupt () عند إدخال مقاطعة و OS_LeaveInterrupt () عند الخروج. اضطررت إلى إنشاء فصل InterruptEntry ، الذي يحتوي فقط على مُنشئ ومدمر:

 namespace OsWrapper { extern void wEnterInterrupt() ; extern void wLeaveInterrupt() ; class InterruptEntry { public: inline InterruptEntry() { wEnterInterrupt() ; } inline ~InterruptEntry() { wLeaveInterrupt() ; } } ; } ; 

يمكنك استخدامه مثل هذا:

 void Button::HandleInterrupt() { const OsWrapper::InterruptEntry ie; EXTI->PR = EXTI_PR_PR13 ; myDesiredTask.Signal(); } void myDesiredTask::Execute() { while(true) { if (WaitForSignal(100000ms) == defaultTaskMaskBits) { GPIOC->ODR ^= (1 << 5) ; } } } ; 

من الواضح ، بالنسبة لـ FreeRTOS ، سيكون كل من المُنشئ والمدمّر فارغًا. وللإخطار ، يمكنك استخدام الوظيفة xTaskNotifyFromISR (..) ، التي تعتبر مكانًا صغيرًا ، بغض النظر عن المكان الذي يتم استدعاؤه منه ، ولكنك لن تفعل ذلك من أجل العالمية. يمكنك بالطبع إنشاء طرق منفصلة للاتصال من المقاطعات ، ولكن الآن قررت أن أفعل ذلك على مستوى العالم.
يمكن تنفيذ نفس الحيلة التي يتم إجراؤها مع InterruptEntry بالقسم الحرج:

 namespace OsWrapper{ class CriticalSection { public: inline CriticalSection() { wEnterCriticalSection() ; } inline ~CriticalSection() { wLeaveCriticalSection() ; } } ; } ; 

الآن فقط أضف تنفيذ الوظائف باستخدام FreeRtos API إلى الملف وقم بتشغيل الفحص ، على الرغم من أنه لا يمكنك تشغيله ، لذا فمن الواضح أنه سيعمل :)
rtosFreeRtos.cpp
 /******************************************************************************* * Filename : rtosFreeRtos.cpp * * Details : This file containce implementation of functions of concrete * FreeRTOS to support another RTOS create the same file with the * same functions but another name< for example rtosEmbOS.cpp and * implement these functions using EmbOS API. * *******************************************************************************/ #include "../thread.hpp" #include "../mutex.hpp" #include "../rtos.hpp" #include "../../../Common/susudefs.hpp" #include "rtosdefs.hpp" #include "../event.hpp" #include <limits> namespace OsWrapper { /***************************************************************************** * Function Name: wCreateThread * Description: Creates a new task and passes a parameter to the task. The * function should call appropriate RTOS API function to create a task. * * Assumptions: RTOS API create task function should get a parameter to pass the * paramete to task. * Some RTOS does not use pStack pointer so it should be set to nullptr * * Parameters: [in] thread - refernce on Thread object * [in] pName - name of task * [in] prior - task priority * [in] stackDepth - size of Stack * [in] pStack - pointer on task stack * Returns: No ****************************************************************************/ void wCreateThread(Thread & thread, const char * pName, ThreadPriority prior, const tU16 stackDepth, tStack *pStack) { #if (configSUPPORT_STATIC_ALLOCATION == 1) if (pStack != nullptr) { thread.handle = xTaskCreateStatic(static_cast<TaskFunction_t>(Rtos::Run), pName, stackDepth, &thread, static_cast<uint32_t>(prior), pStack, &thread.context); } #else thread.handle = (xTaskCreate(static_cast<TaskFunction_t>(Rtos::Run), pName, stackDepth, &thread, static_cast<uint32_t>(prior), &thread.handle) == pdTRUE) ? thread.handle : nullptr ; #endif } /***************************************************************************** * Function Name: wStart() * Description: Starts the RTOS scheduler * * Assumptions: No * Parameters: No * Returns: No ****************************************************************************/ void wStart() { vTaskStartScheduler() ; } /***************************************************************************** * Function Name: wHandleSvcInterrupt() * Description: Handle of SVC Interrupt. The function should call appropriate * RTOS function to handle the interrupt * * Assumptions: No * Parameters: No * Returns: No ****************************************************************************/ void wHandleSvcInterrupt() { vPortSVCHandler() ; } /***************************************************************************** * Function Name: wHandleSvInterrupt() * Description: Handle of SV Interrupt. The function should call appropriate * RTOS function to handle the interrupt * * Assumptions: No * Parameters: No * Returns: No ****************************************************************************/ void wHandleSvInterrupt() { xPortPendSVHandler() ; } /***************************************************************************** * Function Name: wHandleSysTickInterrupt() * Description: Handle of System Timer Interrupt. The function should call * appropriate RTOS function to handle the interrupt * * Assumptions: No * Parameters: No * Returns: No ****************************************************************************/ void wHandleSysTickInterrupt() { xPortSysTickHandler() ; } /***************************************************************************** * Function Name: wSleep() * Description: Suspends the calling task for a specified period of time, * or waits actively when called from main() * * Assumptions: No * Parameters: [in] timeOut - specifies the time interval in system ticks * Returns: No ****************************************************************************/ void wSleep(const tTime timeOut) { vTaskDelay(timeOut) ; } /***************************************************************************** * Function Name: wEnterCriticalSection() * Description: Basic critical section implementation that works by simply * disabling interrupts * * Assumptions: No * Parameters: No * Returns: No ****************************************************************************/ void wEnterCriticalSection() { taskENTER_CRITICAL() ; } /***************************************************************************** * Function Name: wLeaveCriticalSection() * Description: Leave critical section implementation that works by simply * enabling interrupts * * Assumptions: No * Parameters: No * Returns: No ****************************************************************************/ void wLeaveCriticalSection() { taskEXIT_CRITICAL() ; } /**************************************************************************** * Function Name: wEnterInterrupt() * Description: Some RTOS requires to inform the kernel that interrupt code * is executing * * Assumptions: No * Parameters: No * Returns: No ****************************************************************************/ void wEnterInterrupt() { } /**************************************************************************** * Function Name: wLeaveInterrupt() * Description: Some RTOS requires to inform that the end of the interrupt r * outine has been reached; executes task switching within ISR * * Assumptions: No * Parameters: No * Returns: No ****************************************************************************/ void wLeaveInterrupt() { } /**************************************************************************** * Function Name: wSignal() * Description: Signals event(s) to a specified task * * Assumptions: No * Parameters: [in] taskHandle - Reference to the task structure * [in] mask - The event bit mask containing the event bits, * which shall be signaled. * Returns: No ****************************************************************************/ void wSignal(tTaskHandle const &taskHandle, const tTaskEventMask mask) { BaseType_t xHigherPriorityTaskWoken = pdFALSE ; xTaskNotifyFromISR(taskHandle, mask, eSetBits, &xHigherPriorityTaskWoken) ; portYIELD_FROM_ISR( xHigherPriorityTaskWoken ) ; } /**************************************************************************** * Function Name: wWaitForSignal() * Description: Waits for the specified events for a given time, and clears * the event memory when the function returns * * Assumptions: No * Parameters: [in] mask - The event bit mask containing the event bits, * which shall be waited for * [in] timeOut - Maximum time in system ticks waiting for events * to be signaled. * Returns: Set bits ****************************************************************************/ tTaskEventMask wWaitForSignal(const tTaskEventMask mask, tTime timeOut) { uint32_t ulNotifiedValue = 0U ; xTaskNotifyWait( 0U, std::numeric_limits<uint32_t>::max(), &ulNotifiedValue, timeOut); return (ulNotifiedValue & mask) ; } /**************************************************************************** * Function Name: wCreateEvent() * Description: Create an Event object * * Assumptions: No * Parameters: [in] event - reference on tEvent object * * Returns: Handle of created Event ****************************************************************************/ tEventHandle wCreateEvent(tEvent &event) { #if (configSUPPORT_STATIC_ALLOCATION == 1) return xEventGroupCreateStatic(&event); #else return xEventGroupCreate(); #endif } /**************************************************************************** * Function Name: wDeleteEvent() * Description: Create an Event object * * Assumptions: No * Parameters: [in] eventHandle - reference on tEventHandle object * * Returns: No ****************************************************************************/ void wDeleteEvent(tEventHandle &eventHandle) { vEventGroupDelete(eventHandle); } /**************************************************************************** * Function Name: wSignalEvent() * Description: Sets an resumes tasks which are waiting at the event object * * Assumptions: No * Parameters: [in] event - reference on eventHandle object * [in] mask - The event bit mask containing the event bits, * which shall be signaled * * Returns: No ****************************************************************************/ void wSignalEvent(tEventHandle const &eventHandle, const tEventBits mask) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; xEventGroupSetBitsFromISR(eventHandle, mask, &xHigherPriorityTaskWoken) ; portYIELD_FROM_ISR(xHigherPriorityTaskWoken) ; } /**************************************************************************** * Function Name: wWaitEvent() * Description: Waits for an event and suspends the task for a specified time * or until the event has been signaled. * * Assumptions: No * Parameters: [in] event - Reference on eventHandle object * [in] mask - The event bit mask containing the event bits, * which shall be signaled * [in] timeOut - Maximum time in RTOS system ticks until the * event must be signaled. * [in] mode - Indicate mask bit behaviour * * Returns: Set bits ****************************************************************************/ tEventBits wWaitEvent(tEventHandle const &eventHandle, const tEventBits mask, const tTime timeOut, OsWrapper::EventMode mode) { BaseType_t xWaitForAllBits = pdFALSE ; if (mode == OsWrapper::EventMode::waitAnyBits) { xWaitForAllBits = pdFALSE; } return xEventGroupWaitBits(eventHandle, mask, pdTRUE, xWaitForAllBits, timeOut) ; } /**************************************************************************** * Function Name: wCreateMutex() * Description: Create an mutex. Mutexes are used for managing resources by * avoiding conflicts caused by simultaneous use of a resource. The resource * managed can be of any kind: a part of the program that is not reentrant, a * piece of hardware like the display, a flash prom that can only be written to * by a single task at a time, a motor in a CNC control that can only be * controlled by one task at a time, and a lot more. * * Assumptions: No * Parameters: [in] mutex - Reference on tMutex structure * [in] mode - Indicate mask bit behaviour * * Returns: Mutex handle ****************************************************************************/ tMutexHandle wCreateMutex(tMutex &mutex) { #if (configSUPPORT_STATIC_ALLOCATION == 1) return xSemaphoreCreateMutexStatic(&mutex) ; #else return xSemaphoreCreateMutex(); #endif } /**************************************************************************** * Function Name: wDeleteMutex() * Description: Delete the mutex. * * Assumptions: No * Parameters: [in] mutex - handle of mutex * * Returns: Mutex handle ****************************************************************************/ void wDeleteMutex(tMutexHandle &handle) { vSemaphoreDelete(handle) ; } /**************************************************************************** * Function Name: wLockMutex() * Description: Claim the resource * * Assumptions: No * Parameters: [in] handle - handle of mutex * [in] timeOut - Maximum time until the mutex should be available * * Returns: true if resource has been claimed, false if timeout is expired ****************************************************************************/ bool wLockMutex(tMutexHandle const &handle, tTime timeOut) { return static_cast<bool>(xSemaphoreTake(handle, timeOut)) ; } /**************************************************************************** * Function Name: wUnLockMutex() * Description: Releases a mutex currently in use by a task * * Assumptions: No * Parameters: [in] handle - handle of mutex * * Returns: No ****************************************************************************/ void wUnLockMutex(tMutexHandle const &handle) { BaseType_t xHigherPriorityTaskWoken = pdFALSE ; xSemaphoreGiveFromISR(handle, &xHigherPriorityTaskWoken) ; portYIELD_FROM_ISR( xHigherPriorityTaskWoken ) ; } /**************************************************************************** * Function Name: wSleepUntil() * Description: Suspends the calling task until a specified time, or waits * actively when called from main() * * Assumptions: No * Parameters: [in] last - Refence to a variable that holds the time at which * the task was last unblocked. The variable must be initialised * with the current time prior to its first use * [in] timeOut - Time to delay until, the task will be unblocked * at time * * Returns: No ****************************************************************************/ void wSleepUntil(tTime & last, const tTime timeOut) { vTaskDelayUntil( &last, timeOut) ; } /**************************************************************************** * Function Name: wGetTicks() * Description: Returns the current system time in ticks as a native integer * value * * Assumptions: No * Parameters: No * * Returns: Current system time in ticks ****************************************************************************/ tTime wGetTicks() { return xTaskGetTickCount(); } } 


الصورة

نواصل تحسين المهمة


أضفنا المهمة الآن كل ما تحتاجه تقريبًا ، أضفنا طريقة Sleep (). تقوم هذه الطريقة بإيقاف المهمة مؤقتًا لفترة محددة. في معظم الحالات ، يكون هذا كافيًا ، ولكن إذا كنت بحاجة إلى وقت محدد بوضوح ، فإن Sleep () يمكن أن يسبب لك مشاكل. على سبيل المثال ، تريد إجراء بعض العمليات الحسابية وميض مؤشر LED والقيام بذلك مرة واحدة بالضبط كل 100 مللي ثانية

 void MyTask::Execute() { while(true) { DoCalculation(); //It takes about 10ms Led1.Toggle() ; Sleep(100ms) ; } } 

سيومض هذا الرمز على مؤشر LED كل 110 مللي ثانية. ولكنك تريد مرة واحدة كل 100 مللي ثانية ، يمكنك حساب وقت الحساب تقريبًا ووضع وضع السكون (90 مللي ثانية). ولكن ماذا لو كان وقت الحساب يعتمد على معلمات الإدخال ، فلن يكون الوميض حتمًا على الإطلاق. لمثل هذه الحالات في أنظمة تشغيل "كافة" ، هناك طرق خاصة مثل DelayUntil (). يعمل على هذا المبدأ - تحتاج أولاً إلى تذكر القيمة الحالية لعداد التجزئة لنظام التشغيل ، ثم إضافة إلى هذه القيمة عدد علامات التجزئة التي تحتاج إلى إيقاف المهمة مؤقتًا ، بمجرد أن يصل عداد التجزئة إلى هذه القيمة ، يتم إلغاء تأمين المهمة. وبالتالي ، سيتم قفل المهمة تمامًا بالقيمة التي تحددها وسيومض مؤشر LED تمامًا كل 100 مللي ثانية ، بغض النظر عن مدة الحساب.
يتم تنفيذ هذه الآلية بشكل مختلف في أنظمة التشغيل المختلفة ، ولكن لديها خوارزمية واحدة. ونتيجة لذلك ، سيتم تبسيط الآلية ، على سبيل المثال ، المنفذة على FreeRTOS ، إلى الحالة الموضحة في الصورة التالية:

الصورة

كما ترون ، فإن قراءة الحالة الأولية لعداد القراد لنظام التشغيل تحدث قبل الدخول في حلقة لا نهائية ، ونحن بحاجة إلى التوصل إلى شيء لتنفيذ ذلك. يأتي قالب تصميم للإنقاذ طريقة القالب . , , , , Execute(), , .. . , ( ), .

  class Thread { public: virtual void Execute() = 0 ; friend class Rtos ; private: void Run() { lastWakeTime = wGetTicks() ; Execute(); } ... tTime lastWakeTime = 0ms ; ... } 

Run Rtos, Execute(), Run() Thread. Rtos , Run() Thread.

 static void Run(void *pContext ) { static_cast<Thread*>(pContext)->Run() ; } 

القيد الوحيد لأسلوب SleepUntil () هو أنه لا يمكن تطبيقه بالاقتران مع الطرق الأخرى التي تمنع المهمة. بدلاً من ذلك ، لحل مشكلة العمل جنبًا إلى جنب مع الطرق الأخرى التي تحظر المهمة ، سيكون من الممكن إضافة طريقة تحديث عداد التجزئة المتذكر للنظام ، واستدعاؤه قبل SleepUntil () ، ولكن في الوقت الحالي نحن فقط نضع هذا الفارق الدقيق في الاعتبار. تظهر نسخة متطرفة من الفئات في الصورة التالية:
الصورة

خيط. hpp
 /******************************************************************************* * Filename : thread.hpp * * Details : Base class for any Taskis which contains the pure virtual * method Execute(). Any active classes which will have a method for running as * a task of RTOS should inherit the Thread and override the Execute() method. * For example: * class MyTask : public OsWrapper::Thread * { * public: * virtual void Execute() override { * while(true) { * //do something.. * } * } ; * * Author : Sergey Kolody *******************************************************************************/ #ifndef __THREAD_HPP #define __THREAD_HPP #include "FreeRtos/rtosdefs.hpp" #include "../../Common/susudefs.hpp" namespace OsWrapper { extern void wSleep(const tTime) ; extern void wSleepUntil(tTime &, const tTime) ; extern tTime wGetTicks() ; extern void wSignal(tTaskHandle const &, const tTaskEventMask) ; extern tTaskEventMask wWaitForSignal(const tTaskEventMask, tTime) ; constexpr tTaskEventMask defaultTaskMaskBits = 0b010101010 ; enum class ThreadPriority { clear = 0, lowest = 10, belowNormal = 20, normal = 30, aboveNormal = 80, highest = 90, priorityMax = 255 } ; enum class StackDepth: tU16 { minimal = 128U, medium = 256U, big = 512U, biggest = 1024U }; class Thread { public: virtual void Execute() = 0 ; inline tTaskHandle GetTaskHanlde() const { return handle; } static void Sleep(const tTime timeOut = 1000ms) { wSleep(timeOut) ; }; void SleepUntil(const tTime timeOut = 1000ms) { wSleepUntil(lastWakeTime, timeOut); }; inline void Signal(const tTaskEventMask mask = defaultTaskMaskBits) { wSignal(handle, mask); }; inline tTaskEventMask WaitForSignal(tTime timeOut = 1000ms, const tTaskEventMask mask = defaultTaskMaskBits) { return wWaitForSignal(mask, timeOut) ; } friend void wCreateThread(Thread &, const char *, ThreadPriority, const tU16, tStack *); friend class Rtos ; private: tTaskHandle handle ; tTaskContext context ; tTime lastWakeTime = 0ms ; void Run() { lastWakeTime = wGetTicks() ; Execute(); } } ; } ; #endif // __THREAD_HPP 


rtos.hpp
 /******************************************************************************* * Filename : Rtos.hpp * * Details : Rtos class is used to create tasks, work with special Rtos * functions and also it contains a special static method Run. In this method * the pointer on Thread should be pass. This method is input point as * the task of Rtos. In the body of the method, the method of concrete Thread * will run. *******************************************************************************/ #ifndef __RTOS_HPP #define __RTOS_HPP #include "thread.hpp" // for Thread #include "../../Common/susudefs.hpp" #include "FreeRtos/rtosdefs.hpp" namespace OsWrapper { extern void wCreateThread(Thread &, const char *, ThreadPriority, const tU16, tStack *) ; extern void wStart() ; extern void wHandleSvcInterrupt() ; extern void wHandleSvInterrupt() ; extern void wHandleSysTickInterrupt() ; extern void wEnterCriticalSection(); extern void wLeaveCriticalSection(); class Rtos { public: static void CreateThread(Thread &thread , tStack * pStack = nullptr, const char * pName = nullptr, ThreadPriority prior = ThreadPriority::normal, const tU16 stackDepth = static_cast<tU16>(StackDepth::minimal)) ; static void Start() ; static void HandleSvcInterrupt() ; static void HandleSvInterrupt() ; static void HandleSysTickInterrupt() ; friend void wCreateThread(Thread &, const char *, ThreadPriority, const tU16, tStack *); friend class Thread ; private: //cstat !MISRAC++2008-7-1-2 To prevent reinterpet_cast in the CreateTask static void Run(void *pContext ) { static_cast<Thread*>(pContext)->Run() ; } } ; } ; #endif // __RTOS_HPP 



الأحداث


لذلك يتم إنشاء المهمة ، فمن الممكن إرسال حدث إليها ، لكنني أريد تنفيذ حدث لا يمكن إرساله إلى مهمة معينة ، ولكن إلى أي مشترك يقرر انتظار هذا الحدث. تحدث تقريبًا ، نحن بحاجة إلى تنفيذ غلاف على الحدث.

, , , , , , , . , . , , , , .



:

 OsWrapper::Event event{10000ms, 3}; //  ,    10000ms,    0    1. void SomeTask::Execute() { while(true) { using OsWrapper::operator""ms ; Sleep(1000ms); event.Signal() ; //      0   1. Sleep(1000ms); event.SetMaskBits(4) //    2. event.Signal() ; //      2. } } ; void AnotherTask::Execute() { while(true) { using namespace::OsWrapper ; //,      ,    10000ms if ((event.Wait() & defaultTaskMaskBits) != 0) { GPIOC->ODR ^= (1 << 5) ; } } } ; 


,


في البداية ، عندما كنت أكتب هذه المقالة ، لم أقم بتنفيذها بعد ، ولكن كما وعدت ، قمت بتطوير ملفات المزامنة وصناديق البريد الإلكتروني ، يكمن المصدر هنا: GitHub OsWrapper . مثال على استخدام صندوق بريد كما يلي:
 OsWrapper::MailBox<tU32, 10> queue; //    10   int void ReceiveTask::Execute() { tU32 item; while(true) { using OsWrapper::operator""ms ; if (queue.Get(item, 10000ms)) { //    GPIOC->ODR ^= (1 << 9); } } } ; void SendTask::Execute() { tU32 item = 0U; while(true) { queue.Put(item); item ++; SleepUntil(1000ms); } } ; 


كيفية استخدام كل هذه الأعمال


, , , : LedTask , 2 , 2 myTask , 10 , , . 2 . , event . , :)

 using OsWrapper::operator""ms ; OsWrapper::Event event{10000ms, 1}; class MyTask : public OsWrapper::Thread { public: virtual void Execute() override { while(true) { if (event.Wait() != 0) { GPIOC->ODR ^= (1 << 9); } } } using tMyTaskStack = std::array<OsWrapper::tStack, static_cast<tU16>(OsWrapper::StackDepth::minimal)> ; inline static tMyTaskStack Stack; //C++17   IAR 8.30 } ; class LedTask : public OsWrapper::Thread { public: virtual void Execute() override { while(true) { GPIOC->ODR ^= (1 << 5) ; using OsWrapper::operator""ms ; SleepUntil(2000ms); event.Signal() ; } } using tLedStack = std::array<OsWrapper::tStack, static_cast<tU16>(OsWrapper::StackDepth::minimal)> ; inline static tLedStack Stack; //C++17   IAR 8.30 } ; MyTask myTask; LedTask ledTask; int main() { using namespace OsWrapper ; Rtos::CreateThread(myTask, MyTask::Stack.data(), "myTask", ThreadPriority::lowest, MyTask::Stack.size()) ; Rtos::CreateThread(ledTask, LedTask::Stack.data()) ; Rtos::Start(); return 0; } 


الخلاصة


أجرؤ على التعبير عن وجهة نظري الذاتية لمستقبل البرامج الثابتة للمتحكم الدقيق. أعتقد أن C ++ قادم وسيظهر عاجلاً أم آجلاً المزيد والمزيد من أنظمة التشغيل التي توفر واجهة C ++. يحتاج المصنعون الآن إلى إعادة كتابة أو لف كل شيء في C ++.
من وجهة النظر هذه ، أود أن أوصي باستخدام RTOS المكتوب بلغة C ++ ، على سبيل المثال ، المشار إليه أعلاه RTOS MAX ، كم من الوقت يمكن أن يوفر لك ، لا يمكنك حتى أن تتخيل ، لأنه لا تزال هناك ميزات فريدة مثل ، على سبيل المثال ، وسائل تفاعل المهام ، تعمل على ميكروكنترولر مختلفة. إذا كانت لديها أيضًا شهادة أمان ، فلن يكون هناك حل أفضل.

ولكن في غضون ذلك ، يستخدم معظمنا أنظمة تشغيل Sishna التقليدية ، يمكنك استخدام الغلاف كبداية أولية للانتقال إلى مستقبل سعيد باستخدام C ++ :)

لقد قمت بتجميع مشروع اختبار صغير في Clion . , , IAR toolchain, , , elf , hex , , GDB. — , , , 2 , , , , . , Clion. , IAR toolchain , .

IAR 8.30.1, . : XNUCLEO-F411RE , ST-Link. , , Clion — , :)

الصورة

IAR : IAR 8.30.1 , , github, , , FreeRtos.

Z.Y. GitHub

Source: https://habr.com/ru/post/ar420467/


All Articles