
لقد تحدثت بالفعل عن كيفية استخدام 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) {
في "كل" 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"
قد يبدو ملف embOS
rtosEmbOS.cpp هو
نفسه تمامًا
#include "rtos.hpp"
تختلف أنواع أنظمة التشغيل المختلفة أيضًا ، خاصة هيكل سياق المهمة ، لذلك دعونا ننشئ ملف 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 #ifndef __RTOS_HPP #define __RTOS_HPP #include "thread.hpp"
خيط. hpp #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
بدأت في تنفيذه ، وهنا كانت المشكلة الأولى في انتظاري ، اتضح أن نظام التشغيل "أي" يستدعي وظائفه من المقاطعات بطرق مختلفة. على سبيل المثال ، يحتوي
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 #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();
سيومض هذا الرمز على مؤشر 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 #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
rtos.hpp #ifndef __RTOS_HPP #define __RTOS_HPP #include "thread.hpp"
الأحداث
لذلك يتم إنشاء المهمة ، فمن الممكن إرسال حدث إليها ، لكنني أريد تنفيذ حدث لا يمكن إرساله إلى مهمة معينة ، ولكن إلى أي مشترك يقرر انتظار هذا الحدث. تحدث تقريبًا ، نحن بحاجة إلى تنفيذ غلاف على الحدث., , , , , , , . , . , , , , .

:
OsWrapper::Event event{10000ms, 3};
,
في البداية ، عندما كنت أكتب هذه المقالة ، لم أقم بتنفيذها بعد ، ولكن كما وعدت ، قمت بتطوير ملفات المزامنة وصناديق البريد الإلكتروني ، يكمن المصدر هنا: GitHub OsWrapper . مثال على استخدام صندوق بريد كما يلي: OsWrapper::MailBox<tU32, 10> queue;
كيفية استخدام كل هذه الأعمال
, , , :
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 ++ قادم وسيظهر عاجلاً أم آجلاً المزيد والمزيد من أنظمة التشغيل التي توفر واجهة 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