توزيع ثابت للكائنات FreeRTOS

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

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

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

هذه المقالة مخصصة للمبرمجين المبتدئين ، لكن الذين لديهم دراية بالفعل بأساسيات FreeRTOS وبأولويات مزامنة البرامج متعددة مؤشرات الترابط. دعنا نذهب.

FreeRTOS هو نظام تشغيل للميكروكونترولر. حسنًا ، حسنًا ، ليس نظام تشغيل كامل ، ولكن مكتبة تتيح لك تشغيل العديد من المهام بالتوازي. يسمح FreeRTOS أيضًا للمهام بتبادل الرسائل من خلال قوائم انتظار الرسائل ، واستخدام المؤقتات ، ومزامنة المهام باستخدام الإشارات والرموز المتحركة.

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

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

سننظر في مشكلات تنظيم ذاكرة FreeRTOS باستخدام لوحة BluePill على متحكم STM32F103C8T6 كمثال. حتى لا تقلق بشأن المترجم ونظام الإنشاء ، سنعمل في بيئة ArduinoIDE ، مع تثبيت الدعم لهذه اللوحة. هناك العديد من تطبيقات Arduino لـ STM32 - من حيث المبدأ ، ستفعل أي منها. لقد قمت بتثبيت stm32duino وفقًا لتعليمات مشروع Readme.md ، أداة تحميل التشغيل كما هو مذكور في هذه المقالة . يتم تثبيت FreeRTOS الإصدار 10.0 من خلال مدير مكتبة ArduinoIDE. مترجم - مجلس التعاون الخليجي 8.2

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

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

قد يبدو التنفيذ القياسي (وفقًا للوثائق والبرامج التعليمية) هكذا.

#include <STM32FreeRTOS.h> TimerHandle_t xTimer; xSemaphoreHandle xSemaphore; xSemaphoreHandle xMutex; xQueueHandle xQueue; void vTimerCallback(TimerHandle_t pxTimer) { xSemaphoreGive(xSemaphore); } void vTask1(void *) { while(1) { xSemaphoreTake(xSemaphore, portMAX_DELAY); int value = random(1000); xQueueSend(xQueue, &value, portMAX_DELAY); xSemaphoreTake(xMutex, portMAX_DELAY); Serial.println("Test"); xSemaphoreGive(xMutex); } } void vTask2(void *) { while(1) { int value; xQueueReceive(xQueue, &value, portMAX_DELAY); xSemaphoreTake(xMutex, portMAX_DELAY); Serial.println(value); xSemaphoreGive(xMutex); } } void setup() { Serial.begin(9600); vSemaphoreCreateBinary(xSemaphore); xQueue = xQueueCreate(1000, sizeof(int)); xMutex = xSemaphoreCreateMutex(); xTimer = xTimerCreate("Timer", 1000, pdTRUE, NULL, vTimerCallback); xTimerStart(xTimer, 0); xTaskCreate(vTask1, "Task 1", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL); xTaskCreate(vTask2, "Task 2", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY, NULL); vTaskStartScheduler(); } void loop() {} 

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

على سبيل المثال ، بالنسبة لكومة من اسم FreeRTOS ، ستبدو بهذا الشكل (الإخراج من الأداة المساعدة objdump)

 ... 200009dc l O .bss 00002000 ucHeap ... 

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

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

يتم تعيين حجم مكدس المهام عند إنشاء المهمة بواسطة معلمة الدالة xTaskCreate. لا يمكن أن يكون حجم الرصة أقل من المعلمة configMINIMAL_STACK_SIZE (المحددة في ملف التكوين FreeRTOSConfig.h) - وهذا هو نفس الاحتياطي للمقاطعات. يتم تعيين حجم الكومة بواسطة المعلمة configTOTAL_HEAP_SIZE وفي هذه الحالة هو 8 كيلو بايت.

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

توقف ، ما هي المتغيرات xTimer ، xQueue ، xSemaphore ، و xMutex؟ ألا تصف الأشياء التي نحتاجها؟ لا ، هذه فقط مؤشرات - بنية لبنية معينة (غير شفافة) ، تصف كائنات المزامنة نفسها

 200009cc g O .bss 00000004 xTimer 200009d0 g O .bss 00000004 xSemaphore 200009cc g O .bss 00000004 xQueue 200009d4 g O .bss 00000004 xMutex 

كما ذكرت بالفعل ، أقترح إصلاح كل هذه الفوضى بالطريقة نفسها كما في المقالة السابقة - سنقوم بتوزيع جميع الكائنات بشكل ثابت في مرحلة التجميع. تصبح وظائف التوزيع الثابت متاحة إذا تم تعيين المعلمة configSUPPORT_STATIC_ALLOCATION على 1 في ملف تكوين FreeRTOS.

لنبدأ مع الخطوط. فيما يلي كيفية تقديم الوثائق على FreeRTOS لتخصيص قوائم الانتظار

 struct AMessage { char ucMessageID; char ucData[ 20 ]; }; #define QUEUE_LENGTH 10 #define ITEM_SIZE sizeof( uint32_t ) // xQueueBuffer will hold the queue structure. StaticQueue_t xQueueBuffer; // ucQueueStorage will hold the items posted to the queue. Must be at least // [(queue length) * ( queue item size)] bytes long. uint8_t ucQueueStorage[ QUEUE_LENGTH * ITEM_SIZE ]; void vATask( void *pvParameters ) { QueueHandle_t xQueue1; // Create a queue capable of containing 10 uint32_t values. xQueue1 = xQueueCreate( QUEUE_LENGTH, // The number of items the queue can hold. ITEM_SIZE // The size of each item in the queue &( ucQueueStorage[ 0 ] ), // The buffer that will hold the items in the queue. &xQueueBuffer ); // The buffer that will hold the queue structure. // The queue is guaranteed to be created successfully as no dynamic memory // allocation is used. Therefore xQueue1 is now a handle to a valid queue. // ... Rest of task code. } 

في هذا المثال ، يتم وصف قائمة الانتظار بثلاثة متغيرات:

  • صفيف ucQueueStorage هو المكان الذي سيتم فيه وضع عناصر قائمة الانتظار. يتم تعيين حجم قائمة الانتظار من قبل المستخدم لكل قائمة انتظار على حدة.
  • هيكل XQueueBuffer - هنا وصف وحالة قائمة الانتظار ، والحجم الحالي ، وقوائم المهام المعلقة ، وكذلك السمات والحقول الأخرى التي يحتاجها FreeRTOS للعمل مع قائمة الانتظار مباشرة. اسم المتغير ، في رأيي ، ليس ناجحًا بالكامل ، في FreeRTOS نفسه يسمى هذا الشيء QueueDefinition (وصف قائمة الانتظار).
  • المتغير xQueue1 هو معرّف قائمة الانتظار (المقبض). جميع وظائف إدارة قائمة الانتظار ، وكذلك بعض الوظائف الأخرى (على سبيل المثال ، الوظائف الداخلية للعمل مع أجهزة ضبط الوقت ، الإشارات ، والمزيلات) تقبل مثل هذا المقبض. في الواقع ، هذا مجرد مؤشر إلى QueueDefinition ، لكننا لا نعرف هذا (كما كان) ، وبالتالي يجب سحب المقبض بشكل منفصل.

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

 template<class T, size_t size> class Queue { QueueHandle_t xHandle; StaticQueue_t x QueueDefinition; T xStorage[size]; public: Queue() { xHandle = xQueueCreateStatic(size, sizeof(T), reinterpret_cast<uint8_t*>(xStorage), &xQueueDefinition); } bool receive(T * val, TickType_t xTicksToWait = portMAX_DELAY) { return xQueueReceive(xHandle, val, xTicksToWait); } bool send(const T & val, TickType_t xTicksToWait = portMAX_DELAY) { return xQueueSend(xHandle, &val, xTicksToWait); } }; 

في الوقت نفسه ، استقر أيضًا في هذه الفئة وظائف إرسال واستقبال الرسائل ، والتي كانت ملائمة لنا على الفور.

سيتم الإعلان عن قائمة الانتظار كمتغير عام ، شيء من هذا القبيل

 Queue<int, 1000> xQueue; 

إرسال رسالة

  xQueue.send(value); 

تلقي رسالة

  int value; xQueue.receive(&value); 

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

سيكون تنفيذ فئة الإشارة بسيطًا تمامًا - فهو ببساطة يخزن العديد من المتغيرات ويعلن العديد من الوظائف.

 class Sema { SemaphoreHandle_t xSema; StaticSemaphore_t xSemaControlBlock; public: Sema() { xSema = xSemaphoreCreateBinaryStatic(&xSemaControlBlock); } BaseType_t give() { return xSemaphoreGive(xSema); } BaseType_t take(TickType_t xTicksToWait = portMAX_DELAY) { return xSemaphoreTake(xSema, xTicksToWait); } }; 

إعلان إشارة

 Sema xSema; 

القبض على إشارة

  xSema.take(); 

إطلاق إشارة

  xSema.give(); 

الآن mutex

 class Mutex { SemaphoreHandle_t xMutex; StaticSemaphore_t xMutexControlBlock; public: Mutex() { xMutex = xSemaphoreCreateMutexStatic(&xSemaControlBlock); } BaseType_t lock(TickType_t xTicksToWait = portMAX_DELAY) { return xSemaphoreTake(xMutex, xTicksToWait); } BaseType_t unlock() { return xSemaphoreGive(xMutex); } }; 

كما ترون ، فئة mutex مطابقة تقريبًا لفئة الإشارة. ولكن كما قلت دلالات ، هذه هي كيانات مختلفة. علاوة على ذلك ، فإن واجهات هذه الفئات ليست كاملة ، وسوف تتوسع في اتجاهات مختلفة تمامًا. لذلك ، يمكن إضافة أساليب GiveFromISR () و takeFromIS () إلى الإشارة للعمل مع الإشارة في المقاطعة ، بينما يحتوي mutex فقط على أسلوب tryLock () المضافة - ليس لديه عمليات أخرى بشكل دلالي.

أتمنى أن تعرف الفرق بين إشارة ثنائية و mutex.
أنا أسأل هذا السؤال دائمًا في المقابلات ، وللأسف فإن 90٪ من المرشحين لا يفهمون هذا الاختلاف. في الواقع ، يمكن التقاط إشارة وتحريرها من خيوط مختلفة. أعلاه ، أشرت إلى وضع إشارة انتظار إشارة عندما يرسل مؤشر ترابط واحد إشارة (المكالمات تعطي ()) ، والآخر ينتظر إشارة (مع وظيفة take ()).

على العكس من ذلك ، يمكن تحرير Mutex من نفس الدفق (المهمة) التي استولت عليها. لست متأكدًا من أن FreeRTOS تراقب ذلك ، لكن بعض أنظمة التشغيل (على سبيل المثال ، Linux) تتبع ذلك بدقة شديدة.

يمكن استخدام Mutex في النمط C ، أي دعوة مباشرة قفل () / فتح (). ولكن نظرًا لأننا نكتب في C ++ ، يمكننا الاستفادة من سحر RAII وكتابة غلاف أكثر ملاءمة من شأنها التقاط وإطلاق mutex نفسه.

 class MutexLocker { Mutex & mtx; public: MutexLocker(Mutex & mutex) : mtx(mutex) { mtx.lock(); } ~MutexLocker() { mtx.unlock(); } }; 

عند مغادرة النطاق ، سيتم تحرير المزامنة تلقائيًا.

هذا مناسب بشكل خاص إذا كان هناك العديد من المخارج من الوظيفة ولم تكن بحاجة إلى تذكر الحاجة باستمرار إلى توفير الموارد.

  MutexLocker lock(xMutex); Serial.println(value); } // mutex will be unlocked here 

الآن هو دور توقيت.

 class Timer { TimerHandle_t xTimer; StaticTimer_t xTimerControlBlock; public: Timer(const char * const pcTimerName, const TickType_t xTimerPeriodInTicks, const UBaseType_t uxAutoReload, void * const pvTimerID, TimerCallbackFunction_t pxCallbackFunction) { xTimer = xTimerCreateStatic(pcTimerName, xTimerPeriodInTicks, uxAutoReload, pvTimerID, pxCallbackFunction, &xTimerControlBlock); } void start(TickType_t xTicksToWait = 0) { xTimerStart(xTimer, xTicksToWait); } }; 

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

وأخيرا ، المهام. كل مهمة لديها كومة ويجب أن توضع في الذاكرة مقدما. سنستخدم نفس الأسلوب كما هو الحال مع قوائم الانتظار - سنكتب فئة قالب

 template<const uint32_t ulStackDepth> class Task { protected: StaticTask_t xTaskControlBlock; StackType_t xStack[ ulStackDepth ]; TaskHandle_t xTask; public: Task(TaskFunction_t pxTaskCode, const char * const pcName, void * const pvParameters, UBaseType_t uxPriority) { xTask = xTaskCreateStatic(pxTaskCode, pcName, ulStackDepth, pvParameters, uxPriority, xStack, &xTaskControlBlock); } }; 

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

ترجمة واحصل على الخطأ:

 tasks.c:(.text.vTaskStartScheduler+0x10): undefined reference to `vApplicationGetIdleTaskMemory' timers.c:(.text.xTimerCreateTimerTask+0x1a): undefined reference to `vApplicationGetTimerTaskMemory' 

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

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

الدالات نفسها تافهة وإرجاع المؤشرات المقابلة إلى الكائنات المخصصة بشكل ثابت.

 extern "C" void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize) { static StaticTask_t Idle_TCB; static StackType_t Idle_Stack[configMINIMAL_STACK_SIZE]; *ppxIdleTaskTCBBuffer = &Idle_TCB; *ppxIdleTaskStackBuffer = Idle_Stack; *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE; } extern "C" void vApplicationGetTimerTaskMemory (StaticTask_t **ppxTimerTaskTCBBuffer, StackType_t **ppxTimerTaskStackBuffer, uint32_t *pulTimerTaskStackSize) { static StaticTask_t Timer_TCB; static StackType_t Timer_Stack[configTIMER_TASK_STACK_DEPTH]; *ppxTimerTaskTCBBuffer = &Timer_TCB; *ppxTimerTaskStackBuffer = Timer_Stack; *pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH; } 

دعونا نرى ما حصلنا عليه.

سوف أخفي رمز المساعدين تحت المفسد ، لقد رأيته للتو
 template<class T, size_t size> class Queue { QueueHandle_t xHandle; StaticQueue_t xQueueDefinition; T xStorage[size]; public: Queue() { xHandle = xQueueCreateStatic(size, sizeof(T), reinterpret_cast<uint8_t*>(xStorage), &xQueueDefinition); } bool receive(T * val, TickType_t xTicksToWait = portMAX_DELAY) { return xQueueReceive(xHandle, val, xTicksToWait); } bool send(const T & val, TickType_t xTicksToWait = portMAX_DELAY) { return xQueueSend(xHandle, &val, xTicksToWait); } }; class Sema { SemaphoreHandle_t xSema; StaticSemaphore_t xSemaControlBlock; public: Sema() { xSema = xSemaphoreCreateBinaryStatic(&xSemaControlBlock); } BaseType_t give() { return xSemaphoreGive(xSema); } BaseType_t take(TickType_t xTicksToWait = portMAX_DELAY) { return xSemaphoreTake(xSema, xTicksToWait); } }; class Mutex { SemaphoreHandle_t xMutex; StaticSemaphore_t xMutexControlBlock; public: Mutex() { xMutex = xSemaphoreCreateMutexStatic(&xMutexControlBlock); } BaseType_t lock(TickType_t xTicksToWait = portMAX_DELAY) { return xSemaphoreTake(xMutex, xTicksToWait); } BaseType_t unlock() { return xSemaphoreGive(xMutex); } }; class MutexLocker { Mutex & mtx; public: MutexLocker(Mutex & mutex) : mtx(mutex) { mtx.lock(); } ~MutexLocker() { mtx.unlock(); } }; class Timer { TimerHandle_t xTimer; StaticTimer_t xTimerControlBlock; public: Timer(const char * const pcTimerName, const TickType_t xTimerPeriodInTicks, const UBaseType_t uxAutoReload, void * const pvTimerID, TimerCallbackFunction_t pxCallbackFunction) { xTimer = xTimerCreateStatic(pcTimerName, xTimerPeriodInTicks, uxAutoReload, pvTimerID, pxCallbackFunction, &xTimerControlBlock); } void start(TickType_t xTicksToWait = 0) { xTimerStart(xTimer, xTicksToWait); } }; template<const uint32_t ulStackDepth> class Task { protected: StaticTask_t xTaskControlBlock; StackType_t xStack[ ulStackDepth ]; TaskHandle_t xTask; public: Task(TaskFunction_t pxTaskCode, const char * const pcName, void * const pvParameters, UBaseType_t uxPriority) { xTask = xTaskCreateStatic(pxTaskCode, pcName, ulStackDepth, pvParameters, uxPriority, xStack, &xTaskControlBlock); } }; extern "C" void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize) { static StaticTask_t Idle_TCB; static StackType_t Idle_Stack[configMINIMAL_STACK_SIZE]; *ppxIdleTaskTCBBuffer = &Idle_TCB; *ppxIdleTaskStackBuffer = Idle_Stack; *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE; } extern "C" void vApplicationGetTimerTaskMemory (StaticTask_t **ppxTimerTaskTCBBuffer, StackType_t **ppxTimerTaskStackBuffer, uint32_t *pulTimerTaskStackSize) { static StaticTask_t Timer_TCB; static StackType_t Timer_Stack[configTIMER_TASK_STACK_DEPTH]; *ppxTimerTaskTCBBuffer = &Timer_TCB; *ppxTimerTaskStackBuffer = Timer_Stack; *pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH; } 


رمز البرنامج بأكمله.

 Timer xTimer("Timer", 1000, pdTRUE, NULL, vTimerCallback); Sema xSema; Mutex xMutex; Queue<int, 1000> xQueue; Task<configMINIMAL_STACK_SIZE> task1(vTask1, "Task 1", NULL, tskIDLE_PRIORITY); Task<configMINIMAL_STACK_SIZE> task2(vTask2, "Task 2", NULL, tskIDLE_PRIORITY); void vTimerCallback(TimerHandle_t pxTimer) { xSema.give(); MutexLocker lock(xMutex); Serial.println("Test"); } void vTask1(void *) { while(1) { xSema.take(); int value = random(1000); xQueue.send(value); } } void vTask2(void *) { while(1) { int value; xQueue.receive(&value); MutexLocker lock(xMutex); Serial.println(value); } } void setup() { Serial.begin(9600); xTimer.start(); vTaskStartScheduler(); } void loop() {} 

يمكنك تفكيك الملف الثنائي الناتج ومعرفة ماذا وكيف يتم تحديد موقعه (إخراج objdump ملون قليلاً للحصول على قراءة أفضل):

 0x200000b0 .bss 512 vApplicationGetIdleTaskMemory::Idle_Stack 0x200002b0 .bss 92 vApplicationGetIdleTaskMemory::Idle_TCB 0x2000030c .bss 1024 vApplicationGetTimerTaskMemory::Timer_Stack 0x2000070c .bss 92 vApplicationGetTimerTaskMemory::Timer_TCB 0x200009c8 .bss 608 task1 0x20000c28 .bss 608 task2 0x20000e88 .bss 84 xMutex 0x20000edc .bss 4084 xQueue 0x20001ed0 .bss 84 xSema 0x20001f24 .bss 48 xTimer 

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

 Sketch uses 20,800 bytes (15%) of program storage space. Maximum is 131,072 bytes. Global variables use 9,332 bytes (45%) of dynamic memory, leaving 11,148 bytes for local variables. Maximum is 20,480 bytes. 

استنتاج


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

من الواضح أن الطريقة المقترحة مناسبة فقط لوضع الكائنات التي تكون حياتها قابلة للمقارنة مع عمر التطبيق بالكامل. خلاف ذلك ، يجب عليك استخدام الذاكرة الديناميكية.

بالإضافة إلى الموضع الثابت لكائنات FreeRTOS ، قمنا أيضًا بكتابة أغلفة ملائمة على بدائل FreeRTOS ، مما سمح لنا بتبسيط رمز العميل إلى حد ما وتغليفه أيضًا

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

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

مثال من المقال هنا .

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

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


All Articles