آموزش Task در ESP IDF ESP32

مقدمه

در اینترنت اشیا یوکاسافت از تراشه ESP32 برای ساخت دستگاه های خانه هوشمند استفاده می شود. خانه هوشمند خانه ای است که در آن وسایل و دستگاه ها را می توان از راه دور از هر تلفن یا رایانه ای با اتصال به اینترنت کنترل کرد. در این آموزش یوکاسافت، استفاده از FreeRTOS با ESP32 را با استفاده از چارچوب ESP-IDF یاد خواهیم گرفت. 

در این آموزش، ایجاد وظایف، حذف، تنظیم اولویت، وقفه های وظیفه و غیره را در FreeRTSO یاد می گیریم. هنگام استفاده از ESP-IDF، اکثر کدها از قابلیت های Vanilla FreeRTOS v10.4.3 استفاده می کنند.

یک برنامه معمولاً از مجموعه ای از وظایف مستقل یا زیر برنامه تشکیل می شود. در یک MCU تک هسته ای، تنها یک کار می تواند در یک زمان اجرا شود. از طرف دیگر، در یک پردازنده دو هسته ای مانند ESP32 دو وظیفه می توانند همزمان اجرا شوند، زیرا این دو کار هیچ وابستگی به یکدیگر ندارند. زمانبندی FreeRTOS این وظایف را بر اساس اولویت، دوره زمانی و زمان اجرای آنها زمانبندی می کند. برنامه زمانبندی FreeRTOS اغلب با ادامه اجرای برنامه، هر کار را شروع و متوقف می کند. یک کار، درک درستی از فعالیت زمانبندی RTOS ندارد، بنابراین، این مسئولیت زمانبندی FreeRTOS است که زمینه پردازنده (مقادیر ثبت، محتویات پشته و غیره) را تایید کند. هنگامی که یک کار روشن می شود دقیقاً مانند زمانی است که همان کار تعویض شده است. برای این منظور، هر وظیفه با پشته مخصوص به خود ارائه می شود. هنگامی که وظیفه تعویض می‌شود، زمینه اجرا در پشته آن وظیفه ذخیره می‌شود، بنابراین می‌توان آن را زمانی که همان کار بعداً مجدداً جایگزین شد دقیقاً بازیابی کرد. در اینجا  می توانید اطلاعات بیشتری در مورد وظایف FreeRTOS بیابید. 

API های FreeRTOS ویژگی هایی را برای برنامه ریزی، ایجاد، حذف، تعلیق، از سرگیری و تعیین اولویت وظایف ارائه می دهند. نمونه هایی از این موارد را در طول آموزش یوکاسافت خواهیم دید.

ایجاد نمونه کار

 کد VS خود را باز کنید و یک پروژه ESP-IDF جدید ایجاد کنید. سپس به فایل main.c بروید. در اینجا توابع و کد برنامه را تعریف می کنیم. برای شروع، فایل های هدر FreeRTOS را اضافه کنید. کتابخانه freertos/task.h ما را قادر می سازد تا از وظایف FreeRTOS استفاده کنیم...


ساخت اولین Task با ()xTaskCreate

در داخل تابع ()main ، وظایف را ایجاد می کنیم. برای ایجاد یک کار، از تابع ()xTaskCreate  استفاده کنید. این تابع چندین آرگومان می گیرد. 

اولین آرگومان نام تابع است. در مورد ما، آن را روی Demo_Task تنظیم کرده ایم. 

آرگومان دوم نام وظیفه برای اهداف توصیفی است. این معمولاً برای کمک به اشکال زدایی و به دست آوردن یک دسته کار مورد نیاز است. در دو علامت نقل قول مشخص شده است، بنابراین در مورد ما آرگومان دوم را به عنوان "Demo_Task" تنظیم می کنیم. 

آرگومان سوم اندازه پشته کار را مشخص می کند. این نشان دهنده مقدار حافظه ای است که می خواهیم برای یک کار خاص ذخیره کنیم. برای کارایی بیشتر، کاربر می تواند پس از ایجاد کار و به دست آوردن اندازه پشته کار، اندازه پشته را تنظیم کند. ما آن را روی "4096" تنظیم کرده ایم. 

آرگومان چهارم پارامتر است. این مقداری است که به عنوان پارامتر به وظیفه ایجاد شده ارسال می شود. این می تواند برای تعیین متغیری استفاده شود که در تابع اصلی استفاده می شود و مقدار آن باید به کار اضافه شود. روش دیگر پیاده سازی، ایجاد یک متغیر سراسری است. در مورد ما، این آرگومان را به صورت NULL تنظیم می کنیم که نشان می دهد ما از این ویژگی استفاده نمی کنیم.

آرگومان پنجم اولویت تکلیف است. ما آن را روی '10 تنظیم کرده ایم. 

آخرین آرگومان handle ای است که برای تغییر عملکرد وظیفه به عنوان تعلیق، حذف، از سرگیری، دریافت یا تنظیم یک پیکربندی از کار استفاده می شود. این به عنوان یک اشاره گر عمل می کند بنابراین نماد & با آن استفاده می شود. اختیاری است بنابراین می توانیم آن را روی NULL نیز تنظیم کنیم. در مورد ما آن را روی myTaskHandle& تنظیم کرده ایم.

Task Handle 

task handle به عنوان یک متغیر سراسری خارج از تابع اصلی تعریف می شود. ما آن را به عنوان NULL تنظیم کرده ایم.

Function اولین Task

اکنون تعریف تابع Demo_Task را ایجاد می کنیم. در اینجا در داخل حلقه بی نهایت while بعد از هر ثانیه، در حال چاپ پیامی در ترمینال هستیم. باید از یک حلقه بی نهایت استفاده کنید در غیر این صورت میکروکنترلر به تنظیم مجدد ادامه خواهد داد.

()xTaskCreatePinnedToCore برای ایجاد Task دوم 

به همین ترتیب،یک کار دیگر Demo_Task2 ایجاد میکنیم و عملکرد آن را تعریف میکنیم تا بتوانیم هر دو کار را به طور همزمان مشاهده کنیم. برای انجام این کار از تابع ()xTaskCreatePinnedToCore استفاده می کنیم. کاربر را قادر می سازد تا انتخاب کند کدام هسته ESP32 (هسته 0 یا هسته 1) وظیفه خاص را اجرا کند. اجازه دهید core1 را برای کار دوم انتخاب کنیم. ()xTaskCreatePinnedToCore یک آرگومان اضافی در مقایسه با تابع xTaskCreate می گیرد. این آرگومان هفتم است که نشان دهنده هسته ای است که برای کار ایجاد شده استفاده می شود. کاربر می تواند "0" یا 1" را به عنوان آرگومان مشخص کند. اجازه دهید core1 را برای وظیفه دوم که Demo_Task2 است انتخاب کنیم.

کد و نمایش کامل

 این کد کاملی است که ما از آن استفاده می کنیم. در اینجا ما دو وظیفه ایجاد کرده ایم که به طور همزمان اجرا می شوند. Demo_Task1 "Demo_Task printing.." و Demo_Task2 "Demo_Task printing.." را در ترمینال چاپ می کند.

#include <stdio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
TaskHandle_t myTaskHandle = NULL;
TaskHandle_t myTaskHandle2 = NULL;
void Demo_Task(void *arg)
{
    while(1){
        printf("Demo_Task printing..\n");
        vTaskDelay(1000/ portTICK_RATE_MS);
    }
}
void Demo_Task2(void *arg)
{
    while(1){
        printf("Demo_Task2 printing..\n");
        vTaskDelay(1000/ portTICK_RATE_MS);
    }
}
void app_main(void)
{
   xTaskCreate(Demo_Task, "Demo_Task", 4096, NULL, 10, &myTaskHandle);
   xTaskCreatePinnedToCore(Demo_Task2, "Demo_Task2", 4096, NULL,10, &myTaskHandle2, 1);
 }

برای فلش کردن چیپ خود، دستور زیر را در ترمینال سریال تایپ کنید. به یاد داشته باشید که پورت COM را با پورتی که برد شما از طریق آن وصل شده است جایگزین کنید.

idf.py -p COMX flash monitor
خروجی نهایی در تصویر زیر نشان داده شده است.



مثال حذف Task

اکنون اجازه دهید کد بالا را تغییر دهیم تا عملکرد حذف وظیفه را به شما نشان دهیم. به تابع ()Demo_Task2 اصلاح شده در زیر نگاه کنید. به جای استفاده از یک حلقه while بی نهایت، ما یک حلقه for قرار داده ایم که پیام را بعد از هر ثانیه چاپ می کند. این حلقه بعد از 5 ثانیه به پایان می رسد، بنابراین ESP32 در آن نقطه تنظیم مجدد می شود.


اجازه دهید این کار را بعد از 5 ثانیه حذف کنیم تا ESP32 ریست نشود. برای حذف یک کار از تابع ()vTaskDelete استفاده می کنیم و task handle را به عنوان یک پارامتر در داخل آن مشخص می کنیم. اگر NULL را پاس کنیم، باعث می شود که وظیفه فراخوانی حذف شود.

در داخل تابع ()Demo_Task ابتدا یک عدد صحیح "count" ایجاد میکنیم که تعداد ثانیه ها را نگه می دارد. وقتی تعداد به 3 رسید، تابع ()vTaskDelete را فراخوانی می کنیم و handle Demo_Task2 را به عنوان پارامتر داخل آن مشخص می کنیم. این تابع در داخل تابع ()Demo_Task فراخوانی می شود، از این رو، وظیفه اول، وظیفه دوم را پس از 5 ثانیه حذف می کند. به این ترتیب ESP32 ریست نمی شود و اولین کار همچنان در حال اجراست


در زیر می توانید به کد کاملی دسترسی داشته باشید که در آن تابع ()vDeleteTask را نشان می دهیم.

#include <stdio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
TaskHandle_t myTaskHandle = NULL;
TaskHandle_t myTaskHandle2 = NULL;
void Demo_Task(void *arg)
{
    int count = 0;
    while(1){
        count++;
        printf("Demo_Task printing..\n");
        vTaskDelay(1000/ portTICK_RATE_MS);
        if (count == 5)
        {
          vTaskDelete(myTaskHandle2);
          printf("Demo_Task2 is deleted!\n");
        }
    }
}
void Demo_Task2(void *arg)
{
    for(int i=0;i<5;i++){
        printf("Demo_Task2 printing..\n");
        vTaskDelay(1000/ portTICK_RATE_MS);
    }
}
void app_main(void)
{
   xTaskCreate(Demo_Task, "Demo_Task", 4096, NULL, 10, &myTaskHandle);
   xTaskCreatePinnedToCore(Demo_Task2, "Demo_Task2", 4096, NULL,10, &myTaskHandle2, 1);
 }
برای فلش کردن چیپ خود، دستور زیر را در ترمینال سریال تایپ کنید. به یاد داشته باشید که پورت COM را با پورتی که برد شما از طریق آن وصل شده است جایگزین کنید.

idf.py -p COMX flash monitor
خروجی نهایی در تصویر زیر نشان داده شده است.

نمونه کار Suspend & Resume 

برای تعلیق یک کار، از تابع ()vTaskSuspend استفاده می کنیم و task handle را به عنوان پارامتر داخل آن مشخص می کنیم. اگر NULL را پاس کنیم، باعث تعلیق وظیفه فراخوانی شده می شود


برای از سرگیری یک کار، از تابع ()vTaskResume استفاده می کنیم و task handle را به عنوان یک پارامتر در داخل آن مشخص می کنیم. اگر از NULL عبور کنیم، باعث می شود که وظیفه فراخوانی از سر گرفته شود.

اصلاحات کد 

ابتدا کد بالا را تغییر می دهیم تا محدودیت Demo_Task2 به 10 ثانیه افزایش یابد.

سپس در داخل تابع Demo_Task()، Demo_Task2 را پس از 5 ثانیه به حالت تعلیق در می آوریم، سپس آن را پس از 3 ثانیه از سر می گیریم. پس از 10 ثانیه حذف می شود تا ESP32 ریست نشود.

کد و نمایش کامل 

در زیر می توانید به کد کاملی دسترسی پیدا کنید که در آن توابع ()vSuspendTask()، vResumeTaskو ()vDeleteTask را نشان می دهیم.

#include <stdio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
TaskHandle_t myTaskHandle = NULL;
TaskHandle_t myTaskHandle2 = NULL;
void Demo_Task(void *arg)
{
    int count = 0;
    while(1){
        count++;
        printf("Demo_Task printing..\n");
        vTaskDelay(1000/ portTICK_RATE_MS);
        if (count == 5)
        {
          vTaskSuspend(myTaskHandle2);
          printf("Demo_Task2 is suspended!\n");
        }
        if (count == 8)
        {
          vTaskResume(myTaskHandle2);
          printf("Demo_Task2 is resumed!\n");
        }
        if (count == 10)
        {
          vTaskDelete(myTaskHandle2);
          printf("Demo_Task2 is deleted!\n");
        }
    }
}
void Demo_Task2(void *arg)
{
    for(int i=0;i<10;i++){
        printf("Demo_Task2 printing..\n");
        vTaskDelay(1000/ portTICK_RATE_MS);
    }
}
void app_main(void)
{
   xTaskCreate(Demo_Task, "Demo_Task", 4096, NULL, 10, &myTaskHandle);
   xTaskCreatePinnedToCore(Demo_Task2, "Demo_Task2", 4096, NULL,10, &myTaskHandle2, 1);
 }

رای فلش کردن چیپ خود، دستور زیر را در ترمینال تایپ کنید. به یاد داشته باشید که پورت COM را با پورتی که برد شما از طریق آن وصل شده است جایگزین کنید.

idf.py -p COMX flash monitor
خروجی ترمینال سریال در زیر نشان داده شده است. توجه داشته باشید که Demo_Task2 به مدت 5 ثانیه اجرا شد و سپس به مدت سه ثانیه به حالت تعلیق درآمد. پس از پایان سه ثانیه دوباره شروع شد. پس از 10 ثانیه، Demo_Task2 حذف می شود، اما Demo_Task بدون تنظیم مجدد ESP32 به کار خود ادامه می دهد.

برای اطلاعات بیشتر  این لینک  را مشاهده کنید.


ویژگی های Esp32
مقایسه esp32 و esp8266