RTOS یا سیستم عامل بلادرنگ، سیستم عاملی است که در درون دیوایسهای امبدد وجود دارد. از آنجایی که در این ابزارها زمانبندی اجرای وظایف از اهمیت ویژهای برخوردار است، بلادرنگ بودن سیستم عامل مورد استفاده نیز بالطبع مهم خواهد بود.
منظور از بلادرنگ بودن اجرای وظایف این است که مدت زمانی که برای پاسخ دادن سیستم عامل نسبت به هر دستور و وظیفه خاص مورد نیاز است، همواره ثابت است و میتوان تضمین کرد که پاسخهای این سیستم عامل همیشه با آن مقدار مشخص از تاخیر به دست خواهند آمد نه بیشتر و نه کمتر. معمولا در کاربردهایی که زمانبندی بسیار بسیار دقیقی نیاز دارند و نیز درجهی پایداری پاسخهای دریافتی آنها بالاست استفاده میشود. همچنین RTOS به اجرای multi-tasking در پردازندههای تکهستهای نیز کمک میکند.
قبلا در آموزش دیگری در مورد این که چگونه باید از RTOS در امبدد دیوایسها استفاده کرد، صحبت کردهایم. در آنجا در مورد خود RTOS، انواع آن و تفاوتهای آن با سیستم عاملهای معمولی نیز توضیحاتی دادهایم.
در این آموزش هم ابتدا با توضیحات مختصری در مورد FreeRTOS شروع میکنیم. FreeRTOS یکی از کلاسهای RTOS است که آنقدر کوچک است که بتواند بر روی میکروکنترلرهای ۸ و ۱۶ بیتی نیز اجرا شود. البته این به آن معنا نیست که تنها در میکروکنترلرها کاربرد دارد. نکته مثبتی که دارد این است که کاملا اپن سورس است و کدهای آن در گیتهاب در دسترس هستند.
- آموزش مرتبط مفید: آموزش گیتهاب
اگر از قبل مقدماتی از RTOS را بدانید کار کردن با FreeRTOS راحت خواهد بود، چرا که APIهایی وجود دارند که میتواند از آنها استفاده کرد بدون اینکه نیاز باشد که کدهای پشت آنها را به طور دقیق بدانیم. اگر علاقهمند باشید اطلاعات بیشتر در مورد FreeRTOS را میتوانید در اینجا نیز پیدا کنید.
از آنجا که گفتیم این سیستم عامل میتواند بر روی میکروهای ۸ بیتی نیز اجرا شود، پس بر روی بورد Arduino Uno نیز قابل اجرا و استفاده است. کاری که باید انجام دهیم این است که ابتدا کتابخانهی مربوط به FreeRTOS را دانلود کنیم و سپس کد را با کمک API اجرا کنیم.
قبل از ورود به موضوع، تاکید میکنیم که اگر در این موضوع تازهکار هستید اصلا نگران نباشید؛ در این آموزش به نحوی مطالب را پوشش میدهیم که شما نیز بتوانید مرحله به مرحله با ما پیش بیایید. فهرست عناوینی که در مورد آنها صحبت خواهیم کرد از این قرار هستند.
- RTOS چگونه کار میکند؟
- آشنایی با اصطلاحات رایج در RTOS
- نصب کردن FreeRTOS در Arduino IDE
- نحوهی اجرای پروژهها با استفاده از FreeRTOS و ذکر یک مثال
RTOS چگونه کار میکند؟
قبل از اینکه عملکرد RTOS را توضیح دهیم، اجازه بدهید شما را با یک اصطلاح مهم آشنا کنیم و آن task است. task به قطعه کدی گفته میشود که میتوان آن را به صورت برنامهریزی شده (در زمان مشخص) بر روی CPU اجرا کرد. بنابراین اگر بخواهیم چند task مختلف را بر روی CPU اجرا کنیم، باید از kernel delay و یا وقفه (interrupt) استفاده کنیم. این کار توسط واحدی به نام Scheduler که در kernel قرار دارد انجام میشود. در یک پروسسور تک هستهای، scheduler کاری میکند که که taskهای مختلف در یک بخش مشخص از زمان به ترتیب اجرا شوند، اما در مجموع و در خروجی اینطور به نظر میرسد که این taskها به طور همزمان اجرا میشوند. در حقیقت taskها بر اساس اولویت بندیهایشان در آن بازهی زمانی اجرا میشوند.
بیایید یک مثال را با هم بررسی کنیم و ببینیم در داخل RTOS kernel چه اتفاقی میافتد. فرض کنیم task تعریفی ما چشمک زدن LED در بازههای زمانی یک ثانیهای باشد و اولویت این task بالاترین اولویت باشد.
به جز LED task که ما تعریف میکنیم، ممکن است taskهای دیگری نیز در kernel وجود داشته باشند که توسط خود آن ساخته میشوند. به این taskهای خود ساخته idle task گفته میشود. این نوع task زمانی ساخته میشود که task دیگری برای انجام وجود نداشته باشد. این task همواره دارای کمترین اولویت یعنی اولویت صفر است.
اگر به گراف زمانبندی تصویر فوق دقت کنید نیز میبینیم که ابتدا LED task اجرا میشود و اجرای آن مدت زمان بخصوصی طول خواهد کشید. پس از آن تا زمانی که یک interrup رخ بدهد، idle task اجرا میشود. بنابراین kernel براساس اولویت بندی taskها و اینکه task اصلی (مثلا در اینجا LED چشمک زن) به چه میزان زمان نیاز دارد، تصمیم میگیرد که در هر لحظه کدام task اجرا شود. اگر به محض پایان interrupt ،LED task برسد، در بازهی بعدی مجددا LED task شروع به اجرا میکند نه idle task که از دور قبلی باقی مانده است. چون اولویت LED task، یک و اولویت idle task، صفر است. به عبارت دیگر میتوان گفت که LED task همیشه به idle task تقدم دارد. در مواردی هم که چند task با اولویتهای برابر وجود داشته باشند به صورت نوبتی و یکی در میان اجرا خواهند شد. در تصویر زیر نحوهی سوییچ کردن بین دو task که یکی در حال اجراست و دیگری در انتظار اجرا، نشان داده شده است.
هر task جدیدی که ساخته میشود در Ready state قرار میگیرد که قسمتی از بخش در انتظار اجرا است. اگر این task جدید اولویت بالاتری نسبت به سایر taskهای موجود در صف انتظار داشته باشد، در نوبت بعدی که kernel سوییچ میکند این task اجرا خواهد شد. اگر task فعلی که در حال اجرا است نیز، اولویت بالاتری نسبت به سایر taskهای در انتظار داشته باشد، در Ready state قرار میگیرد و در نوبت بعدی مجددا اجرا خواهد شد. در صورتی که نخواهیم چنین حلقهای ایجاد شود، باید با استفاده از APIها آن را پس از یک دور اجرا block کنیم تا زمانی که تمام taskها اجرا شوند و فرآیند دوباره از سر گرفته شود.
به عنوان مثال اگر از Suspend APIها استفاده کنیم، در این صورت task مورد نظر پس از اجرا شدن به وضعیت Suspended state میرود و تا پایان آن بازهی زمانی در همانجا میماند. اگر بخواهید آن را در همان بازه به چرخهی اجرا برگردانید، کافیست از resume API استفاده کنید تا دوباره به Ready state برود و بر اساس اولویت آن با آن رفتار شود. این فرآیند را که در بلوک دیاگرام نشان داده شده میتوانید ببینید.
بسیار خب، این مطلب چگونگی اجرای taskها و جابهجایی بین آنها را به زبان ساده و مختصر توضیح داد. در قسمتهای عملی این آموزش دو task مختلف را به عنوان نمونه با استفاده از Free RTOS APIها بر روی Arduino Uno اجرا خواهیم کرد.
آشنایی با اصطلاحات رایج در RTOS
- Task: قطعه کدی که به صورت زمانبندی شده بر روی CPU اجرا میشود.
- Scheduler: واحدی که وظیفهی آن این است که taskها را از لیست ready state انتخاب کند و به اجرا در آورد. این واحد معمولا به شکلی طراحی میشود که تمام بخشهای کامپیوتر را درگیر میکند.
- Preemption: عمل متوقف کردن یک task در حال اجرا و انتقال آن به لیست انتظار به منظور اجرای task دیگری که به آن حق تقدم دارد.
- Context Switching: در هر بازهی زمانی، Scheduler مدام اولویتهای taskها در حال اجرا و taskهای آمادهی اجرا را بررسی کرده و با هم مقایسه میکند. اگر اولویت یکی از taskهای آماده به اجرا از اولویت task در حال اجرا بالاتر باشد، بین آن دو سوییچ میکند. به این عمل Context Switching گفته میشود. در خلال این عمل سوییچ کردن، محتوای این دو task در stack متناظر با هر یک ذخیره میشود.
- انواع سیاستهای زمانبندی
- زمانبندی Preemptive: در این نوع زمانبندی taskهای مختلف بدون توجه به اولویتهای آنها در بازههای زمانی برابر اجرا میشوند.
- زمانبندی اولویت مبنا (Priority-based): taskهایی که اولویت بالاتری دارند زودتر اجرا میشوند.
- زمانبندی Co-operative: زمانبندی با همکاری و مشارکت خود taskها انجام میشود. یعنی عمل سوییچ کردن بین taskهای مختلف زمانی انجام میشود که task در حال اجرا سیگنال آمادگی برای متوقف شدن را ارسال کند.
- Kernel Objects: برای آن که به هر task فرمان اجرا صادر شود، از فرآیندی به نام synchronization استفاده میشود. kernel objectها مسئول اجرای این فرآیند هستند.
برخی از آنها عبارتند از: Semaphores ،Queues ،Mutex ،Mailboxes و … اگر علاقهمند باشید در آموزشهای بعدی نحوه ی کار با این objectها را نیز توضیح خواهیم داد.
بسیار خب، تا اینجای مطلب مقدماتی از RTOS را با هم مرور کردیم و حالا آماده هستیم که یک پروژهی FreeRTOS را بر روی آردوینو اجرا کنیم. اولین قدم برای این کار این است که کتابخانههای FreeRTOS را در Arduino IDE نصب کنیم.
نصب کتابخانهی FreeRTOS در Arduino IDE
- IDE را باز کنید و به مسیر زیر بروید
Sketch -> Include Library -> Manage Libraries
در آنجا FreeRTOS را جستجو کنید و کتابخانهای که ارائه میشود را مانند تصویر زیر نصب کنید.
یک راه دیگر هم این است که کتابخانه را از گیتهاب دانلود کنید و فایل زیپ آن را به مسیر زیر اضافه کنید.
Sketch-> Include Library -> Add .zip
حالا یک بار IDE را ببندید و دوباره باز کنید. اضافه شدن این کتابخانه ی جدید موجب شده است که مثالهای جدیدی نیز به IDE اضافه شوند که مانند شکل زیر در مسیر File -> Examples -> FreeRTOS در دسترس هستند.
بعد از اینکه این آموزش را با هم طی کردیم، میتوانید به این مثالها مراجعه کنید و با آشنایی بیشتری از آنها استفاده کنید.
مدار مورد نیاز برای پروژه
در تصویر زیر مداری که برای اجرای این پروژه نیاز داریم را میبینید.
مراحل ایجاد یک FreeRTOS task در Arduino IDE
این مراحل را با اجرای یک مثال ساده با هم مرور میکنیم.
- ابتدا هدر فایل مربوط به FreeRTOS را مینویسیم.
#include <Arduino_FreeRTOS.h>
- تمام توابعی که قرار است استفاده کنید را به شکل زیر معرفی کنید.
void Task1( void *pvParameters ); void Task2( void *pvParameters ); .. ….
- حالا در بخش تابع ()task ،void setupها را ایجاد کرده و آنها را زمانبندی میکنیم. برای ایجاد هر xTaskCreate() ،task که یک API است فراخوانی میشود که آرگومانهای آن را باید خودمان مشخص کنیم.
xTaskCreate( TaskFunction_t pvTaskCode, const char * const pcName, uint16_t usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pxCreatedTask );
این آرگومانها شامل ۶ آرگومان مختلف هستند که آنها را در ادامه توضیح خواهیم داد.
- pvTaskCode: این آرگومان در واقع یک پوینتر است که به نام تابعی که قرار است task آن را اجرا کند اشاره میکند. در عمل چیزی که میبینیم همان نام تابع است.
- pcName: نامی که آن task را توصیف میکند. این آرگومان در واقع با هدف دیباگ کردن آسان اضافه شده است و کارکردی برای FreeRTOS ندارد.
- usStackDepth: هر task دارای یک stack (پشته) است که در زمان ایجاد آن توسط kernel به آن اختصاص داده میشود. این عدد در واقع نشان دهندهی تعداد کلماتی است که آن پشته میتواند نگه دارد. بنابراین دقت کنید که عدد مذکور نشان دهندهی تعداد بایتها نیست. به عنوان مثال؛ اگر stack موجود ۳۲ بیتی است و مقدار آرگومان usStackDepth نیز ۱۰۰ ذکر شده است، ۴۰۰ بایت (4×100) از فضای stack به این task اختصاص خواهد یافت. در استفاده از این آرگومان دقت داشته باشید که بورد آردوینو Uno دارای حافظهی رم 2Kbytes است.
- pvParameters: پارامتر ورودی task که میتواند خالی گذاشته شود.
- uxPriority: اولویت آن task میباشد.
- pxCreatedTask: چیزی شبیه یک نوع برچسب (label) که بعدا میتوانیم به کمک آن به این task ارجاع بدهیم. مثلا در APIهایی که برای تغییر اولویت taskها و یا حذف آنها استفاده میکنیم.
یک مثال را برای ایجاد task به عنوان نمونه ببینید.
xTaskCreate(task1,"task1",128,NULL,1,NULL); xTaskCreate(task2,"task2",128,NULL,2,NULL);
در اینجا task2 اولویت بالاتری دارد و اول اجرا خواهد شود.
- پس از ایجاد task باید scheduler را برای آن در یک void setup ایجاد کنیم. این کار را با استفاده از این API انجام میدهیم.
vTaskStartScheduler();
5. قسمت تابع حلقه (()void lop) را خالی میگذاریم چون نمیخواهیم کاری به صورت بینهایت تکرار شود. در واقع چون از scheduler استفاده میکنیم، هر زمان که نیاز باشد task مورد نظر اجرا خواهد شد. پس نیازی به حلقه نیست.
6.بسیار خب حالا باید کاری را که میخواهیم در درون این task انجام شود (محتوای آن) را به صورت تابع منطقی بیان کنیم. نام این تابع دقیقا باید برابر با همان آرگون اولی باشد که در xTaskCreate استفاده کردیم.
void task1(void *pvParameters) { while(1) { .. ..//your logic } }
- برای متوقف کردن taskها نیاز به delay داریم اما در RTOS استفاده از تابع ()Delay توصیه نمیشود. چرا که کل CPU و خود RTOS را نیز متوقف میکند. در عوض؛ از یک API مخصوص خود FreeRTOS استفاده میکنیم.
vTaskDelay( const TickType_t xTicksToDelay );
این API در واقع با هدف ایجاد تاخیر استفاده میشود و با استفاده از آن میتوان task مورد نظر را برای چند بازهی زمانی متوقف نگه داشت. طول این زمان بستگی به نرخ تکرار بازههای زمانی دارد. اگر نیاز داشته باشیم که این مدت زمان را به طور دقیق محاسبه کنیم میتوانیم از portTICK_PERIOD_MS استفاده کنیم. بنابراین اگر به طور مثال یک تاخیر ۲۰۰ میلیثانیهای بخواهیم، کافیست دستور زیر را بنویسیم.
vTaskDelay( 200 / portTICK_PERIOD_MS );
در پروژهای که ما در این جلسه داریم، میخواهیم سه task مختلف ایجاد کنیم.
- چشمک زدن LED پین ۸ با فرکانس 200ms
- چشمک زدن LED پین ۷ با فرکانس 300ms
- نمایش اعدادی بر روی مانیتور سریال با فرکانس 500ms
API هایی که استفاده میکنیم به شرح زیر میباشند.
- xTaskCreate();
- vTaskStartSchedule()r;
- vTaskDelay();
پیادهسازی و اجرای Taskها در آردوینو
- با توجه به توضیحات قسمت قبل پیش میرویم و ابتدا هدر فایل FreeRTOS را اضافه میکنیم. سپس سه تابعی که قرار است استفاده کنیم را تعریف میکنیم.
#include <Arduino_FreeRTOS.h> void TaskBlink1( void *pvParameters ); void TaskBlink2( void *pvParameters ); void Taskprint( void *pvParameters );
- در ()void setup، یک ارتباط سریال را با بادریت ۹۶۰۰ آغاز میکنیم و با استفاده از ()task ،xTaskCreateهای مورد نظرمان را ایجاد میکنیم. در ابتدا اولویت هر سه را هم برابر با یک قرار میدهیم.
void setup() { Serial.begin(9600); xTaskCreate(TaskBlink1,"Task1",128,NULL,1,NULL); xTaskCreate(TaskBlink2,"Task2 ",128,NULL,1,NULL); xTaskCreate(Taskprint,"Task3",128,NULL,1,NULL); vTaskStartScheduler(); }
- حال هر سه را مانند کد زیر تعریف میکنیم.
void TaskBlink1(void *pvParameters) { pinMode(8, OUTPUT); while(1) { digitalWrite(8, HIGH); vTaskDelay( 200 / portTICK_PERIOD_MS ); digitalWrite(8, LOW); vTaskDelay( 200 / portTICK_PERIOD_MS ); } }
- Taskهای دوم و سوم را نیز به همین ترتیب ایجاد میکنیم.
void Taskprint(void *pvParameters) { int counter = 0; while(1) { counter++; Serial.println(counter); vTaskDelay( 500 / portTICK_PERIOD_MS ); } }
بسیار خب. تمام شد. تمام کاری که ما باید انجام میدادیم همین بود و حالا میتوانیم پروژه را تست کنیم. کد کامل و ویدئوی اجرا پروژه را نیز به روال همیشه در انتهای جلسه در دسترستان قرار دادهایم.
برای تست پروژه، دو LED به پینهای ۷ و ۸ وصل کنید، کد را بر روی بورد آپلود کنید و serial monitor را نیز باز کنید. خواهید دید که مانند تصویر زیر، نام هر task در حال اجرا نوشته شده و اعداد نیز به همان صورت که خواسته بودیم بر روی صفحه نمایش داده میشوند.
اگر به LEDها نیز دقت کنید، میبینید که با فواصل زمانی متفاوت چشمک میزنند. برای تمرین و یادگیری بیشتر، یک پیشنهاد این است که اولویتهای taskها را متفاوت قرار دهید و نحوهی اجرای پروژه را ببینید.
حالا اگر به دو مثال اولی که در خود کتابخانه وجود دارند مراجعه کنید میتوانید از آنها سر در بیاورید. یکی مربوط به خواندن مقادیر آنالوگ و دیگری مربوط به خواندن مقادیر دیجیتال است. به این ترتیب با APIهای بیشتری آشنا میشوید و میتوانید پروژههای پیشرفتهتری بسازید.
کد
#include <Arduino_FreeRTOS.h> void TaskBlink1( void *pvParameters ); void TaskBlink2( void *pvParameters ); void Taskprint( void *pvParameters ); void setup() { // initialize serial communication at 9600 bits per second: Serial.begin(9600); xTaskCreate( TaskBlink1 , "task1" , 128 , NULL , 1 , NULL ); xTaskCreate( TaskBlink2 , "task2" , 128 , NULL , 1 , NULL ); xTaskCreate( Taskprint , "task3" , 128 , NULL , 1 , NULL ); vTaskStartScheduler(); } void loop() { } void TaskBlink1(void *pvParameters) { pinMode(8, OUTPUT); while(1) { Serial.println("Task1"); digitalWrite(8, HIGH); vTaskDelay( 200 / portTICK_PERIOD_MS ); digitalWrite(8, LOW); vTaskDelay( 200 / portTICK_PERIOD_MS ); } } void TaskBlink2(void *pvParameters) { pinMode(7, OUTPUT); while(1) { Serial.println("Task2"); digitalWrite(7, HIGH); vTaskDelay( 300 / portTICK_PERIOD_MS ); digitalWrite(7, LOW); vTaskDelay( 300 / portTICK_PERIOD_MS ); } } void Taskprint(void *pvParameters) { int counter = 0; while(1) { counter++; Serial.println(counter); vTaskDelay(500 / portTICK_PERIOD_MS); } }
ویدئو
منبع: ترجمه از سایت circuitdigest.com
امیداوریم آموزش FreeRTOS در آردوینو برایتان مفید واقع شده باشد. برای آنکه در بهبود کیفیت مطالب ارائه شده به ما کمک کنید، در قسمت کامنتها ما را مطلع کنید که از نظر شما این آموزش چطور بود؟ مهمترین نکتهای که از آن آموختید و بهترین ایدهای که در ذهن شما ایجاد کرد چه چیزی است؟
اگر این نوشته برایتان مفید بود لطفا کامنت بنویسید.
بسیار مجموعه عالی هستید سپاسسسسسسسس
PERFECT BUD… THANKS
سلام. خیلی ممنون از سایت خوب شما.
بنده خیلی استفاده کردم از اینکه در بخش کدنویسی در درایو موتور با چنین چالشی روبرور بودم که راهگشا بود. موفق باشید.
عالی بود، چندین آموزش دیدم همه ایرادهایی داشتند، ولی آموزش شما کامل . بدون مشکل اجرا شد و با همین روش تعداد تسک ها رو افزایش دادم و بدون مشکل اجرا شد.