سیستم عامل بلادرنگ FreeRTOS در میکروکنترلرهای STM32

0

سیستم عامل بلادرنگ FreeRTOS در میکروکنترلرهای STM32

در بخش قبل مبانی کلی سیستم­ عامل­های بلادرنگ به طور مفصل شرح داده شد. در این بخش و چهار بخش بعدی به معرفی و چگونگی استفاده از سیستم عامل FreeRTOS با چند مثال عملی پرداخته شده است.  FreeRTOS یک سیستم عامل رایگان و متن باز است که برای میکروکنترلرها و پردازنده­های کوچک طراحی شده است. این سیستم به گونه ای بهینه شده است تا حد امکان ساده و کوچک باشد. همچنین استفاده از آن نیز بسیار ساده است. FreeRTOS به زبان C گسترش داده شده است و به گونه توسعه داده شده است تا بسیار Portable باشد. در FreeRTOS دارای اکثر ویژگی­های سیستم عامل بلادرنگ از جمله سمافور و Queue و … است.   این سیستم عامل از بیش از 27 پلتفرم مختلف ازجمله AVR، ARM و … پشتیبانی می­کند.

سیستم عامل FreeRTOS دارای یک شبیه ساز بسیار دقیق است است که با استفاده از آن می­توان رفتار سیستم عامل در آن شبیه­سازی نمود و بینش بیشتری نسبت به آن بدست آورد.

در این پروژه از نرم افزار STM32CubeMX برای مدیریت سیستم عامل نظیر ایجاد تسک، سمافور و … بصورت گرافیکی استفاده شده است. لازم به ذکر است که می­توان بدون استفاده از نرم افزار STM32CumeMX نیز سیستم عامل FreeRTOS را به نرم افزار اضافه نمود. برای این منظور باید فایل­های موردنیاز را بصورت دستی اضافه و تنظیمات آن را در برنامه تنظیم کرد.

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

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

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

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

نحوه فعال نمودن FreeRTOS در نرم افزار STM32CubeMX

برای اضافه نمودن FreeRTOS به نرم افزار ابتدا در نرم افزار STM32CubeMX از قسمت Middleware بر روی گزینه FreeRTOS کلیک کنید (شکل 1) تا پنجره FREERTOS Mode and Configuration که در شکل 2 نشان داده شده است، باز شود. سپس از قسمت Interface گزینه CMSIS_V1 را انتخاب کنید. در اینجا از ورژن یک CMSIS استفاده شده است. ولی می­توان از ورژن دوم آن نیز استفاده نمود.

نحوه انتخاب فعال نمودن FreeRTOS در نرم افزار STM32CubeMX بخش اول
نحوه انتخاب فعال نمودن FreeRTOS در نرم افزار STM32CubeMX بخش اول
نحوه انتخاب فعال نمودن FreeRTOS در نرم افزار STM32CubeMX بخش دوم
نحوه انتخاب فعال نمودن FreeRTOS در نرم افزار STM32CubeMX بخش دوم

با فعال نمودن FreeRTOS صفحه Configuration باز می­شود. در این قسمت می­توان کلیه تنظیمات لازم را بصورت گرافیکی انجام داد. در سربرگ Config parameters تنظیمات و کانفیگ­های FreeRTOS تنظیم می­شوند.

برای ایجاد تسک به سربرگ Tasks and Queues که در شکل 3 نشان داده شده است، بروید.

سربرگ Task and Queues در نرم افزار STM32CubeMX
سربرگ Task and Queues در نرم افزار STM32CubeMX

برای ایجاد تسک بر روی دکمه Add کلیک کنید تا پنجره New Task باز شود. فیلدهای این پنجره عبارتند از:

Task Name: نام تسک را مشخص می­کند. نام تسک فقط برای دیباگ کاربر قرار داده شده است.

Priority: اولویت تسک است. تعداد سطوح اولویت در سربرگ Config parameters  فیلد MAX_PRIORITY تنظیم می­شود.

Stack Size: اندازه حافظه Stack تسک را مشخص می­کند.

Entry Function: نام تابع مرتبط با تسک است. هنگام شروع تسک این تابع فراخوانی می­شود.

Code Generation Option: چگونگی ایجاد Entry Function را مشخص می­کند.

Parameter: آرگومان پاس داده شده به تسک است.

Allocation: چگونگی اختصاص حافظه مورد نیاز تسک را مشخص می­کند که می­تواند بصورت Static و Dynamic باشد. در صورتیکی Static باشد از حافظه Stack و اگر Dynamic انتخاب شود از حافظه Heap استفاده می­شود.

در این پروژه دو تسک با اولویت یکسان درست شده است. در شکل 4 نحوه تنظیم تسک­ها نشان داده شده است.

تسکهای تعریف مثال اول در نرم افزار STM32CubeMX
تسکهای تعریف مثال اول در نرم افزار STM32CubeMX

با توجه به اینکه همواره باید یک تسک برای اجرا وجود داشته باشد، نرم افزار STM32cumeMX بصورت پیش­فرض یک تسک به نام  defaultTask ایجاد می­کند. برای تغییر مشخصه­های آن بر روی دو بار کلیک کنید تا پنجره Edit task باز شود.

FreeRTOS نیازمند واحد یک تایمر سخت افزاری برای Scheduling و … استفاده می­کند. در نرم افزار STM32CubeMX تایمر systick به سیستم عامل اختصاص می­دهد. لذا برای انجام تنظیمات سیستم و کتابخانه HAL از یک تایمر دیگر باید استفاده نمود. در این پروژه از تایمر یک , پورت USART3 برای ارسال داده به کامپیوتر استفاده شده است.

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

پس از انجام تنظیمات فوق به سربرگ Project Manager رفته و پس از وارد کردن نام، مسیر ذخیره آن و انتخاب نرم افزار دیباگر بر روی گزینه Generate Code کلیک کرده تا کد نرم افزار اولیه ایجاد شود. در این پروژه از نرم افزار IAR Embedded Workbench برای کامپایل و پروگرم استفاده شده است.

پس از باز نمودن پروژه ایجاد شده می­توانید ملاحظه کنید که نرم افزار STM32CubeMX برای ایجاد تسک برنامه زیر را به نرم افزار اضافه کرده است:

/* Create the thread(s) */
/* definition and creation of Task1 */
osThreadDef(Task1, funcTask1, osPriorityNormal, 0, 128);
Task1Handle = osThreadCreate(osThread(Task1), NULL);

/* definition and creation of Task2 */
osThreadDef(Task2, funcTask2, osPriorityIdle, 0, 128);
Task2Handle = osThreadCreate(osThread(Task2), NULL);

/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
/* USER CODE END RTOS_THREADS */

/* Start scheduler */
osKernelStart();
 

همچنین برای توابع funcTask1 و funcTask2 را به برنامه اضافه کرده است.

/* USER CODE END Header_funcTask1 */
void funcTask1(void const * argument)
{
  /* USER CODE BEGIN 5 */
  /* Infinite loop */
  for(;;)
  {
    osDelay(1);
  }
  /* USER CODE END 5 */ 
}

/* USER CODE END Header_funcTask2 */
void funcTask2(void const * argument)
{
  /* USER CODE BEGIN funcTask2 */
  /* Infinite loop */
  for(;;)
  {
    osDelay(1);
  }
  /* USER CODE END funcTask2 */
}
 

لازم به ذکر است که علاوه بر تسک­هایی که کاربر مشخص کرده است، تسک­­های دیگری از جمله تسک IDLE را سیستم عامل ایجاد می کند تا هنگامی که هیچ تسکی برای اجرا وجود نداشت، اجرا شود. در بخش­های بعدی چگونگی و اهمیت استفاده از تسک idle بیان خواهد شد.

بررسی برنامه ایجاد شده

همانطور که ملاحظه می­کنید نرم افزار STM32CubeMX  دو تسک با نام­های Task1 و Task2 ایجاد نموده است. ماکروی osThreadDef بصورت زیر تعریف شده است.

#define osThreadDef(name, thread, priority, instances, stacksz)  \
const osThreadDef_t os_thread_def_##name = \
{ #name, (thread), (priority), (instances), (stacksz), NULL, NULL }
 

این ماکرو یک structure از جنس osThreadDef با نام os_thread_def_##name ایجاد می¬کند. عملوند ## یک عملوند Preprocessor است که دو رشته کاراکتری را در به هم می¬چسباند.
تابع osThreadCreate با استفاده از تابع xTaskCreate تسک جدید ایجاد می کند. الگوی تابع xTaskCreate بصورت زیر است:

BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
                        const char * const pcName,      /*lint !e971 Unqualified char types are allowed for strings and single characters only. */
                        const configSTACK_DEPTH_TYPE usStackDepth,
                        void * const pvParameters,
                        UBaseType_t uxPriority,
                        TaskHandle_t * const pxCreatedTask )
 

لذا برای ایجاد تسک می­توان مستقیما تابع xTaskCreate را فراخوانی نمود.

مثال اول: اجرای دو تسک

هدف از این مثال مشاهده اجرای همزمان و مستقل دو تسک است. لذا دو تسک مستقل ایجاد نموده و هر تسک بصورت متناوب داده ای را از پورت سریال ارسال می­کنند. توابع funcTask1 و funcTask2 بصورت زیر نوشته شده است.

void funcTask1(void const * argument)
{
  /* USER CODE BEGIN 5 */
  uint8_t messageFromTask1[] = "Task 1 is runing\n";
  /* Infinite loop */
  for(;;)
  {
    HAL_UART_Transmit(&huart3, messageFromTask1, sizeof messageFromTask1, 100);
    osDelay(100);
  }
  /* USER CODE END 5 */ 
}

void funcTask2(void const * argument)
{
  /* USER CODE BEGIN funcTask2 */
  uint8_t messageFromTask2[] = "Task 2 is runing\n";
  /* Infinite loop */
  for(;;)
  {
    HAL_UART_Transmit(&huart3, messageFromTask2, sizeof messageFromTask2, 100);
    osDelay(100);
  }
  /* USER CODE END funcTask2 */
 
خروجی مثال اول FreeRTOS
خروجی مثال اول FreeRTOS

مثال دوم: حذف تسک

در مثال اول هر دو تسک در تابع main ایجاد شدند. در این مثال نحوه ایجاد یک تسک توسط تسک دیگر و چگونگی حذف تسک آن بررسی شده است. لازم به ذکر است که هیچ تسکی نباید به پایان برسد. در صورتیکه دیگر نیازی به تسک موردنظر نباشد باید صریحا با استفاده از تابع vTaskDelete حذف شود.

در این مثال ابتدا فقط Task1 وجود دارد. بعد از مدتی Task1، Task2 را ایجاد کرده و بعد از مدتی Task1 را متوقف کرده است.

کد این برنامه بصورت زیر است:

/* USER CODE END Header_funcTask1 */
void funcTask1(void const * argument)
{
  /* USER CODE BEGIN 5 */
  uint8_t messageFromTask1[] = "Task 1 is runing\n";
  int i = 0;
  /* Infinite loop */
  for(;;)
  {
    HAL_UART_Transmit(&huart3, messageFromTask1, sizeof messageFromTask1, 100);
    i++;
    if (i == 5)
    {
      osThreadDef(Task2, funcTask2, osPriorityLow, 0, 128);
      Task2Handle = osThreadCreate(osThread(Task2), NULL);
    }
    else if (i==10)
    {
      vTaskDelete(Task2Handle);
    }
    osDelay(100);
  }

  /* USER CODE END 5 */ 
}
/* USER CODE BEGIN Header_funcTask2 */
/**
* @brief Function implementing the Task2 thread.
* @param argument: Not used
* @retval None
*/
/* USER CODE END Header_funcTask2 */
void funcTask2(void const * argument)
{
  /* USER CODE BEGIN funcTask2 */
  uint8_t messageFromTask2[] = "Task 2 is runing\n";
  /* Infinite loop */
  for(;;)
  {
    HAL_UART_Transmit(&huart3, messageFromTask2, sizeof messageFromTask2, 100);
    osDelay(100);
  }
  /* USER CODE END funcTask2 */
 
خروجی مثال دوم FreeRTOS
خروجی مثال دوم FreeRTOS

مثال سوم: پاس دادن آرگومان به تسک

برای این منظور تنها کافیست تا آدرس یا اشاره گر داده موردنظر را هنگام ایجاد تسک به آن بدهیم. با توجه به اینکه تنها می­توان یه پارامتر به تسک داد، برای ارسال پارمترهای بیشتر می­توان یک struct تعریف نمود و کلیه پارمترهای مورد نیاز را در آن قرار داد.

void funcTask1(void const * argument)
{
  /* USER CODE BEGIN 5 */
  uint8_t messageFromTask1[] = "Task 1 is runing\n";
  uint8_t argumentToTask2[] = "Task 2 is runing\n";
  customDataStructur DataToTask2;
  DataToTask2.data = argumentToTask2;
  DataToTask2.len = sizeof argumentToTask2;
  int i = 0;
  /* Infinite loop */
  for(;;)
  {
    HAL_UART_Transmit(&huart3, messageFromTask1, sizeof messageFromTask1, 100);
    i++;
    if (i == 5)
    {
      osThreadDef(Task2, funcTask2, osPriorityLow, 0, 128);
      Task2Handle = osThreadCreate(osThread(Task2), (void*)&DataToTask2);
    }
    else if (i==10)
    {
      vTaskDelete(Task2Handle);
    }
    osDelay(100);
  }

  /* USER CODE END 5 */ 
}

void funcTask2(void const * argument)
{
  /* USER CODE BEGIN funcTask2 */
  customDataStructur* arg =(customDataStructur*)argument; 
  uint8_t* messageFromTask2 = arg->data;
  uint8_t len = arg->len;
  /* Infinite loop */
  for(;;)
  {
    HAL_UART_Transmit(&huart3, messageFromTask2, len, 100);
    osDelay(100);
  }

  /* USER CODE END funcTask2 */
}
 
خروجی مثال دوم FreeRTOS
خروجی مثال سوم FreeRTOS
Choose your Reaction!
دیدگاه خود را بنویسید

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

redronic.com