RTOS kullanan gerçek zamanlı uygulamalarda birbirinden bağımsız iş parçacıkları bulunabilir. Bu iş parçacıkları FreeRTOS da “task“, CMSIS_RTOS da ise”thread” olarak isimlendirilir. Bu task’ların kendilerine ait sonsuz döngüleri olabilir. Her task’ı main fonksiyonu ve bunun içinde sonsuz döngüsü olan bir program gibi düşünebiliriz.
Task Durumları
Birçok task bulunan ve tek çekirdekli bir işlemciye sahip bir sistemde herhangi bir zamanda sadece tek bir task CPU üzerinde çalıştırılır. Buna göre bir task iki ana durumdan birinde bulunabilir. Bunlar “running state” ve “not running state” dir. “Not running State” in alt durumları da vardır. Bunlar “Ready“,”Blocked” ve “Suspended” durumlarıdır.
Bir task “running state” durumunda olduğu zaman işlemci o task’ın kodlarını yürütür. Bir task “not running state” durumuna geçtiğinde task bir nevi uykudadır. Tekrar “running state” durumuna girdiğinde kaldığı yerden kodları yürütmeye devam eder. Bir task’ın “Not running state” den “running state”e geçmesi “switched in” veya “swaped in” olarak adlandırılır. Tam tersi durumda ise “switched out” veya “swapped out” olarak adlandırılır. Burada task’ları switch in veya out yapmakta sadece scheduler(zamanlayıcı) yetkilidir.
Task Priorities( Öncelikleri)
Her task’ın 0 dan başlayarak (configMAX_PRIORITIES-1) ‘e kadar tanımlanabilen öncelikleri vardır. Burada tanımlamaların başında küçük harflerle yazılan kısım hangi dosya içerisinde tanımlandığını işaret eder. Yani configMAX_PRIORITIES , FreeRTOSConfig.h dosyası içerisinde tanımlıdır. RAM kullanımı açısından configMAX_PRIORITIES olabildiğince küçük tanımlanmalıdır. Öncelik numaraları arttıkça task öncelikleri de artar yani öncelik için atanan numaralar ile task öncelikleri doğru orantılıdır. Birden fazla task aynı önceliği paylaşabilir.
Schedular başladıktan sonra task öncelikleri vTaskPrioritySet() fonksiyonu ile değiştirilebilir.
FreeRTOS ile Task Kullanımına Ait Bir Örnek
Amaç: iki adet task oluştur. Taskın biri UART üzerinden string göndersin. Diğeri led toggle etsin.
FreeRTOS u projeye CubeMX ile dahil edeceğim. Aynı proje üzerinde önce FreeRTOS ile daha sonra CMSIS-RTOS ile task oluşturma fonksiyonlarını kullanmış olacağız.
FreeRTOS da bir task oluşturmak için aşağıdaki fonksiyon kullanılır.
1 2 3 4 5 6 | BaseType_t xTaskCreate(TaskFunction_t pvTaskCode, const char * const pcName, uint16_t usStackDepth, void *pvParameters, UBaseType_t uxPriority, TaskHandle_t *pxCreatedTask) |
- pvTaskCode: Bu parametre Task için oluşturulmuş fonksiyonu temsil eder.
- pcName: String olarak bir task’a isim vermek için kullanılır. Bu parametre freertos tarafından kullanılmaz. Debug vb. olaylar için kullanılabilir.
- usStackDepth: Task için ayrılacak bellek boyutu. Burada girilen parametre byte cinsinden değildir.?Örneğin 32 bitlik bir işlemci için usStackDepth 100 girilirse stack olarak 100×4 byte=400 byte yer ayrılırır.
- pvParameters: Task fonksiyonuna bir parametre göndermek icin kullanılır. Burada parametre tipi void* olarak belirlenmiştir. Fonksiyona gönderilicek parametrenin tipinin kesin olmadığı(int ,float,char vb) zamanlarda void* ile parametre geçilir. Yani parametre olarak ister int ister float isterse string gönderilebilir.
- uxPriority: Task önceliğini ifade eder. Öncelik için girilen sayı arttıkça öncelik de artar.
- pxCreatedTask: Bir handle oluşturmak için kullanılır. Bu handle daha sonra task silmek,taskın önceliğini değiştirmek için kullanılabilir.
- Return Değeri: xTaskCreate() fonksiyonu iki farklı değer döndürür. Eğer task sorunsuz bir şekilde oluşturulduysa pdPASS , task oluşturmada sorun çıktıysa (bellek ayırma ile ilgili) pdFAIL değerini döndürür.
1 2 3 4 5 6 7 8 9 10 | xTaskCreate(Task1, // Task function "Task 1", // Task name 100, // Stack depth NULL, // Task parameter 0, // Priority NULL); // Task handle xTaskCreate(Task2,"Task 2",100,NULL,0,NULL); xTaskStartScheduler(); |
Yukarıda iki adet task oluşturulmuştur. xTaskCreate() fonksiyonundan önce Task1 ve Task2( isimler farklı da olabilir) isimli iki adet task fonksiyonu oluşturmamız gerekir. Bu fonksiyonlar aşağıdaki gibi tanımlanabilir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | void Task1(void * argument) { /* Infinite loop */ for(;;) { led_toggle(); vTaskDelay(pdMS_TO_TICKS(1000)); } } void Task2(void * argument) { /* Infinite loop */ for(;;) { Uart_Print("Task2 is running...\n"); vTaskDelay(pdMS_TO_TICKS(1000)); } } |
Task fonksiyonları herhangi bir değer döndürmediği için void tipinde tanımlıdır ve her iki task’ın da bir sonsuz döngüsü vardır. Tasklardan biri C13 e bağlı ledi bir saniye aralıklarla toggle ederken diğer task UART ile usb-ttl üzerinden bilgisayara string göndermektedir. Bu işlemler için kullandığım fonksiyonlar yabancı gelebilir. Bunlar benim tanımladığım macro fonksiyonlardır.
1 2 | #define led_toggle() HAL_GPIO_TogglePin(GPIOC,led_Pin) #define Uart_Print(__message) HAL_UART_Transmit(&huart1,(uint8_t *)__message,strlen(__message),1000) |
Yukarıdaki task fonksiyonlarının içinde gecikme fonksiyonu olarak FreeRTOS API sinin vTaskDelay() fonksiyonunu kullandık. Bu delay fonksiyonu TickType_t tipinde bir parametre alır ve bu milisaniye cinsinden değildir. Bu yüzden delay fonksiyonuna parametre girilirken pdMS_TO_TICKS() ile kullanılabilir. Bu fonksiyon milisaniyeyi tick e çevirir. Bunun yerine 1000 / portTICKRATEMS şeklinde de kullanılabilir.
Yeri gelmişken delay fonksiyonlarına değinelim. Bu örnekte HAL kütüphanesinin veya kendi oluşturduğumuz bir delay fonksiyonunu kullanmak mı daha mantıklıdır yoksa FreeRTOS un bize sağladığı delay fonksiyonunu kullanmak mı? FreeRTOS’un vTaskDelay() fonksiyonu bir task içerisinde çağırıldığında task, fonksiyon içine girilen tick boyunca bloklanır. Yani vTaskDelay() fonksiyonu çağırıldığında task çalıştırılmaz ve sıradaki taskın kodları yürütülür. Diğer delay fonksiyonları( örneğin; HAL_Delay) kullanılsaydı program task içerisinde çalışmaya devam eder. Çünkü bu delay fonksiyonları işlemciyi sonsuz bir döngü içerisinde oyalar. Bu yüzden FreeRTOS da bu fonksiyonları kullanmak yerine RTOS un sağladığı delay fonksiyonunu kullanmak verim açısından daha iyidir.
Scheduler task’lar arasında geçiş yapabilmesi için bir timer kaynağına ihtiyac duyar. Arm tabanlı mikrodenetleyicilerde FreeRTOS bunun için systick timerı kullanılır. FreeRTOSConfig dosyasına gittimizde aşağıdaki gibi bir tanımlama görürüz.
1 | #define configTICK_RATE_HZ ((TickType_t)1000) |
Bu satır task’lar arası geçişin kaç saniyede bir olacağını belirlemek için kullanılır. Belirlenen bu değere göre belli bir zaman sonra tick kesmesi oluşur ve bu kesmenin içerisinde bir task tan diğerine geçiş işlemi(context switching) yapılır. Örneğin yukarıdaki satırda tick rate 1000 hz olarak belirlenmiş. Bu demektir ki aynı öncelikteki her task arası geçiş 1 ms de bir olacak. Normalde tick rate 100 hz civarlarında tanımlanır.
Yukarıdaki task fonskiyonlarındaki işlemler(gecikme fonksiyonu hariç) 1 ms den kısa sürdüğü için task’ın içerisindeki işlemler yapıldıktan sonra bir sonraki task yürütülecektir. Eğer task fonksiyonunun içindeki kodların işlenme süresi 1 ms den fazla olsaydı kodlar tick kesmesi oluşana kadar yürütülecek ve diğer task’a geçiş yapılacaktı. Bir sonraki tick kesmesinden sonra önceki task’ta kodlar kaldığı yerden yürütülmeye devam edecekti.
Task Fonksiyonuna Parametre Gönderme
Bu örnekte task’ların yaptıkları işlemler aynı fakat task2’nin UART üzerinden gönderecek olduğu stringi task oluşturulurken parametre olarak task fonksiyonuna gönderelim. Bu işlemi aşağıdaki gibi yapabiliriz.
1 | xTaskCreate(Task2,"Task 2",100,(void *)"Task2 is running...\n",0,NULL); |
1 2 3 4 5 6 7 8 9 10 11 | void Task2(void * argument) { /* Infinite loop */ for(;;) { Uart_Print((char *) argument); vTaskDelay(1000); } } |
Heap Size Seçimi
Task fonksiyonlarının içerisinde yapılacak işlemlerin sayısı arttıkça daha fazla stack ihtiyacı duyulacaktır. Yukarıdaki task’lar içerisinde çok büyük işlemler yapılmadığı için usStackDepth parametresi 100 girilmiştir. Bu demektir ki heap içerisinde 100×4= 400 byte lık bir alan Task1 ve Task2 için ayrı ayrı ayrılmıştır. Her oluşturulan RTOS nesnesi heap içerisinde yer kaplamaktadır. Her nesnenin boyutunu hesaplamak böyle zor olacaktır kaldı ki sadece taskların stack boyutu değil aynı zamanda TCB(task control block) lerin heap içerisinde kapladığı boyutu da hesaplamak gerekir. Bunun yerine aşağıdaki fonksiyon ile tanımlanan heap in ne kadarının boş olduğu çekilebilir.
1 | size_t size = xPortGetFreeHeapSize(); |
Bu fonksiyonun kod içerisinde kullanım yeri önemlidir. Bütün RTOS nesneleri oluşturulduktan sonra bu fonksiyonun çağrılması daha iyi sonuç verecektir. Bu fonksiyonu vTaskStartScheduler() dan sonra çağırmak daha iyi olacaktır fakat program main içerisinde hiç bir zaman bu fonksiyondan sonrasına geçmez. vTaskStartScheduler() fonksiyonundan önce de çağıramayız çünkü bu scheduler başlatıldığında idle task oluşturulur ve bu da heap içerisinde bir alan kaplar. xPortGetFreeHeapSize() fonksiyonunu oluşturulan task fonksiyonlarından birinin içerisinde çağırmak daha iyidir.
1 2 3 4 5 6 7 8 9 10 11 | void Task1(void * argument) { size_t size = xPortGetFreeHeapSize(); /* Infinite loop */ for(;;) { led_toggle(); vTaskDelay(1000); } } |
Ben FreeRTOS ile proje geliştirirken başlangıç olarak heap boyutunu olabildiğince yüksek(mikrodenetleyicinin RAM boyutu göz önüne alınarak) tanımlıyorum fakat heap boyutunun gereğinden fazla tanımlanması ise gereksiz RAM kullanımına yol açacaktır. Bunun için tüm RTOS nesnelerini oluşturduktan sonra kullanılmayan heap boyutunu öğrenip heap boyutunu buna göre azaltıyorum. Örneğin; Yukarıdaki task1 ve task2 örnekleri için başlangıçta 5 kb olacak şekilde aşağıdaki gibi tanımladım.
1 | #define configTOTAL_HEAP_SIZE ((size_t)(5*1024))// 5kb |
Daha sonra Debug ekranından “size” değişkenin boyutunu öğrendim.
Yukarıda görüldüğü gibi heap içerisinde 3.5 kb a yakın boş bir alan bulunmaktır ve bu RAM de yer kaplamaktadır. Artık heap içerisindeki boş alanı bulduğumuza göre heap boyutunu tekrar düzenleyebiliriz.
1 | #define configTOTAL_HEAP_SIZE ((size_t)(5*1024-3480)) |
Bu tanımlamadan sonra tekrar debug yapıldığında “size” değişkeni sıfır olur. Böylelikle RAM kullanımını olabildiğince azaltabiliriz. Burada boş alanın hepsini çıkarmamız task içerisinde başka bir RTOS nesnesi oluşturduğumuz projelerde sorun çıkarabilir. Bu yüzden burada 3480 yerine 3400 gibi daha düşük bir değer kullanılabilir. Yukarıdaki gibi bir tanımlamanın basit led yakıp söndüren ve uart üzerinden string gönderen bir program için kullanılmasında bir sakınca yoktur.
Tüm Kodlar(FreeRTOS ile)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 | /* Includes ------------------------------------------------------------------*/ #include "main.h" #include "stm32f1xx_hal.h" //#include "cmsis_os.h" /* USER CODE BEGIN Includes */ #include "FreeRTOS.h" #include "task.h" #include <string.h> #define led_toggle() HAL_GPIO_TogglePin(GPIOC,led_Pin) #define Uart_Print(__message) HAL_UART_Transmit(&huart1,(uint8_t *)__message,strlen(__message),1000) /* USER CODE END Includes */ /* Private variables ---------------------------------------------------------*/ UART_HandleTypeDef huart1; //osThreadId defaultTaskHandle; /* USER CODE BEGIN PV */ /* Private variables ---------------------------------------------------------*/ size_t size; /* 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 Task2(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 */ /* USER CODE END 2 */ /* USER CODE BEGIN RTOS_MUTEX */ /* add mutexes, ... */ /* USER CODE END RTOS_MUTEX */ /* 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 defaultTask */ // osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 128); // defaultTaskHandle = osThreadCreate(osThread(defaultTask), 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 */ xTaskCreate(Task1, // Task function "Task 1", // Task name 100, // Stack depth NULL, // Task parameter 0, // Priority NULL); // Task handle xTaskCreate(Task2,"Task 2",100,(void *)"Task2 is running...\n",0,NULL); vTaskStartScheduler(); 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_GPIOC_CLK_ENABLE(); __HAL_RCC_GPIOD_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(led_GPIO_Port, led_Pin, GPIO_PIN_RESET); /*Configure GPIO pin : led_Pin */ GPIO_InitStruct.Pin = led_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(led_GPIO_Port, &GPIO_InitStruct); } /* USER CODE BEGIN 4 */ void Task1(void * argument) { size = xPortGetFreeHeapSize(); /* Infinite loop */ for(;;) { led_toggle(); vTaskDelay(1000); } } void Task2(void * argument) { /* Infinite loop */ for(;;) { Uart_Print((char *) argument); vTaskDelay(1000); } } /* 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 Task(Thread) Kullanımı
Aynı uygulamayı CubeMx ile CMSIS-RTOS kullanarak yapalım. Burada CMSIS-RTOS diyorum ama aslında arkaplanda FreeRTOS kullanılıyor.
CubeMx ile aşağıdaki gibi FreeRTOS aktif edilebilir. FreeRTOS systick timerı kullandığı için “TimeBase Source” systick timerdan farklı seçilmelidir. Ben aşağıda görüldüğü gibi Timer1 olarak seçtim. Yani HAL kütüphanesi Timer1’i kullanacak.
TICK_RATE_HZ bir önceki örnekte olduğu gibi 1 khz olarak ayarlandı.
USE_PREEMPTION Enabled ise FreeRTOS pre-emptive(öncelik) olarak ,Disabled ise co-operative(işbirliği) olarak çalışır.
FreeRTOS da bu FreeRTOSConfig.h dosyası içerisinde aşağıdaki gibi tanımlanır.
1 | #define configUSE_PREEMPTION 1 |
MINIMAL_STACK_SIZE , Idle task için tanımlanır. Burada default olarak 128 olarak tanımlanmış o yüzden değiştirmedim.
TOTAL_HEAP_SIZE ı 5000 byte(tam olarak 5 kb değil) olarak tanımladım. Bir önceki örnekte de, bu örnekte de heap_4 kullanılmıştır.
Başlangıçta CubeMx “defaultTask” isminde bir task oluşturur. Bu task idle task değildir. O yüzden bunu yeniden düzenleyebiliriz.
defaultTask’ı aşağıdaki gibi düzenleyebiliriz. Burada öncelikler sayı olarak değil isim olarak karşımıza çıkmaktadır. Bu örnekte önceliği “osPriorityNormal” olarak ayarladım.
Task2’nin ayarları ise aşağıdaki gibidir. Burada her iki task’ın önceliğini aynı olarak ayarladım.
FreeRTOS Heap Usage sekmesinden taskların kullandığı alanları ve kullanılmayan alanları görebiliriz. CubeMx bize böyle bir güzellik yapmış.
Bu ayarlamalardan sonra kod kısmına geçebiliriz. CubeMX in oluşturduğu kodları bir inceleyelim.
main fonksiyon üzerinde ilk olarak aşağıdaki kodlar oluşturulmuş.
1 | osThreadId Task1Handle; osThreadId Task2Handle; |
Buradan anlıyoruz ki task oluşturulurken handle parametresi kullanılacak.Daha sonra task fonksiyonlarının main üzerinde aşağıdaki gibi tanımlaması karşımıza çıkıyor.
1 2 | void Task1_Function(void const * argument); void Task2_Function(void const * argument); |
CubeMX fonksiyonları main fonksiyonun altında aşağıdaki tanımlar. Başlangıçta bu fonksiyonların içinde sadece sonsuz döngü ve bu döngünün içerisinde osDelay(1) şeklinde bir gecikme bulunur.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | /* Task1_Function function */ void Task1_Function(void const * argument) { /* USER CODE BEGIN 5 */ /* Infinite loop */ for(;;) { led_toggle(); osDelay(1000); } /* USER CODE END 5 */ } /* Task2_Function function */ void Task2_Function(void const * argument) { /* USER CODE BEGIN Task2_Function */ /* Infinite loop */ for(;;) { Uart_Print("Task2 is running...\n"); osDelay(1000); } /* USER CODE END Task2_Function */ } |
Yukarıda görüldüğü gibi task1 ve task2 fonksiyonları bir önceki örnek ile aynı işlemleri yapmaktadır. Burada delay fonksiyonu CMSIS-RTOS’a özeldir. FreeRTOS da olduğu gibi tick i milisaniyeye çevirmemiz gerekmez. main fonskiyon içerisinde aşağıdaki task tanımlamaları yapılmış. Buradan sonra artık task yerine thread olarak isimlendirsek daha doğru olur.
1 2 3 4 5 6 7 8 9 | /* 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 Task2 */ osThreadDef(Task2, Task2_Function, osPriorityNormal, 0, 128); Task2Handle = osThreadCreate(osThread(Task2), NULL); |
osThreadDef fonksiyonu cmsis_os.h dosyası içerisinde tanımlanmış aşağıdaki gibi bir makrodur.
1 | #define osThreadDef(name, thread, priority, instances, stacksz) |
Burada instance parametresi var. Bu parametre osThreadCreate() fonksiyonun kaç kere çağrılacağını bildirir. CMSIS-RTOS da tek bir thread fonksiyonu farklı thread id ve farklı parametreler gönderilerek iki farklı thread fonksiyonu gibi kullanılabilir. Örnek olarak aşağıdaki gibi instance parametresi 2 olarak girilirse osThreadCreate() fonksiyonu 2 kere çağrılabilir.
1 | osThreadDef(thread1, osPriorityNormal, 2, 0); |
Bunun kullanım amacını basitçe şöyle anlayabiliriz. UART üzerinden haberleşen bir task düşünelim. Aynı task kodlarını değiştirmeden aynı fonksiyonu farklı bir UART ( UART2,UART3 vb.) ile kullanmak istediğimizde instance parametresini kullanabiliriz.
1 2 | threadId1 = osThreadCreate(osThread(thread1), UART1); threadId2 = osThreadCreate(osThread(thread1), UART2); |
Task fonksiyonu ise aşağıdaki gibi olabilir. Projede aşağıdaki gibi sadece tek bir task fonksiyonu bulunsa da osThreadCreate fonksiyonu iki kere kullanıldığı için sanki iki adet task varmış gibi program çalışır. Task ilk çalıştığında UART1 den veri gönderirken , scheduler diğer task’ı(aslında aynı fonksiyon) çalıştırdığında UART2 den veri gönderilir.
1 2 3 4 5 6 7 8 9 10 11 | void Task_Function(void const * argument) { /* USER CODE BEGIN 5 */ /* Infinite loop */ for(;;) { Uart_Print("Task is running..\n",argument); // argument burada UART1 ve UART2 olabilir. osDelay(1000); } /* USER CODE END 5 */ } |
osThreadCreate fonksiyonu ise cmsis_os.c içerisinde aşağıdaki tanımlıdır.
1 2 3 4 5 | osThreadId osThreadCreate (const osThreadDef_t *thread_def, void *argument) { ... ... } |
Thread’ler tanımlandıktan sonra aşağıdaki kod ile scheduler başlatılır.
1 | osKernelStart(); |
Tüm Kodlar( CMSIS-RTOS ile)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 | /* Includes ------------------------------------------------------------------*/ #include "main.h" #include "stm32f1xx_hal.h" #include "cmsis_os.h" /* USER CODE BEGIN Includes */ #define led_toggle() HAL_GPIO_TogglePin(GPIOC,led_Pin) #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 Task2Handle; /* USER CODE BEGIN PV */ /* Private variables ---------------------------------------------------------*/ //size_t size; /* 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 Task2_Function(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 */ /* 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 Task2 */ osThreadDef(Task2, Task2_Function, osPriorityNormal, 0, 128); Task2Handle = osThreadCreate(osThread(Task2), 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_GPIOC_CLK_ENABLE(); __HAL_RCC_GPIOD_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(led_GPIO_Port, led_Pin, GPIO_PIN_RESET); /*Configure GPIO pin : led_Pin */ GPIO_InitStruct.Pin = led_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(led_GPIO_Port, &GPIO_InitStruct); } /* USER CODE BEGIN 4 */ /* USER CODE END 4 */ /* Task1_Function function */ void Task1_Function(void const * argument) { /* USER CODE BEGIN 5 */ /* Infinite loop */ for(;;) { led_toggle(); osDelay(1000); } /* USER CODE END 5 */ } /* Task2_Function function */ void Task2_Function(void const * argument) { /* USER CODE BEGIN Task2_Function */ /* Infinite loop */ for(;;) { Uart_Print("Task2 is running...\n"); osDelay(1000); } /* USER CODE END Task2_Function */ } /** * @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
- Mastering the FreeRTOS™ Real Time Kernel-A Hands-On Tutorial Guide , Richard Barry
- freertos.org
- https://www.keil.com/pack/doc/CMSIS/RTOS/html/index.html
Öncelikle size çok ama çok teşekkür ederim, acaba iot ile alakalı da böyle detaylı bir yazı yazmanız mümkün mü? Özellikle STM32 ile iot kullanımı hakkında. Teşekkür ederim.
Rica ederim. Daha önceki yazılarımda IOT projelerinde sıkça kullanılan MQTT protokolüne değinmiştim. O yazıya ulaşmak için şu linke tıklayabilirisiniz. https://mehmettopuz.net/esp8266-ve-stm32-ile-mqtt-protokolu/.html
İlerleyen zamanlarda IOT ile ilgili çok detaylı yazılar gelmese bile IOT kavramları ile ilgili yazılar yazabilirim.
Çünkü IOT kavramı FreeRTOS gibi böyle sekiz yazıda anlatılacak bir konu değil. Daha çok IOT projelerinde kullanılan haberleşme protokolleri vb. konular ile ilgili basit projeler paylaşabilirim belki ilerleyen zamanlarda.
Teşekkür ederim.