فهرست مطالب
در این مطلب آموزشی چگونگی تبادل داده بین تسکها با استفاده از Queue بررسی شده است. در این بخش تنها حالت تسک-تسک بیان شده است و تبادل داده بین تسک و وقفه در بخشهای پنجم و بعد از اشنایی با مکانیزم وقفه بیان شده است. در انتهای این بخش با استفاده از یک مثال عملی نحوه استفاده از Queue بیان شده است. در این مثال نحوه خواندن داده از سنسور BMP180 که یک سنسور فشار است شرح داده شده است.
سخت افزار مورد نیاز
- سنسور BMP180
- هر یک از میکروکنترلرهای 32 بیتی شرکت ST. در این پروژه از بورد STM32F4DISCOVERY استفاده شده است.
نرم افزار مورد نیاز
- stm32cubemx
- یک نرم افزار برای کامپایل و پروگرم کردن میکروکنترلر. در این پروژه از بورد IAR استفاده شده است.
معرفی Queue
Queue برای ذخیره داده هایی با طول ثابت استفاده میشود. ایده کلی Queue در شکل 1 نشان داده شده است. در این شکل تسک Producer داده را با استفاده از Queue به تسک consumer ارسال میکند. در حالت کلی تعداد تسکهایی که میتوانند در Queue بنویسند یا از آن بخوانند میتواند بیشتر از یک باشد.
Queue ها معمولا بصورت First In First Out استفاده میشوند که داده جدید در انتهای Queue نوشته شده و داده قدیمی از ابتدای آن خوانده میشود. در شکل 2 نحوه کار Queue نشان داده شده است. به حداکثر تعداد دادههایی که Queue میتواند نگه دارد، طول Queue گفته میشود. اندازه Queue برابر اندازه المانهای Queue است. اندازه و طول Queue هنگامی تعریف Queue تنظیم میشوند.
در حالت کلی Queue به دو روش Queue by copy و Queue by reference قابل پیاده سازی است. در روش Queue by copy داده عینا در داخل Queue کپی میشود ولی در روش دوم اشاره گر داده به Queue داده میشود.
در سیستم عامل FreeRTOS از روش اول استفاده شده است. مزیت این روش به روش دوم سادگی و در عین حال قدرتمندتر بودن آن است. در این روش نیازی به تخصیص حافظه و ارسال آدرس آن ندارد و فرستنده میتواند بلافاصله از بافر موردنظر استفاده کند. فرستنده و گیرنده Queue کاملا مستقل از هم هستند. همچنین با استفاده از این روش میتوان آدرس و اشاره گر بافر موردنظر را نیز ارسال نمود.
در سیستم عامل FreeRTOS هنگامیکه یک تسک قصد نوشتن داده در Queue را داشته باشد، میتواند بطور اختیاری یک مدت زمان Block را مشخص کند. در این حالت هنگامیکه Queue پر باشد و جایی برای نوشتن داده جدید نباشد، تسک را به حالت کاری Block میرود و به محض این که جای خالی در Queue ایجاد شود و یا هنگامی که مدت زمان موردنظر به اتمام برسد، تسک به حالت کاری Ready میرود. مکانیزم مشابهی نیز برای خواندن از Queue درنظر گرفته شده است. بدین ترتیب میتوان هنگام خواندن از Queue مدت زمانی را مشخص کرد تا درصورتیکه که داده ای در آن وجود نداشت، تسک به حالت کاری Block برود. هنگامیکه داده در آن نوشته شد و یا بعد از سپری شدن مدت زمان مشخص شده، تسک به حالت کاری Ready میرود.
در حالت کلی تعداد تسکهایی که از Queue میخوانند میتواند بیشتر از یک باشد. لذا در صورتیکه چندین تسک منتظر داده باشند تسکی که اولویت بالاتری دارد انتخاب شده و به حالت کاری Ready میرود و اگر اولویت تسکها یکسان باشد آن تسکی که مدت زمان بیشتری منتظر بوده است انتخاب میشود.
نحوه استفاده از Queue در سیستم عامل FreeRTOS
در این بخش به معرفی توابع و API های سیستم عامل FreeRTOS برای استفاده از Queue معرفی شده است.
تابع xQueueCreate
برای ایجاد Queue از تابع xQueueCreate استفاده میشود. الگوی این تابع بصورت زیر است.
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, UBaseType_t uxItemSize );
که آرگومانهای uxQueueLength و uxItemSize به ترتیب طول و اندازه Queue را مشخص میکنند. مقدار برگشتی تابع Handler آن است که برای اشاره و استفاده از Queue است. هنگام تعریف Queue سیستم عامل مقدار حافظه مورد نیاز را از حافظه Heap اختصاص میدهد. در صورتیکه فضای کافی وجود نداشته باشد، مقدار NULL برگردانده میشود.
تابع xQueueSendToBack و xQueueSendToFront
از این توابع همانطور که از نامهایشان برمیآید، برای نوشتن داده در ابتدا و انتهای Queue استفاده میشود. از تابع xQueueSendToFront هنگامی استفاده میشود که اولویت داده موردنظر بالا باشد و نیازمند پردازش سریع باشد.
الگوی تابع xQueueSendToBack بصورت زیر است:
BaseType_t xQueueSendToBack( QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait );
که در آن آرگومان xQueue، Queue مورد نظر را مشخص میکند. آرگومان pvItemToQueue اشاره گر داده است. اندازه داده هنگام تعریف Queue تنظیم میشود. آرگومان xTicksToWait نیز مدت زمان انتظار در صورتیکه که Queue پر باشد را مشخص میکند اگر این پارامتر صفر باشد، بلافاصله از این تابع خارج میشود. در صورتیکه نوشتن داده در این تابع موفقیت آمیز باشد pdPASS برگردانده میشود. در صورتیکه Queue پر باشد و امکان نوشتن در آن نباشد این تابع مقدار errQUEUE_FULL برمیگرداند.
تابع xQueueReceive
این تابع برای خواندن داده از Queue استفاده میشود. لازم به ذکر است پس خواندن داده از داخل Queue حذف میشود.
الگوی این تابع بصورت زیر است:
BaseType_t xQueueReceive( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait );
آرگومان pvBuffer اشارهگر داده خوانده شده از Queue است. همینطور xTicksToWait مدت زمان انتظار برای داده در صورتیکه Queue خالی باشد را مشخص میکند.
آ
تابع uxQueueMessagesWaiting
تابع uxQueueMessagesWaiting تعداد المانهای موجود در Queue را بر میگرداند. الگوی این تابع بصورت زیر است.
UBaseType_t uxQueueMessagesWaiting( QueueHandle_t xQueue );
آرگومان xQueue ، Queue مورد نظر را مشخص میکند است و مقدار برگشتی آن تعداد المانهای موجود در آن است.
استفاده از Queue بعنوان Mailbox
در بحث سیستم عامل تعاریف متعددی برای Mailbox ارائه شده است. در FreeRTOS منظور از Mailbox ، Queue با طول یک است که با خواندن از آن داده از Queue پاک نمیشود. Queue برای ارسال و دریافت داده بین دو تسک و یا بین تسک و وقفه استفاده میشود که فرستنده داده را در آن نوشته و گیرنده داده را از آن برمیدارد. با خواندن داده از Mailbox داده از آن داخل Queue پاک میشود. ولی Mailbox برای نگهداری داده که هر تسک یا وقفهای میتواند از آن بخواند، استفاده میشود و داده تا زمانیکه داده جدید در آن نوشته نشود داده در آن میماند و با خواندن از آن داده پاک نمیشود.
برای استفاده از Mailbox در سیستم عامل FreeRTOS توابع xQueueOverwrite و xQueuePeek فراهم شده است. تابع xQueueOverwrite داده را در Queue مینویسد و تابع xQueuePeek داده از Queue میخواند. بر خلاف تابع xQueueReceive هنگام خواندن داده، داده داخل Queue از بین نمیرود.
چگونگی استفاده از Queue در میکروکنترلرهای STM32
در این مثال آموزشی نحوه خواندن داده از ماژول BMP180 که یک سنسور فشار دیجیتال است با استفاده از FreeRTOS نشان داده شده است. برای این منظور دو تسک به نامهای Task_A و Task_B تعریف شده است. تسک Task_A داده را از سنسور خوانده و با استفاده از یک Queue داده سنسور را به تسک Task_B ارسال میکند. تسک Task_B نیز پس از دریافت داده آن را با گذرگاه UART به کامپیوتر ارسال میکند.
نحوه تعریف تسک در قسمتهای قبل بررسی شده است. لذا در این قسمت فقط بیان چگونگی تعریف Queue پرداخته شده است.
جهت تعریف Queue در نرم افزار STM32CubeMX در بخش FreeRTOS به سربرگ Tasks and Queues بروید. سپس در قسمت Queues بر روی دکمه Add کلیک کنید تا پنجره New Queue باز شود. در این پنجره نام و طول Queue و اندازه آن را مشخص نمایید و در نهایت بر روی دکمه OK کلیک کنید. در این پروژه از Queue به طول 32 که هر کدام از المانهای آن هشت بیت است، تعریف شده است. در شکل 3 مشخصات Queue و تسک های Task_A و Task_B نشان داده شده است.
گذرگاه ارتباطی ماژول BMP180 ، i2c است. در شکل زیر نحوه تنظیم واحد I2C1 نشان داده شده است.
نحوه تنظیم کلاک میکروکنترلر نیز در شکل زیر نشان داده شده است.
بعد از انجام تنظیمات فوق به سر برگ Project Manager رفته و نام و مسیر ذخیره پروژه و IDE موردنظر را انتخاب کنید و در نهایت بر روی گزینه GENERATE CODE کلیک نمایید تا پروژه ایجاد شود.
در کد زیر بدنه تسکهای Task_A و Task_B نشان داده شده است. در Task_A بعد از یافتن ID ماژول بصورت متناوب داده سنسور را خوانده و در Queue قرار میدهد. تسک دوم نیز دائما منتظر داده جدید است و به محض دریافت داده جدید آن را با گذرگاه UART به کامپیوتر ارسال میکند.
/* USER CODE END Header_Task_A_Func */
void Task_A_Func(void const * argument)
{
/* USER CODE BEGIN 5 */
/* Infinite loop */
uint8_t id = 0;
uint8_t rxdata[5] = {0};
uint8_t Txdata[5] = {0};
uint16_t ADC_dout;
volatile uint8_t i = 0;
for (i =0;i<255;i++)
{
HAL_StatusTypeDef eRes = HAL_I2C_IsDeviceReady(&hi2c1, i, 1,100);
if (eRes == HAL_OK)
{
id = i;
break;
}
}
for(;;)
{
Txdata[0] = 0xF6;
HAL_StatusTypeDef eRes = HAL_I2C_Master_Transmit(&hi2c1, id, Txdata, 1, 100);
eRes = HAL_I2C_Master_Receive(&hi2c1, id, rxdata, 2, 100);
ADC_dout = ((uint16_t)rxdata[0] << 8U) + ((uint16_t)rxdata[1]);
xQueueSendToBack( Queue_BMP180Handle, &ADC_dout, osWaitForever );
osDelay(100);
}
/* USER CODE END 5 */
}
/* USER CODE END Header_Task_B_Func */
void Task_B_Func(void const * argument)
{
/* USER CODE BEGIN Task_B_Func */
uint8_t strDataToPC[20] = {0};
uint16_t ADC_dout;
/* Infinite loop */
for(;;)
{
xQueueReceive(Queue_BMP180Handle, &ADC_dout, osWaitForever);
sprintf(strDataToPC, "BMP Data = %4d\n", ADC_dout);
HAL_UART_Transmit(&huart3, strDataToPC, strlen(strDataToPC), 100);
}
/* USER CODE END Task_B_Func */
}
سلام و خسته نباشید و ممنون از مطالب عالیتان
سلام
در انتهای توضیحات نمونه کدی دیده نمیشود
سلام
در انتهای برنامه ،نمونه برنامه ای نیست
با سلام و احترام،
کد مربوطه قرار داده شد.
سپاس از کامنت شما.
با سلام و احترام
کد انتهای مقاله قرار داده شد.