2

FreeRTOS Notları #5: Semaphore

    Semaphore konusuna başlamadan önce FreeRTOS‘da kesme(interrupt) yönetimine değinmek istiyorum.
    Gömülü gerçek zamanlı sistemler ortamdan kaynaklanan olaylara(events) göre belli aksiyonlar(actions) almak zorundadır. Önemsiz olmayan sistemlerin farklı işlem ve cevap süreleri gerektiren birden fazla kaynaktan gelen olaylara hizmet etmesi gerekecektir. Her durumda en iyi olay işleme(event processing) stratejisi hakkında karar verilmelidir.
  1.  Olay(event) nasıl algılanmalı? Kesmeler normal olarak kullanılır ancak input’lar polling ile algılanır.
  2. Kesmeler kullanıldığında ISR(Interrupt Service Routine) içerisinde ne kadar işlem yapılmalı? Genelde her ISR in olabildiğince kısa tutulması tavsiye edilir.
  3. Olaylar ana kod(ISR içinde olmayan) ile nasıl iletişime geçirilmeli?
    FreeRTOS , geliştiriciye olayların işlenme stratejisi ile ilgili herhangi bir yük yüklemez ancak seçilen stratejinin basit ve sürdürülebilir şekilde kullanılmasına izin veren özellikler sağlar.
    Bir taskın önceliği ile bir kesmenin önceliği arasında seçim yapmak önemlidir.
  • Task, FreeRTOS un üzerinde çalıştığı donanım ile ilgisi olmayan bir yazılım özelliğidir. Bir taskın önceliği yazılımcı tarafından atanır ve buna göre scheduler hangi taskın running state de bulunacağına karar verir.
  • ISR bir donanım özelliğidir çünkü hangi ISR’in çalışacağını ve ne zaman çalışacağını donanım kontrol eder. Tasklar sadece ISR’in yürütülmediği zamanlarda yürütülür. Bu nedenle en düşük öncelikli ISR en yüksek öncelikli taskı kesintiye uğratabilir.

FreeRTOS API Fonksiyonlarının Bir ISR İçerisinden Kullanılması

Interrupt Safe API

    Bazen FreeRTOS API fonksiyonlarını ISR içerisinde kullanmak gerekebilir fakat çoğu FreeRTOS API fonksiyonları ISR içerisinde geçerli olmayan işlemler gerçekleştirir. Örneğin; bir taskı bloklamak. FreeRTOS bu problemi aynı API fonksiyonunun farklı bir versiyonunu sağlayarak çözer. Aynı işlemi yapan bir versiyon task tarafından, diğer versiyonu ISR tarafından kullanılabilir. ISR içerisinden kullanılacak fonksiyonun sonunda “fromISR” bulunur. Bu duruma “interrupt-safe” adı verilir. Kesme içerisinde kullanılmak üzere bir API’ye sahip olmak , ISR fonksiyonunun daha verimli ve kesmeye girişin daha kolay olmasını sağlar.

Semaphore

    FreeRTOS’da semaphore‘lar karşılıklı dışlama(mutual exclusion) ve senkronizasyon amacı ile kullanılır. Semaphore bir taskı bloklayabilir. Bu bloğu sadece farklı bir task veya ISR kaldırabilir. FreeRTOS da iki tip semaphore vardır. Bunlar;
  • Binary Semaphore
  • Counting Semaphore

Binary Semaphore

    Binary semaphore ve mutex birbirine çok benzer ama bazı ince farklılıkları vardır. Mutex’ler bir öncelik kalıtım mekanizması içerirken, binary semaphore içermez.  Bu binary semaphore’u tasklar veya task-interrupt arasındaki senkronizasyon için iyi bir seçim yapar.
    Semaphore API fonksiyonları bir blok süresi belirtmeye izin verir. Blok süresi xSemaphoreTake() fonksiyonu ile semaphore alınamıyorsa maksimum tick cinsinden bloklanmış durumda ne kadar bekleneceğidir. Eğer birden fazla task aynı semaphore ile bloklanmış ise semaphore xSemaphoreGive() fonksiyonu ile bırakıldığında ilk çalışacak task önceliği en yüksek olandır.
    Binary semaphore’u ,tek eleman tutan bir kuyruk(queue) gibi düşünebiliriz. Bloklanmış durumdaki tasklar bu kuyrukta ne olduğuyla değil ,dolu mu yoksa boş mu olduğuyla ilgilenecektir. Böyle bir mekanizma ile task bir interrupt ile senkronize bir şekilde çalışabilir.
    Bir çevrebirimini kullanan bir task düşünün. Polling metodu ile çevrebiriminin durumunu kontrol etmek CPU nun boşa çalışması ve diğer taskların daha az çalışması anlamına gelir. Bu nedenle taskların zamanın çoğunu bloklanmış durumda geçirmesi ve sadece ihtiyaç duyulduğunda yürütülmesi tercih edilir. Bu durum semaphore’un task tarafından alınması(take) ile sağlanabilir ve istenilen bir ISR fonksiyonu veya başka bir task fonksiyonu içerisinden semaphore’un serbest bırakılarak(give) bloklanmış durumdaki taskın kaldığı yerden çalışması sağlanabilir.

xSemaphoreCreateBinary() Fonksiyonu

    FreeRTOS’da binary semaphore oluşturmak için xSemaphoreCreateBinary() API fonksiyonu kullanılır.

SemaphoreHandle_t xSemaphoreCreateBinary( void );
    Görüldüğü gibi bu fonksiyon bir parametre almaz. Bu fonksiyon eğer semaphore oluşturmada bir hata oluştu ise(bellekten kaynaklı) NULL değeri döndürür. Başarılı bir şekilde semaphore oluşturuldu ise SemaphoreHandle_t tipinde bir handle döndürür.

xSemaphoreTake() Fonksiyonu

    Semaphore’u almak için ise xSemaphoreTake() fonksiyonu kullanılır. Binary semaphore sadece bir kez alınabilir. Recursive mutex hariç bütün FreeRTOS semaphore tipleri bu fonksiyon ile semaphore’u alır. Bu fonksiyon interrupt fonksiyonu içerisinde kullanılmamalıdır(çünkü bir kesme bloklanamaz).

BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );
  • xSemaphore: Alınacak olan semaphore’un girildiği parametredir. Birden fazla semaphore olabilir. Bu parametre xSemaphoreCreateBinary() fonksiyonunun döndürdüğü değeri alır.
  • xTicksToWait:  Eğer semaphore verilmemiş ise ne kadar süre ile blocked state de kalacağının belirlendiği parametredir. portMAX_DELAY ile süresiz olarak bloklanabilir.( FreeRTOSConfig.h dosyasında INCLUDE_vTaskSuspended makrosu 1 olarak tanımlı ise)
  • Return Değeri: Semaphore alındı ise pdTRUE, semaphore elde edilemediyse ve blok süresi dolduysa pdFALSE değerini döndürür.

xSemaphoreGive() ve xSemaphoreGiveFromISR() Fonksiyonu

    Semaphore vermek veya bırakmak için xSemaphoreGive() fonksiyonu kullanılır. Bu fonksiyonun aldığı tek parametre verilecek olan semaphore’un handle’ıdır.

xSemaphoreGive( SemaphoreHandle_t xSemaphore );

    Eğer interrupt fonksiyonu içerisinden bir semaphore bırakılacak ise xSemaphoreGiveFromISR() API fonksiyonu kullanılmalıdır. Bu fonksiyon xSemaphoreGive() fonksiyonunun interrupt-safe versiyonudur.

BaseType_t xSemaphoreGiveFromISR( SemaphoreHandle_t xSemaphore,
                                  BaseType_t *pxHigherPriorityTaskWoken );
  • xSemaphore: Bırakılacak olan semaphore handle’ı
  • *pxHigherPriorityTaskWoken: Bu parametreyi anlamak biraz zor. Eğer xSemaphoreGiveFromISR() fonksiyonu bu parametreyi pdTRUE olarak ayarlarsa semaphore’u alan taskın önceliği, o anda çalışan taskın(kesme ile kesintiye uğrayan task) önceliğinden yüksek demektir. Eğer böyle bir durum var ise ISR fonksiyonundan çıkmadan FreeRTOS’u  “context switch” denen bir işleme zorlamak gerekir. Context switching scheduler’ın tasklar arasında yaptığı geçiştir. Bu geçişi ise ISR fonksiyonunun sonunda portYIELD_FROM_ISR( xHigherPriorityTaskWoken ) şeklinde bir kod kullanarak yapabiliriz.
  • Return Değeri: Eğer semaphore verildi(give) ise pdPASS, semaphore zaten mevcut ise pdFAIL değeri döndürür.

Counting Semaphore

    Counting Semaphore’da bir semaphore her bırakıldığında yani xSemaphoreGive() fonksiyonu her çağrıldığında semaphore’un sayaç değeri artar. Herhangi bir task semaphore’u xSemaphoreTake() fonksiyonu ile almak istediğinde sayaç değeri azalır. Eğer sayaç değeri sıfıra ulaşmış ise task bloklanabilir. Counting semaphore’u kullanabilmek için FreeRTOSConfig.h dosyasındaki configUSE_COUNTING_SEMAPHORES  makrosu 1 olarak ayarlanmalıdır.
    Binary semaphore’u tek elamanlı bir kuyruk gibi düşünmüştük. Counting semaphore ise birden fazla eleman tutan bir kuyruk olarak düşünülebilir. Bu durumda task’lar kuyruğun içindeki veriler ile değil kuyruğun boş olup olmaması ile ilgilenecektir. Her xSemaphoreGive() fonksiyonu çağrıldığında kuyruğa bir değer eklenmiş gibi düşünebiliriz. xSemaphoreTake() fonksiyonu ise bu kuyruktaki verileri azaltacak ve en sonunda kuyrukta eleman kalmayınca taskı bloklayacakmış gibi düşünebiliriz.

xSemaphoreCreateCounting() Fonksiyonu

    Counting semaphore oluşturmak için xSemaphoreCreateCounting fonksiyonu kullanılır.
SemaphoreHandle_t xSemaphoreCreateCounting( UBaseType_t uxMaxCount,
                                            UBaseType_t uxInitialCount);
  • uxMaxCount: Maksimum count değerini ifade eder. Semaphore bu değere ulaştığında xSemaphoreGive() fonksiyonu ile daha fazla semafor verilemez.
  • uxInitialCount: Başlangıçtaki count değerinin giridiği parametredir.
  • Return Değeri: Semaphore oluşturulamadı ise NULL,oluşturuldu ise bir handle döndürür.

FreeRTOS’daki semaphore ile ilgili diğer fonksiyonlar hakkında daha fazla bilgi için buraya tıklayabilirsiniz.

FreeRTOS Semaphore Kullanımına Ait Örnek

    Bu örnekte bir butona bastığımızda bir taskın çalışmasını sağlayacak bir uygulama yapalım. Burada butonu dış kesme(external interrupt) olarak kullanalım ve semaphore’un ISR içerisinden kullanımını görelim.
    Semaphore kullanabilmek için “semaphr.h” isimli kütüphaneyi dahil ediyoruz.
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
    Bu örnekte sadece binary semahore kullanılmıştır. Binary semaphore için bir handle aşağıdaki gibi oluşturulmuştur.
SemaphoreHandle_t BinarySem;
    xSemaphoreCreateBinary() fonksiyonu ile binary semaphore oluşturulur ve fonksiyonun döndürdüğü değer tanımlanan semaphore handle’na atanır.
BinarySem = xSemaphoreCreateBinary();
    İki adet task aşağıdaki gibi oluşturulmuştur. Burada kesme tarafından tetiklenecek taskın(HandlerTask) önceliği diğerine göre yüksek seçilmiştir.
xTaskCreate(Task1,"Task 1",configMINIMAL_STACK_SIZE,NULL,0,NULL);        // priorty 0
xTaskCreate(HandlerTask,"Task 2",configMINIMAL_STACK_SIZE,NULL,1,NULL);  // priority 1
    
vTaskStartScheduler();
    Task ve ISR fonksiyonları aşağıdaki gibidir. Burada Task1 periyodik olarak her saniyede bir UART üzerinden string göndermektedir. Handler task ise başlangıçta çalışacak xSemaphoreTake() fonksiyonunun kullanıldığı satıra geldiğinde task süresiz olarak bloklanacaktır.
void Task1(void * argument)
{
    for(;;)
    {
        Uart_Print("Task1 is running...\n");
        vTaskDelay(pdMS_TO_TICKS(1000));
    
    }
}
void HandlerTask(void * argument)
{
    for(;;)
    {
            xSemaphoreTake(BinarySem,portMAX_DELAY);       // If semaphore is not available, the task waits for the maximum time in the blocked state.
            Uart_Print("Handler Task is running...\n");
        
    }
}
void EXTI0_IRQHandler(void)                                                                        // ISR function
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    Uart_Print("ISR is runnng...\n");
    xSemaphoreGiveFromISR(BinarySem,&xHigherPriorityTaskWoken);    // Give the semaphore from ISR
    __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);                          // clear flag
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);                  // if xHigherPriorityTaskWoken is equal pdTRUE then force the kernel to context switching
    
}
Önemli Düzeltme: Eğer bir kesme içerisinden interrupt-safe API fonksiyonlarını çağıracak isek o kesmenin önceliğine dikkat etmemiz gerekir. FreeRTOSConfig.h dosyası içerisinde aşağıdaki gibi tanımlanmış bir makro vardır.
/* The highest interrupt priority that can be used by any interrupt service
routine that makes calls to interrupt safe FreeRTOS API functions.  DO NOT CALL
INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER
PRIORITY THAN THIS! (higher priorities are lower numeric values. */
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
    Kesme önceliğini ayarlar iken configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY  makrosuna göre ayarlamamız gerekir. Kesme önceliği atanırken 5 değerinden büyük eşit olmak zorundadır. ARM işlemcilerde öncelik numaraları ile önceliğin ters orantılı olduğunu unutmayalım. Yani öncelik numarası 4 olan  kesme öncelik numarası 5 olan kesmeden daha önceliklidir. FreeRTOS çekirdeği de arkaplanda kesmeler kullandığı için ve bu kesmeleri kesintiye uğratmamak için kendi kullanacağımız kesmelerin önceliğini beş veya beşten büyük tanımlamamız gerekir. Bu yüzden dış kesmenin önceliği 5 olarak aşağıdaki gibi tanımlanmıştır.
/* EXTI interrupt init*/
HAL_NVIC_SetPriority(EXTI0_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
    FreeRTOS içeren bir projede eğer kesme içerisinde herhangi bir “fromISR” ile biten fonksiyon çağırmayacak iseniz bu makroya dikkat etmenize gerek yoktur. İstediğiniz önceliği verebilirsiniz.
    Sonuç olarak bu örnekte STM32F103 ün A0 pinine bir buton bağlanmıştır. Bu butona basıldığında bir kesme oluşacak ve program EXTI0_IRQHandler() fonksiyonuna dallanacaktır. Programın Bu fonksiyona girdiğini görmek için UART üzerinden başka bir string daha gönderilmiştir.
    Burada portYIELD_FROM_ISR() fonksiyonu xHigherPriorityTaskWoken değişkeni eğer pdTRUE ise kernel’ı context switching’e zorlayacaktır. Burası biraz karmaşık gelebilir. Kesme oluşmadan hemen önce Task1 çalışıyordu ve kesme oluştuktan sonra ISR fonksiyonu içerisinde context switching yani kernel’ın sıradaki taskı çalıştırmasını istemeseydik program ISR fonksiyonundan çıkıp Task1 içerisinde nerede kaldıysa yürütmeye devam edecekti. Eğer xHigherPriorityTaskWoken değeri xSemaphoreGiveFromISR() fonksiyonu tarafından pdTRUE olarak ayarlanmış ise sırada bekleyen daha yüksek öncelikli bir task var demektir. Bu yüzden portYIELD_FROM_ISR(xHigherPriorityTaskWoken); satırı programın Task1 içerisine değil sırada bekleyen daha yüksek öncelikli olan task(HandlerTask)  içerisine gitmesini sağlayacaktır.

FreeRTOS ile Semaphore Kullanımı Tüm Kodlar

/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * This notice applies to any and all portions of this file
  * that are not between comment pairs USER CODE BEGIN and
  * USER CODE END. Other portions of this file, whether
  * inserted by the user or by software development tools
  * are owned by their respective copyright owners.
  *
  * Copyright (c) 2020 STMicroelectronics International N.V.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted, provided that the following conditions are met:
  *
  * 1. Redistribution of source code must retain the above copyright notice,
  *    this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright notice,
  *    this list of conditions and the following disclaimer in the documentation
  *    and/or other materials provided with the distribution.
  * 3. Neither the name of STMicroelectronics nor the names of other
  *    contributors to this software may be used to endorse or promote products
  *    derived from this software without specific written permission.
  * 4. This software, including modifications and/or derivative works of this
  *    software, must execute solely and exclusively on microcontroller or
  *    microprocessor devices manufactured by or for STMicroelectronics.
  * 5. Redistribution and use of this software other than as permitted under
  *    this license is void and will automatically terminate your rights under
  *    this license.
  *
  * THIS SOFTWARE IS PROVIDED BY STMICROELECTRONICS AND CONTRIBUTORS "AS IS"
  * AND ANY EXPRESS, IMPLIED OR STATUTORY WARRANTIES, INCLUDING, BUT NOT
  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  * PARTICULAR PURPOSE AND NON-INFRINGEMENT OF THIRD PARTY INTELLECTUAL PROPERTY
  * RIGHTS ARE DISCLAIMED TO THE FULLEST EXTENT PERMITTED BY LAW. IN NO EVENT
  * SHALL STMICROELECTRONICS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
  ******************************************************************************
  */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f1xx_hal.h"


/* USER CODE BEGIN Includes */
#include <string.h>
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"


#define Uart_Print(__message)                    HAL_UART_Transmit(&huart1,(uint8_t *)__message,strlen(__message),1000)
/* USER CODE END Includes */


/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart1;


/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/
SemaphoreHandle_t BinarySem;
/* USER CODE END PV */


/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);


/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/
void Task1(void * argument);
void HandlerTask(void * argument);
/* USER CODE END PFP */


/* USER CODE BEGIN 0 */


/* USER CODE END 0 */


/**
  * @brief  The application entry point.
  *
  * @retval None
  */
int main(void)
{
  /* USER CODE BEGIN 1 */


  /* USER CODE END 1 */


  /* MCU Configuration----------------------------------------------------------*/


  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();


  /* USER CODE BEGIN Init */


  /* USER CODE END Init */


  /* Configure the system clock */
  SystemClock_Config();


  /* USER CODE BEGIN SysInit */


  /* USER CODE END SysInit */


  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */


    BinarySem = xSemaphoreCreateBinary();
    
    xTaskCreate(Task1,"Task 1",configMINIMAL_STACK_SIZE,NULL,0,NULL);        // priority 0
    xTaskCreate(HandlerTask,"Task 2",configMINIMAL_STACK_SIZE,NULL,1,NULL);  // priority 1
    
    vTaskStartScheduler();
  /* USER CODE END 2 */


  /* We should never get here as control is now taken by the scheduler */


  /* Infinite loop */
  /* USER CODE BEGIN WHILE */


  while (1)
  {


  /* USER CODE END WHILE */


  /* USER CODE BEGIN 3 */


  }
  /* USER CODE END 3 */


}


/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{


  RCC_OscInitTypeDef RCC_OscInitStruct;
  RCC_ClkInitTypeDef RCC_ClkInitStruct;


    /**Initializes the CPU, AHB and APB busses clocks
    */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }


    /**Initializes the CPU, AHB and APB busses clocks
    */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;


  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }


    /**Configure the Systick interrupt time
    */
  HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);


    /**Configure the Systick
    */
  HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);


  /* SysTick_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(SysTick_IRQn, 15, 0);
}


/* USART1 init function */
static void MX_USART1_UART_Init(void)
{


  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }


}


/** Configure pins as
        * Analog
        * Input
        * Output
        * EVENT_OUT
        * EXTI
*/
static void MX_GPIO_Init(void)
{


  GPIO_InitTypeDef GPIO_InitStruct;


  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();


  /*Configure GPIO pin : PA0 */
  GPIO_InitStruct.Pin = GPIO_PIN_0;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
  GPIO_InitStruct.Pull = GPIO_PULLDOWN;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);


  /* EXTI interrupt init*/
  HAL_NVIC_SetPriority(EXTI0_IRQn, 5, 0);
  HAL_NVIC_EnableIRQ(EXTI0_IRQn);


}


/* USER CODE BEGIN 4 */


/*        Task Functions  */
void Task1(void * argument)
{


    for(;;)
    {
        Uart_Print("Task1 is running...\n");
        vTaskDelay(pdMS_TO_TICKS(1000));
    
    }
}
void HandlerTask(void * argument)
{
    
    for(;;)
    {
            xSemaphoreTake(BinarySem,portMAX_DELAY);                                // If semaphore is not available, the task waits for the maximum time in the blocked state.
            Uart_Print("Handler Task is running...\n");
        
    }
}
void EXTI0_IRQHandler(void)                                                                        // ISR function
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    Uart_Print("ISR is runnng...\n");
    xSemaphoreGiveFromISR(BinarySem,&xHigherPriorityTaskWoken);    // Give semaphore from ISR
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);                                                    // clear ISR flag
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);                                // if xHigherPriorityTaskWoken is equal pdTRUE then force the kernel to context switching
    
}
/* USER CODE END 4 */




/**
  * @brief  Period elapsed callback in non blocking mode
  * @note   This function is called  when TIM1 interrupt took place, inside
  * HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment
  * a global variable "uwTick" used as application time base.
  * @param  htim : TIM handle
  * @retval None
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* USER CODE BEGIN Callback 0 */


  /* USER CODE END Callback 0 */
  if (htim->Instance == TIM1) {
    HAL_IncTick();
  }
  /* USER CODE BEGIN Callback 1 */


  /* USER CODE END Callback 1 */
}


/**
  * @brief  This function is executed in case of error occurrence.
  * @param  file: The file name as string.
  * @param  line: The line in file as a number.
  * @retval None
  */
void _Error_Handler(char *file, int line)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  while(1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}


#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t* file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */


/**
  * @}
  */


/**
  * @}
  */


/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

CMSIS-RTOS ile Semaphore Örneği

osSemaphoreDef() Makrosu

    osSemaphoreDef() “cmsis_os.h” dosyası içerisine tanımlanmış aşağıdaki gibi bir makrodur. Parametre olarak sadece semaphore’a verilecek olan ismi alır.
#define osSemaphoreDef( name)   const osSemaphoreDef_t os_semaphore_def_##name = { 0 }

osSemaphoreCreate() Fonskiyonu

    CMSIS-RTOS ile semaphore oluşturmak için osSemaphoreCreate() fonksiyonu kullanılır.
osSemaphoreId osSemaphoreCreate (const osSemaphoreDef_t *semaphore_def, int32_t count)
  • *semaphore_def:  osSemaphoreDef() makrosu ile tanımlanmış olan semaphore isminin girildiği parametredir. Bu parametre osSemaphore() referansı ile girilmelidir.
  • count: Buradaki count parametresi semaphore’un binary mi yoksa counting mi olacağını belirler. Eğer bu parametre 1 girilirse binary semaphore, 1 den fazla bir değer girilirse counting semaphore olarak tanımlanır.
  • Return Değeri: Hata oluştu ise NULL, oluşmadı ise semaphore handle’ı döndürür.
semaphore1Handle = osSemaphoreCreate(osSemaphore(semaphore1), 1);
    osSemaphoreCreate() fonksiyonu yukarıdaki gibi kullanılabilir. Bu fonksiyonu kullanmadan önce osSemaphoreId tipinde bir handle tanımlanmalıdır.
osSemaphoreId semaphore1Handle;

osSemaphoreWait() Fonskiyonu

    Semaphore’u almak için bu fonksiyon kullanılır. Yani FreeRTOS daki xSemaphoreTake() fonksiyonunun CMSIS-RTOS daki karşılığıdır.

int32_t osSemaphoreWait (osSemaphoreId semaphore_id, uint32_t millisec)
  • semaphore_id: Alınacak olan semaphore handle’ı.
  • millisec: Semaphore yoksa ne kadar süre bekleneceği bu parametre ile belirlenir. Bu parametre yerine osWaitForever yazılırsa süresiz olarak beklenir.
  • Return Değeri : RTOS’da token( jeton) diye bir kavram vardır. Bu token sayısı kuyruktaki eleman sayısı gibi düşünülebilir. Bu fonksiyon kuyrukta bekleyen token yani jetonların sayısını döndürür. Counting semaphore kullanırken bu parametre kullanışlı olabilir. Eğer kuyrukta token kalmamışsa ve “millisec” parametresi osWaitForever dan farklı bir şey girilmiş ise bu süre sonunda -1 değeri döndürür. Bu demektir ki blok süresi dolmuş ve token yok.
osSemaphoreWait(semaphore1Handle,osWaitForever);

osSemaphoreRelease() Fonskiyonu

    Bu fonksiyon semaphore’u bırakmak için kullanılır. CMSIS-RTOS’da bu fonksiyonun interrupt içinde de kullanılabilir. CMSIS-RTOS’da iki farklı versiyon fonksiyon oluşturmak yerine bu işlem tek fonskiyon ile yapılmış. Bu fonksiyon nereden çağrıldığını(interrupt veya task) biliyor. İlginç değil mi? Hatta eğer interrupt içinden çağrılmış ise context switching’i otomatik olarak yapıyor.

osStatus osSemaphoreRelease (osSemaphoreId semaphore_id)
  • semaphore_id: Semaphore handle
  • Return Değeri: Eğer fonksiyon başarılı bir şekilde çalışmış ise osOK, hata oluşmuş ise osErrorOS değerini döndürür.
    Task ve ISR fonksiyonları aşağıdaki gibidir. FreeRTOS örneğindeki aynı işlemleri yapmaktadırlar.
void EXTI0_IRQHandler(void)                                   // ISR function
{
    
    Uart_Print("[CMSIS-RTOS] ISR is runnng...\n");
    osSemaphoreRelease(semaphore1Handle);                    // relaese semaphore
    __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);                    // clear ISR flag
    
}
/* Task1_Function function */
void Task1_Function(void const * argument)
{
  /* USER CODE BEGIN 5 */
  /* Infinite loop */
  for(;;)
  {
        Uart_Print("[CMSIS-RTOS] Task1 is running...\n");
    osDelay(1000);
  }
  /* USER CODE END 5 */
}

/* HandlerTask function */
void HandlerTask(void const * argument)
{
  /* USER CODE BEGIN HandlerTask */
  /* Infinite loop */
  for(;;)
  {
    osSemaphoreWait(semaphore1Handle,osWaitForever);
        Uart_Print("[CMSIS-RTOS] Handler Task is running...\n");
  }
  /* USER CODE END HandlerTask */
}
    CMSIS-RTOS’da osSemaphoreWait() fonksiyonu ilk seferde semaphore’u almadı. Aşağıda da görüldüğü gibi program çalışır çalışmaz ilk olarak HandlerTask çalıştı. Bu bir sorun mu yoksa CMSIS-RTOS’da böyle mi çalışıyor bilmiyorum. Bunun önüne geçmek için osSemaphoreWait() fonksiyonu task içerisinde for döngüsünden önce de bir kere çağrılabilir. Daha sonra normal olarak taskı bloklayacaktır.

CubeMX Ayarları

 
    Yukarıda görüldüğü gibi HandlerTask ‘ın önceliği Task1’in önceliğinden daha yüksektir.
    CubeMX de her ne kadar binary ve counting semaphore için ayrı ayrı kutucuklar olsa da. CMSIS-RTOS’un her iki semaphore’u oluşturmak için aynı fonksiyonu kullandığını gördük.

CMSIS-RTOS ile Semaphore Tüm Kodlar

/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * This notice applies to any and all portions of this file
  * that are not between comment pairs USER CODE BEGIN and
  * USER CODE END. Other portions of this file, whether
  * inserted by the user or by software development tools
  * are owned by their respective copyright owners.
  *
  * Copyright (c) 2020 STMicroelectronics International N.V.
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted, provided that the following conditions are met:
  *
  * 1. Redistribution of source code must retain the above copyright notice,
  *    this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright notice,
  *    this list of conditions and the following disclaimer in the documentation
  *    and/or other materials provided with the distribution.
  * 3. Neither the name of STMicroelectronics nor the names of other
  *    contributors to this software may be used to endorse or promote products
  *    derived from this software without specific written permission.
  * 4. This software, including modifications and/or derivative works of this
  *    software, must execute solely and exclusively on microcontroller or
  *    microprocessor devices manufactured by or for STMicroelectronics.
  * 5. Redistribution and use of this software other than as permitted under
  *    this license is void and will automatically terminate your rights under
  *    this license.
  *
  * THIS SOFTWARE IS PROVIDED BY STMICROELECTRONICS AND CONTRIBUTORS "AS IS"
  * AND ANY EXPRESS, IMPLIED OR STATUTORY WARRANTIES, INCLUDING, BUT NOT
  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  * PARTICULAR PURPOSE AND NON-INFRINGEMENT OF THIRD PARTY INTELLECTUAL PROPERTY
  * RIGHTS ARE DISCLAIMED TO THE FULLEST EXTENT PERMITTED BY LAW. IN NO EVENT
  * SHALL STMICROELECTRONICS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
  * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
  ******************************************************************************
  */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f1xx_hal.h"
#include "cmsis_os.h"


/* USER CODE BEGIN Includes */
#include <string.h>


#define Uart_Print(__message)                    HAL_UART_Transmit(&huart1,(uint8_t *)__message,strlen(__message),1000)
/* USER CODE END Includes */


/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart1;


osThreadId Task1Handle;
osThreadId HandlerHandle;
osSemaphoreId semaphore1Handle;


/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/


/* USER CODE END PV */


/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
void Task1_Function(void const * argument);
void HandlerTask(void const * argument);


/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/


/* USER CODE END PFP */


/* USER CODE BEGIN 0 */


/* USER CODE END 0 */


/**
  * @brief  The application entry point.
  *
  * @retval None
  */
int main(void)
{
  /* USER CODE BEGIN 1 */


  /* USER CODE END 1 */


  /* MCU Configuration----------------------------------------------------------*/


  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();


  /* USER CODE BEGIN Init */


  /* USER CODE END Init */


  /* Configure the system clock */
  SystemClock_Config();


  /* USER CODE BEGIN SysInit */


  /* USER CODE END SysInit */


  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */


  /* USER CODE END 2 */


  /* USER CODE BEGIN RTOS_MUTEX */
  /* add mutexes, ... */
  /* USER CODE END RTOS_MUTEX */


  /* Create the semaphores(s) */
  /* definition and creation of semaphore1 */
  osSemaphoreDef(semaphore1);
  semaphore1Handle = osSemaphoreCreate(osSemaphore(semaphore1), 1);


  /* USER CODE BEGIN RTOS_SEMAPHORES */
  /* add semaphores, ... */
  /* USER CODE END RTOS_SEMAPHORES */


  /* USER CODE BEGIN RTOS_TIMERS */
  /* start timers, add new ones, ... */
  /* USER CODE END RTOS_TIMERS */


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


  /* definition and creation of Handler */
  osThreadDef(Handler, HandlerTask, osPriorityAboveNormal, 0, 128);
  HandlerHandle = osThreadCreate(osThread(Handler), NULL);


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


  /* USER CODE BEGIN RTOS_QUEUES */
  /* add queues, ... */
  /* USER CODE END RTOS_QUEUES */


  /* Start scheduler */
  osKernelStart();
  
  /* We should never get here as control is now taken by the scheduler */


  /* Infinite loop */
  /* USER CODE BEGIN WHILE */


  while (1)
  {


  /* USER CODE END WHILE */


  /* USER CODE BEGIN 3 */


  }
  /* USER CODE END 3 */


}


/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{


  RCC_OscInitTypeDef RCC_OscInitStruct;
  RCC_ClkInitTypeDef RCC_ClkInitStruct;


    /**Initializes the CPU, AHB and APB busses clocks
    */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }


    /**Initializes the CPU, AHB and APB busses clocks
    */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;


  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }


    /**Configure the Systick interrupt time
    */
  HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);


    /**Configure the Systick
    */
  HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);


  /* SysTick_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(SysTick_IRQn, 15, 0);
}


/* USART1 init function */
static void MX_USART1_UART_Init(void)
{


  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }


}


/** Configure pins as
        * Analog
        * Input
        * Output
        * EVENT_OUT
        * EXTI
*/
static void MX_GPIO_Init(void)
{


  GPIO_InitTypeDef GPIO_InitStruct;


  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();


  /*Configure GPIO pin : PA0 */
  GPIO_InitStruct.Pin = GPIO_PIN_0;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
  GPIO_InitStruct.Pull = GPIO_PULLDOWN;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);


  /* EXTI interrupt init*/
  HAL_NVIC_SetPriority(EXTI0_IRQn, 5, 0);
  HAL_NVIC_EnableIRQ(EXTI0_IRQn);


}


/* USER CODE BEGIN 4 */


void EXTI0_IRQHandler(void)                                                                        // ISR function
{
    
    Uart_Print("[CMSIS-RTOS] ISR is runnng...\n");
    osSemaphoreRelease(semaphore1Handle);                                                // relaese semaphore
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);                                                    // clear ISR flag
    
}
/* USER CODE END 4 */


/* Task1_Function function */
void Task1_Function(void const * argument)
{


  /* USER CODE BEGIN 5 */
  /* Infinite loop */
  for(;;)
  {
        Uart_Print("[CMSIS-RTOS] Task1 is running...\n");
    osDelay(1000);
  }
  /* USER CODE END 5 */
}


/* HandlerTask function */
void HandlerTask(void const * argument)
{
  /* USER CODE BEGIN HandlerTask */
  /* Infinite loop */
    
  for(;;)
  {
    osSemaphoreWait(semaphore1Handle,osWaitForever);
        Uart_Print("[CMSIS-RTOS] Handler Task is running...\n");
  }
  /* USER CODE END HandlerTask */
}


/**
  * @brief  Period elapsed callback in non blocking mode
  * @note   This function is called  when TIM1 interrupt took place, inside
  * HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment
  * a global variable "uwTick" used as application time base.
  * @param  htim : TIM handle
  * @retval None
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* USER CODE BEGIN Callback 0 */


  /* USER CODE END Callback 0 */
  if (htim->Instance == TIM1) {
    HAL_IncTick();
  }
  /* USER CODE BEGIN Callback 1 */


  /* USER CODE END Callback 1 */
}


/**
  * @brief  This function is executed in case of error occurrence.
  * @param  file: The file name as string.
  * @param  line: The line in file as a number.
  * @retval None
  */
void _Error_Handler(char *file, int line)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  while(1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}


#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t* file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */


/**
  * @}
  */


/**
  * @}
  */


/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

Kaynaklar

Mehmet Topuz

2 Comments

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir