Queue و ارتباط بین تسکها در FreeRTOS با STM32

0

Queue و ارتباط بین تسکها در FreeRTOS با STM32

در این مطلب آموزشی چگونگی تبادل داده بین تسک­ها با استفاده از Queue بررسی شده است. در این بخش تنها حالت تسک-تسک بیان شده است و تبادل داده بین تسک و وقفه در بخش­­های پنجم و بعد از اشنایی با مکانیزم وقفه بیان شده است. در انتهای این بخش با استفاده از یک مثال عملی نحوه استفاده از Queue بیان شده است. در این مثال نحوه خواندن داده از سنسور BMP180 که یک سنسور فشار است شرح داده شده است.

سخت افزار مورد نیاز

  • سنسور BMP180
  • هر یک از میکروکنترلرهای 32 بیتی شرکت ST. در این پروژه از بورد STM32F4DISCOVERY استفاده شده است.

نرم افزار مورد نیاز

  • stm32cubemx
  • یک نرم افزار برای کامپایل و پروگرم کردن میکروکنترلر. در این پروژه از بورد IAR استفاده شده است.

معرفی Queue

Queue برای ذخیره داده هایی با طول ثابت استفاده می­شود. ایده کلی Queue در شکل 1 نشان داده شده است. در این شکل تسک Producer داده را با استفاده از Queue به تسک consumer ارسال می­کند. در حالت کلی تعداد تسک­هایی که می­توانند در Queue بنویسند یا از آن بخوانند می­تواند بیشتر از یک باشد.

نمایش مفهومی Queue
نمایش مفهومی Queue

Queue ها معمولا بصورت First In First Out استفاده می­شوند که داده جدید در انتهای Queue نوشته شده و داده قدیمی از ابتدای آن خوانده می­شود. در شکل 2 نحوه کار Queue نشان داده شده است.  به حداکثر تعداد داده­هایی که Queue می­تواند نگه دارد، طول 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 نشان داده شده است.

نمایش چگونگی تعریف Queue در نرم افزار STM32CubeMX
نمایش چگونگی تعریف Queue در نرم افزار STM32CubeMX

گذرگاه ارتباطی ماژول BMP180 ، i2c است. در شکل زیر نحوه تنظیم واحد I2C1 نشان داده شده است.

نحوه تنظیم واحد I2C برای ارتباط با ماژول BMP180
نحوه تنظیم واحد I2C برای ارتباط با ماژول BMP180

نحوه تنظیم کلاک میکروکنترلر نیز در شکل زیر نشان داده شده است.

نحوه تنظیم کلاک در نرم افزار STM32CubeMX
نحوه تنظیم کلاک در نرم افزار STM32CubeMX

بعد از انجام تنظیمات فوق به سر برگ 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 */
 
Choose your Reaction!
دیدگاه خود را بنویسید

آدرس ایمیل شما منتشر نخواهد شد.

redronic.com