در سومین قسمت از سلسله آموزش های سیستم عامل FreeRTOS روی ESP32، قسمت سوم به مفهوم صف می پردازیم. صف در سیستم عامل یکی از روش های مهم و پرکابرد در زمینه انتقال داده بین Task هاست. به کمک این فناوری شما می توانید داده های خود منتقل کنید. در سیستم عامل FreeRTOS نیز مفهوم صف به خوبی شناخته و پیاده سازی شده است. در این آموزش نیز قصد داریم تا به بررسی این مفهوم بپردازیم. مطابق رویه معمول آموزش ها، ابتدا به معرفی و بررسی مفهوم صف خواهیم پرداخت. سپس در ادامه دستورات صف را بررسی خواهیم کرد. در نهایت با ارایه یک مثال برنامه نویسی،به شرح بیشتری از جزییات خواهیم پرداخت. بنابراین در ادامه آموزش Queue صف با مرجع تخصصی سیستم عامل FreeRTOS به زبان فارسی، دیجی اسپارک همراه باشید.
مفهوم صف در سیستم های کامپیوتری
یکی از مهم ترین ساختارهای داده در سیستم های کامپیوتری، Queue یا همان صف ها هستند. در علم رایانه، ساختار های داده های مختلفی داریم. آرایه، ساختمان، unio، پشته و…. همگی جزو مهم ترین ساختارها به شمار می روند. ساختار داده صف هم در کنار سایر ساختارها، کاربردهای فراوانی دارد. یک صف در یک سیستم کامپیوتری دقیقا همان مفهوم صف در دنیای واقعی را دارد. واضح ترین مثال برای صف را می توان صف نانوایی دانست. در یک صف نانوایی افراد پشت سر هم قرار قرار می گیرند. هر فردی که زودتر رسیده باشد، نان را زودتر دریافت کرده و از صف خارج می شود. سایر افراد نیز به همین ترتیب نان خود را دریافت کرده و تا صف خالی شود.
در یک سیستم کامپیوتری هم صف دقیقا به همین منوال پیاده سازی می شود. تعدادی عضو که منتظر دریافت سرویس هستند. به عنوان مثال فرض کنید تعدادی عدد باید در حافظه ذخیره شوند. از آنجاییکه امکان ذخیره همزمان تمام اعداد وجود ندارد، بنابراین این اعداد باید در یک صف قرار گیرند تا به صورت مرحله به مرحله هر کدام از صف خارج شده و در حافظه ذخیره گردند.
پیاده سازی Queue صف در سیستم کامپیوتری
جهت پیاده سازی Queue صف در سیستم های کامپیوتری، نیازمند استفاده از آرایه ها یا لیست های پیوندی هستیم. به عنوان مثال فرض کنید یک آرایه ۵ عضوی به شکل زیر داریم.
Int ar[5]={1,2,3,4,5}
مطابق تعریف فوق، یک صف ۵ عضوی داریم. این صف اعداد ۱ الی ۵ را در خود ذخیره کرده است. حال برای دسترسی ترتیبی، می توانیم به عناصر آرایه دسترسی داشته باشیم. در کنار این، برای پیاده سازی صف می توانیم از لیست های پیوندی هم استفاده کنیم. یک لیست پیوندی مشابه یک ارایه اما با تعداد عناصر نامحدود(به اندازه حافظه!) است. در این روش به کمک تخصیص حافظه پویا، می توانیم حافظه را در اختیار قرار دهیم. در یک لیست پیوندی، آدرس شروع در اختیار است. با طی کردن آدرس و حرکت از یک جز به جز دیگر، می توان به عناصر لیست دسترسی داشت.
تعریف صف در سیستم عامل نیازمند استفاده از دستورات خاصی است. در قسمت های بعد به این موضوع می پردازیم. اما پیش از آنکه به دستورات بپردازیم، خوب است تا کمی به بررسی این مفهوم در سیستم عامل بپردازیم. همانطور که پیشتر گفته شد، صف مجموعه ای از داده های پشت سر هم است که منتظر پردازش هستند. در یک سیستم عامل، صف به عنوان وسیله ای جهت تبادل داده بین دو Task دیده و تعریف می شود. دو Task را در نظر بگیرید. یک Task وظیفه خواندن کانال های ADC A0 الی A5 را بر عهده دارد. از طرف دیگر، Task شماره ۲ وظیفه تجزیه و تحلیل و پردازش داده ها را بر عهده دارد. در این جا به کمک صف، می توانیم داده ها را از Task شماره ۱ به Task شماره ۲ انتقال دهیم.
دستورات صف در سیستم عامل
مطابق رویه معمول، ابتدا باید از کلاس کتابخانه شی تعریف شود. به همین منظور، ابتدا مطابق دستور زیر از کلاس صف، یک شی با نام دلخواه تعریف می کنیم.
QueueHandle_t structQueue
پس از دستور فوق، باید تابع سازنده کلاس کتابخانه را نیز، فراخوانی کنیم. به همین منظور، به شیوه زیر عمل می کنیم.
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,UBaseType_t uxItemSize )
مطابق دستور فوق نوع و اندازه صف باید در ورودی های اول و دوم تعیین گردد. در مثالی که در ادامه خواهد آمد، قصد داریم تا تعدادی ورودی از پایه ها بخوانیم و در قالب صف ارسال کنیم. به همین منظور یک آرایه تعریف خواهیم کرد. برای تعیین حجم صف، به شکل زیر عمل می کنیم.
structQueue = xQueueCreate(10, sizeof(int) )
مطابق دستور فوق، تعداد اعضای صف ۱۰ عنصر و ورودی دوم نیز حجم اعضا که int هستند را مشحص می کند. ساختمان تعیین شده است.
BaseType_t xQueueSend( QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait );
در تابع فوق، ورودی ها از سمت چپ به راست به ترتیب زیر تعریف می گردند.
- شی تعریف شده از کلاس صف
- مقداری که قرار است در صف قرار بگیرد
- مدت زمان انتظار جهت خالی شدن صف برای قرار گرفتن داده جدید
در ادامه نیز، Task دیگری که وظیفه دریافت داده از طریق صف را دارد، داده ها را به شکل زیر دریافت می کند.
۱
۲ ۳ ۴ ۵ ۶ ۷ ۸ ۹ |
BaseType_t xQueueReceive(
QueueHandle_t xQueue,
void *pvBuffer,
TickType_t xTicksToWait
); |
مطابق تابع فوق، ورودی ها از چپ به راست به ترتیب تعریف می گردد.
۱-شی تعریف شده از کلاس Queue صف
۲-مقداری که باید الان خوانده شود.
۳-مدت زمان انتطار برای دریافت داده
اجرای یک نمونه برنامه Queue
پس از بررسی دستورات، نوبت به اجرای یک نمونه برنامه می رسد. به کدهای برنامه دقت کنید. در این برنامه چندین Task تعریف شده که دو مورد از آن ها وظیفه تبادل داده با یکدیگر را از طریق صف، بر عهده دارند.
int pinReadArray[4]={0,0,0,0}; //آرایه جهت خواندن پایه ها //تعریف Task ها void TaskBlink(void *pvParameters); void TaskAnalogReadPin0(void *pvParameters); void TaskAnalogReadPin1(void *pvParameters); void TaskSerial(void *pvParameters); //تعریف یک شی از کلاس کتابخانه QueueHandle_t arrayQueue; void setup() { arrayQueue=xQueueCreate(10, //تعداد اعضای صف sizeof(int)); //حجم هر عضو از صف if(arrayQueue!=NULL){ //در صورتیکه صف با موفقیت اجیاد شده باشد // تعریف Task ها //Task مربوط به سریال و نمایش داده xTaskCreate(TaskSerial,// "PrintSerial",// ۱۲۸,// NULL, ۲,// NULL); //Task مربوط به خواندن از ورودی آنالوگ و ارسال در صف xTaskCreate(TaskAnalogReadPin0, // Task function "AnalogRead1",// ۱۲۸,// NULL, ۱,// NULL); //خواندن از ورودی آنلاوگ xTaskCreate(TaskAnalogReadPin1,// "AnalogRead2",// ۱۲۸,// NULL, ۱,// NULL); xTaskCreate(TaskBlink,// "Blink", // ۱۲۸,// NULL, ۰,// NULL); } } void loop() {} void TaskAnalogReadPin0(void *pvParameters){ (void) pvParameters; for (;;){ pinReadArray[0]=0; pinReadArray[1]=analogRead(A0); xQueueSend(arrayQueue,&pinReadArray,portMAX_DELAY); vTaskDelay(1); } } void TaskAnalogReadPin1(void *pvParameters){ (void) pvParameters; for (;;){ pinReadArray[2]=1; pinReadArray[3]=analogRead(0); xQueueSend(arrayQueue,&pinReadArray,portMAX_DELAY); vTaskDelay(1); } } void TaskSerial(void *pvParameters){ (void) pvParameters; Serial.begin(9600); while (!Serial) { vTaskDelay(1); } for (;;){ if(xQueueReceive(arrayQueue,&pinReadArray,portMAX_DELAY) == pdPASS ){ Serial.print("PIN:"); Serial.println(pinReadArray[0]); Serial.print("value:"); Serial.println(pinReadArray[1]); Serial.print("PIN:"); Serial.println(pinReadArray[2]); Serial.print("value:"); Serial.println(pinReadArray[3]); vTaskDelay(500/portTICK_PERIOD_MS); } } } void TaskBlink(void *pvParameters){ (void) pvParameters; pinMode(4,OUTPUT); digitalWrite(4,LOW); for (;;){ digitalWrite(4,HIGH); vTaskDelay(250/portTICK_PERIOD_MS); digitalWrite(4,LOW); vTaskDelay(250/portTICK_PERIOD_MS); } }
لوازم مورد نیاز
لینک خرید برد ESP32، کلیک کنید
جمع بندی
در مجموعه آموزش های سیستم عامل برای ESP32 این قسمت را به یکی از مهم ترین و کلیدی ترین مفاهیم این سیستم عامل، صف پرداختیم. به کمک ساختار داده صف می توانیم داده ها را بین دو Task جا به جا کنیم. بدین ترتیب تعداد زیادی از داده ها می توانند توسط یک Task دریافت و جهت پردازش به Task دیگر ارسال شوند. در این آموزش به تحلیل و بررسی این مفهوم پرداختیم؛ با دستورات و نحوه تعریف آن آشنا شدیم و در نهایت با ارایه یک مثال، به بحث خاتمه دادیم.
چنانچه در مراحل راه اندازی و انجام این پروژه با مشکل مواجه شدید، بدون هیچ نگرانی در انتهای همین پست، به صورت ثبت نظر سوالتان را مطرح کنید. من در سریعترین زمان ممکن پاسخ رفع مشکل شما را خواهم داد. همچنین اگر ایرادی در کدها و یا مراحل اجرایی وجود دارند میتوانید از همین طریق اطلاع رسانی کنید.
سلام مهندس صابری . خدا قوت بده به شما .
این سیستم عامل روی ESP8266 قابل اجرا نیست ؟
سلام و متشکرم از شما
قابلیت اجرا دارد، اما به این شکل نیست. در esp8266 باید SDK آن روی ESP نصب شود و دستورات کاملا متفاوت است. با دستورات آردوینو نبوده و با دستورات مستقیم خود سیستم عامل است.