کنترل وقفه در سیستم عامل FreeRTOS در میکروکنترلرهای STM32

0

کنترل وقفه در سیستم عامل FreeRTOS در میکروکنترلرهای STM32

وقفه در سیستم های Embedded نقش بسیار مهمی دارند. سیستم­های بلادرنگ باید سریعا پاسخ وقفه­های گوناگونی که از محیط خارج و داخل میکروکنترلر نشأت می­گیرند را فراهم کند. برای مثال سیستم آسانسور را در نظر بگیرید. سیستم باز شدن کیسه هوای خودرو را در نظر بگیرید که حتی اندکی تاخیر می­تواند منجر به تلفات جبران ناپذیری شود. در این بخش نحوه مدیریت و کنترل وقفه در با استفاده از سیستم عامل FreeRTOS بررسی شده است.

سیستم عامل FreeRTOS هیچگونه محدودیتی جهت کنترل و مدیریت وقفه­ها­ به طراح سیستم تحمیل نمی­کند. در عوض امکاناتی را فراهم می­کند که طراح با استفاده از آن براحتی طرح خود را پیاده سازی کند.

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

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

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

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

 

تفاوت اولویت تسک با وقفه

قبل از هر چیز ضروریست تا تفاوت اولویت تسک با اولویت وقفه بیان شود. تسک کلا ماهیت نرم افزاری دارد و ارتباطی به سخت افزار ندارد و اولویت آن بصورت نرم افزاری تنظیم می­شود. ولی وقفه ماهیت سخت افزاری دارد. تسک­ها فقط زمانی اجرا می­شوند که هیچ روتین وقفه­ای در حال اجرا نباشد. بعبارت دیگر وقفه حتی با پایین ترین اولویت بر تسک با بالاترین اولویت مقدم است و هیچ تسکی نمی­تواند یک وقفه را Preempt کند.

استفاده از توابع سیستم عامل FreeRTOS در روتین وقفه

برخی از توابعی که FreeRTOS فراهم کرده است در روتین وقفه معتبر نیستند. از جمله این توابع می­توان به توابعی که تسک را به حالت کاری می­برند، اشاره نمود. چرا که بلافاصله پس از روی دادن وقفه، کلیه تسک­ها متوقف شده و روتین وقفه اجرا می­شود. FreeRTOS برای جلوگیری از خطاهای ممکن، یک API دیگر که فقط در داخل روتین وقفه قابل استفاده هستند فراهم نموده است. نام همه این توابع “FromISR” خاتمه می­یابد. بعنوان مثال برای نوشتن داده در Queue در روتین وقفه از تابع xQueueSendFromISR باید استفاده شود.

پارامتر xHigherPriorityTaskWoken

با مقایسه توابع مختص ISR با توابع متناظر آن مشاهده می­شود که توابع مخصوص ISR دارای پارامتر خاصی به نام xHigherPriorityTaskWoken هستند.  در این قسمت به بررسی این پارامتر پرداخته شده است.

برخی مواقع ممکن است که پس از رخداد یک وقفه نیاز به Context Switching باشد. در این حالت تسکی که بعد از اتمام روتین وقفه اجرا می­شود با تسکی که قبل از رخداد یکسان نیست.

با فراخوانی برخی از توابع FreeRTOS ممکن است یک تسک از حالت Block به حالت کاری Ready برود. بعنوان مثال با فراخوانی تابع xQueueSendToBack  تسک دیگری که منتظر دریافت داده بوده است به حالت کاری Ready می­رود. در صورتی که تابع موردنظر در داخل یک تسک فراخوانی شود و مقدار configUSE_PREEMPTION یک تنظیم شده باشد، بلافاصله تسک Context Switching اجرا می­شود و تسک با اولویت از حالت کاری Ready به Active می­رود. ولی اگر تابع موردنظر در روتین وقفه فراخوانی شود، Context Switching بصورت خودکار انجام نمی­شود. در این حالت می­توان یک متغیر را استفاده کرد تا در روتین وقفه تنظیم شود و بعدا در برنامه این متغیر بررسی شود و در صورت نیاز Context Switching انجام شود. پارامتر xHigherPriorityTaskWoken برای این منظور در نظر گرفته شده است. لذا در صورتیکه نیاز به Context Switching باشد باید مقدار xHigherPriorityTaskWoken را pdTRUE تنظیم کرد.

ماکرو portYIELD_FROM_ISR

ماکرو portYIELD_FROM_ISR

از این ماکرو برای درخواست Context Switching در روتین وقفه استفاده می­شود. الگوی این ماکرو بصورت زیر است:

portYIELD_FROM_ISR( xHigherPriorityTaskWoken );

در صورتی که پارامتر xHigherPriorityTaskWoken را pdTRUE تنظیم شود، درخواست Context Switching داده می­شود و ممکن است تسک در حال اجرا عوض شود. ولی اگر مقدار آن pdFALSE باشد، درخواستی داده نمی­شود.

Deferred Interrupt Processing

در سیستم­های Embedded همواره تلاش می­شود تا روتین وقفه تا حد ممکن کوتاه باشد. از جمله دلایل این امر می­توان به موارد زیر اشاره نمود:

  • کلیه تسک­ها تنها زمانی اجرا می­شوند که هیچ روتین وقفه­ای در حال اجرا نباشد. لذا طولانی بودن روتین وقفه ممکن است موجب اختلال در عملکرد سیستم شود. روتین وقفه با تحت تاثیر قرار دادن زمان شروع تسک و مدت زمان اجرای آن موجب اضافه نمودن Jitter و اختلال در سیستم می­شود.
  • طولانی بودن روتین وقفه موجب از عدم دریافت وقفه و پاسخگویی مناسب به آن می­شود.
  • مکانیزم وقفه در اکثر میکروکنترلرها و پردازنده­های امروزی بصورت nested است. بطوریکه وقفه با اولویت بالاتر می­تواند روتین وقفه با اولویت پایین تر را متوقف کند که موجب پیچیده تر شدن سیستم می­شود.

لذا باید در روتین وقفه تنها علت وقفه ذخیره شود و Flag وقفه پاک شود و پردازش موردنیاز در داخل یک تسک دیگر انجام شود. به این فرآیند Deferred interrupt processing گفته می­شود. Deferred interrupt processing این امکان را فراهم می­کند تا طراح بین تسک مربوط به وقفه و سایر تسک­های سیستم اولویت بندی کند. در صورتیکه اولویت تسک مربوط به وقفه بسیار بالاتر از سایر تسک باشد می­توان با استفاده از ماکروی portYIELD_FROM_ISR در خواست Context Switching داد و بلافاصله خارج از روتین وقفه پردازش موردنظر را انجام داد.

استفاده از Queue در داخل روتین وقفه

استفاده از Queue در داخل روتین وقفه

در بخش قبل نحوه استفاده از Binary Semaphore برای همزمانسازی و اطلاع رسانی بین وقفه و تسک در حال اجرا بررسی شد. برای انتقال داده بین روتین وقفه و تسک از Queue استفاده می­شود. در بخش­های قبل نحوه استفاده از Queue بین تسک­ها بررسی شد. در این قسمت نحوه تبادل داده از روتین وقفه به تسک و برعکس بررسی شده است.

برای ارسال داده در روتین وقفه باید از توابع xQueueSendToFrontFromISR و xQueueSendToBackFromISR بجای xQueueSendToFront و xQueueSendToBack استفاده کرد. همچنین برای دریافت داده از تابع xQueueReceiveFromISR بجای تابع xQueueReceive  باید استفاده کرد

مثال نمونه

در این مثال نحوه استفاده از Queue در داخل روتین وقفه نشان و نحوه به عقب انداختن پردازش مودنیاز وقفه (Deferred Interrupt Processing) نشان داده شده است. در این مثال میکروکنترلر داده را با گذرگاه Uart از کامپیوتر دریافت می کند و با توجه به مقدار آن یک LED را روشن و یا خاموش می­کند. در صورتیکه داده سریال “A” باشد LED روشن شده و در صورتیکه “B” باشد LED خاموش می­شود.

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

در قسمت­های قبل نحوه اضافه نمودن تسک و Queue بیان شده است. لذا در این بخش از بیان مجدد آن خودداری شده است.

این روش نسبت به روش RTC انعطاف پذیری بالاتری دارد ولی در عوض پیچیدگی­های بیشتری به سیستم تحمیل می­کند. چرا که هنگامی که تسک قبل از به اتمام رسیدن متوقف می­شود نیازمند Context Switching است.

نحوه تنظیم GPIO

برای روشن و خاموش کردن LED ابتدا باید پایه موردنظر بصورت خروجی تعیین شود.  این LED در بورد استفاده شده به پایه PD.13 متصل شده است. برای تنظیم پایه بعنوان خروجی در نرم افزار STM32CubeMX در پنجره Pinout View بر روی پایه مورد نظر کلیک کرده و گزینه GPIO_Output را انتخاب کنید 

نحوه تنظیم پایه بعنوان خروجی در نرم افزار STM32CubeMX
نحوه تنظیم پایه بعنوان خروجی در نرم افزار STM32CubeMX

نحوه تنظیم پورت UART

در این پروژه از پورت UART1 برای ارتباط با کامپیوتر استفاده شده است.  نحوه تنظیم پورت UART در شکل 2 نشان داده شده است.

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

همچنین برای فعال نمودن وقفه UART در سربرگ NVIC Settings تیک Enabled را فعال نمایید

نحوه فعال نمودن وقفه دریافت UART در نرم افزار STM32CubeMX
نحوه فعال نمودن وقفه دریافت UART در نرم افزار STM32CubeMX

نرم افزار

در کد زیر تابع main نشان داده شده است. در تابع main پس از انجام تنظیمات GPIO و UART یک تسک به نام myTask و یک Queue بنام UartQueue تعریف شده است. پس از آن Scheduler فعال شده است.

int main(void)
{

  HAL_Init();

  SystemClock_Config();

  MX_GPIO_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  HAL_UART_Receive_IT(&huart1, UARTRXData, 1);
  /* USER CODE END 2 */

  /* definition and creation of UartQueue */
  osMessageQDef(UartQueue, 16, uint8_t);
  UartQueueHandle = osMessageCreate(osMessageQ(UartQueue), NULL);

  osThreadDef(myTask, myTaskFunction, osPriorityNormal, 0, 128);
  myTaskHandle = osThreadCreate(osThread(myTask), NULL);

  osKernelStart();
  
  while (1)
  {
      
  }
}
 

روتین وقفه UART

در روتین وقفه دریافت UART داده در Queue نوشته شده است. همانطور که ملاحظه می­کنید پارامتر pxHigherPriorityTaskWoken نیز برابر pdTRUE تنظیم شده است تا پس از اتمام روتین وقفه Scheduler سریعا تسک myTask را اجرا نماید. کد  روتین وقفه بصورت زیر است:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  BaseType_t eISCWRequired = pdTRUE;
  xQueueSendFromISR( UartQueueHandle, &UARTRXData[0], &eISCWRequired );
  HAL_UART_Receive_IT(&huart1, UARTRXData, 1);
}
 

برنامه تسک مرتبط با وقفه UART

void myTaskFunction(void const * argument)
{
  /* USER CODE BEGIN 5 */
  uint8_t QueueData[10];
  /* Infinite loop */
  for(;;)
  {
    xQueueReceive( UartQueueHandle, QueueData, osWaitForever);
    if (QueueData[0] == 'A')
    {
       HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, GPIO_PIN_SET);
    }
    else if (QueueData[0] == 'B')
    {
       HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, GPIO_PIN_RESET);
    }
    else 
    {
      
    }
  }
  /* USER CODE
 

سیستم عامل­های بلادرنگ معمولا دارای API های متعددی برای پشتیبانی از درایورهای ادوات مختلف هستند.

Choose your Reaction!
دیدگاه خود را بنویسید

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

redronic.com