STM32/STM32_RTOS

STM32 RTOS 알아보기 1편(xTaskCreate, vTaskDelay, vTaskDelayUntil)

원원 2024. 8. 11. 11:02

안녕하세요. 오늘은 STM32보드로 RTOS에서 사용하는 기본적인 함수에 대해 알아보겠습니다. 사용하는 보드는 NUCLEO-F103RB이고 어떤보드던 큰 상관은 없습니다.


먼저 프로젝트를 만들고 FREERTOS를 활성화해줘야합니다. FREERTOS를 선택하면 Interface를 선택하라고 나옵니다. CMSIS V1과 CMSIS V2의 차이는 CMSIS 인터페이스에서 사용하는 함수들의 차이인데 여기서는 CMSIS 인터페이스를 사용하지않고 FreeRTOS 인터페이스를 사용합니다. 예를들어 테스크를 만들때 FreeRTOS는 xTaskCreate함수를 사용하고 CMSIS는 osThreadCreate함수를 사용합니다. 사실 osThreadCreate함수의 코드를보면 결국에는 xTaskCreate함수를 호출합니다. 결국에는 xTaskCreate함수를 래핑해서 osThreadCreate함수를 만든겁니다. 

그래서 CMSIS V1을 선택하고 함수는 FreeRTOS 함수를 사용할예정입니다. 아래의 그림에서보면 태스크,타이머,이벤트 등 선택해서 코드제너레이터를하면 CMSIS 형태로 나옵니다.


Task에 대해 알아보겠습니다. 실제 물리적인 CPU는 1개인데 가상의 CPU를 가지고있는것처럼 태스크가 동작합니다.
여기서 중요한건 실제 물리적인 CPU가 1개이므로 하나의 태스크가 계속 CPU를 점유하고있으면 다른태스크들은 동작을 안하게됩니다. 그래서 이러한것을 고려해서 코드를 작성해야합니다.


* xTaskCreate함수
BaseType_t xTaskCreate(
TaskFunction_t pvTaskCode,
const char * const pcName,
unsigned short usStackDepth,
void *pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *pxCreatedTask
);
태스크를 생성하는 함수입니다. 함수에서 맨 앞에 x가 붙어있는데 이 접두사는 함수의 반환형이 BaseType_t을 의미합니다. BaseType_t는 성공(pdPASS) or 실패(pdFAIL)을 리턴해줍니다.
pvTaskCode : 실행될 함수의 포인터입니다.
pcName : 태스크의 이름을 나타내는 문자열 포인터입니다. 디버깅시 태스크를 식별하기위해 사용합니다.
usStackDepth : 태스크의 스택 크기입니다. 크기가 작으면 태스크가 실행안될수도 있습니다.
pvParameters : 태스크에 전달할 인수입니다.
uxPriority : 태스크의 우선순위를 설정합니다.
pxCreatedTask : 생성된 태스크의 핸들을 저장할 포인터입니다. 이 핸들을 통해서 태스크를 참조하거나 제어할 수 있습니다.

이제 실제로 xTaskCreate함수를 사용해서 태스크를 만들어보겠습니다. 아래의 코드에서 myTask()는 main문에서 실행한 함수입니다. 1초마다 Hello wolrd문자와 설정한 태스크의 이름이 시리얼모니터에 표시됩니다.


TaskHandle_t xHandleTask1;
void Task1(void *pvParameters)
{
  while(1)
  {
    printf("Hello world  %s \n",pcTaskGetName(NULL));
    vTaskDelay(pdMS_TO_TICKS(1000));
  }
}

void myTask()
{
  BaseType_t xReturned;
  xReturned = xTaskCreate(Task1,
            "Task1",
            256,
            NULL,
            0,
            &xHandleTask1 );
  if(xReturned == pdPASS)
  {
    printf("Task1 PASS \n");
  }
  else
  {
    printf("Task1 FAIL \n");
  }
}


*vTaskDelay함수
void vTaskDelay(TickType_t xTicksToDelay);
태스크에 딜레이를 줄때 사용하는 함수입니다. 태스크를 일정 시간동안 대기상태로 전환시킵니다.
접두사 v는 반환 타입이 void인것을 의미합니다.
xTicksToDelay : 현재 태스크가 얼마나 오랜 시간 동안 지연될지를 틱 단위로 지정합니다.
틱(Tick)은 FreeRTOS에서 시간의 기본 단위입니다. 틱은 일정 주기로 발생하고 ' configTICK_RATE_HZ' 매크로로 설정됩니다. #define configTICK_RATE_HZ ((TickType_t)1000)
기본적으로 틱은 1000으로 설정되어있고 이는 1초에 1000틱이 발생하는것을 의미합니다. 
그래서 vTaskDelay(1000)으로 한다면 1000틱만큼 지연이니 1초가 지연됩니다. 그러나 TICK이 1000으로 설정이 안되어있고 500으로 설정되어있는경우라면 vTaskDelay(1000)은 2초입니다.
TICK의 값이 무엇이던 1000이면 1초로 설정하기위해 pdMS_TO_TICKS매크로를 사용합니다.
#
define pdMS_TO_TICKS( xTimeInMs ) ( ( TickType_t ) ( ( ( TickType_t ) ( xTimeInMs ) * ( TickType_t ) configTICK_RATE_HZ ) / ( TickType_t ) 1000 ) )

pdMS_TO_TICKS(1000) 을 호출할때 틱이 1000인경우와 500인경우 예를들어보겠습니다.
1) 틱이 1000인경우
1000(매개변수) * 1000(틱) / 1000(고정값) = 1000
==> 틱이 1000이므로 1초 맞음

2) 틱이 500인경우
1000 * 500 / 1000 = 500
==> 틱이 500이므로 1초 맞음


*vTaskDelayUntil함수
void vTaskDelayUntil( TickType_t *pxPreviousWakeTime, TickType_t xTimeIncrement );
태스크를 일정시간마다 태스크를 실행되도록 합니다.
pxPreviousWakeTime : 태스크가 마지막으로 깨어났던 시점을 가리키는 포인터입니다.
xTimeIncrement : 태스크가 주기적으로 실행될 간격(틱단위)입니다.


vTaskDelayUntil함수를 사용하기위해서 INCLUDE_vTaskDelayUntil가 1이여야 합니다.
아래의 예시에서 Task1가 있고 delay_200ms_func()은 호출하면 200ms동안 지연되는 함수입니다. 
Task1 실행 => 200ms 지연 => 800ms 지연 => Task1 실행 =>  200ms 지연 => 800ms 지연 ...
vTaskDelayUntil은 1000ms인데 800ms가지연된 걸볼 수 있습니다. 그 이유는 Task가실행되고 나서200ms만큼 이미 지연이됐기 때문에800ms만큼 지연시켜야1000ms이므로그렇습니다.

void Task1(void *pvParameters)
{
  TickType_t xLastWakeTime;
  xLastWakeTime = xTaskGetTickCount();

  while(1)
  {
    delay_200ms_func();
   

    vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(1000));
  }
}



xLastWakeTime값에 실제로 어떤값이 들어가는지 알아보겠습니다.
먼저 초기값으로는 xLastWakeTime에 0이 들어가있습니다. 그 다음 GetTick을 하는데 그 위에서 200ms만큼 지연을하니 202ms가 나옵니다. (2ms가 더 높게 나오는이유는 다른 태스크or인터럽트or함수실행시간등..)
vTaskDelayUntil함수에서는 아래의 코드처럼 (현재틱 -xLastWakeTime) < 딜레이시간을 해서 vTaskDelay 시킵니다.
그럼 (202 - 0) < 1000이므로 True이고 xTimeToWake = xLastWakeTime+딜레이시간 이므로 xTimeToWake = 1000
vTaskDelay(1000-202) 딜레이시킵니다.
결국에 vTaskDelayUntil도 시간을 계산해서 남은시간을 vTaskDelay로 딜레이시키는 함수입니다.

void Task1(void *pvParameters)
{
  TickType_t xLastWakeTime;
  xLastWakeTime = xTaskGetTickCount();

  while(1)
  {
    vTaskDelay(pdMS_TO_TICKS(200));
    printf("xLastWakeTime : %d  \n",xLastWakeTime);
    printf("GetTick : %d  \n",xTaskGetTickCount());

    vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(1000));
  }
}


예시로 LED 6개를 연결하고 TASK를 6개 만들어서 LED가 순차적으로 켜지게 해보겠습니다.
이 예제에서는 LED관련 파라메터를 만들고 태스크를 만들때 파라메터를 넘깁니다. 그리고나서 파라메터를 이용해서 태스크를 만들게됩니다.

#include <stdio.h>
#include "cmsis_os.h"
#include "main.h"
#include "FreeRTOS.h"
#include "task.h"

void LED_Task(void *pvParameters);

TaskHandle_t xHandleTask1, xHandleTask2, xHandleTask3, xHandleTask4, xHandleTask5, xHandleTask6;

typedef struct {
    GPIO_TypeDef* GPIO_Port;
    uint16_t GPIO_Pin;
    TickType_t onTime;
    TickType_t period;
} LED_Params;

void myTask()
{
    static LED_Params led1 = {LED1_GPIO_Port, LED1_Pin, pdMS_TO_TICKS(100), pdMS_TO_TICKS(1000)};
    static LED_Params led2 = {LED2_GPIO_Port, LED2_Pin, pdMS_TO_TICKS(200), pdMS_TO_TICKS(1000)};
    static LED_Params led3 = {LED3_GPIO_Port, LED3_Pin, pdMS_TO_TICKS(300), pdMS_TO_TICKS(1000)};
    static LED_Params led4 = {LED4_GPIO_Port, LED4_Pin, pdMS_TO_TICKS(400), pdMS_TO_TICKS(1000)};
    static LED_Params led5 = {LED5_GPIO_Port, LED5_Pin, pdMS_TO_TICKS(500), pdMS_TO_TICKS(1000)};
    static LED_Params led6 = {LED6_GPIO_Port, LED6_Pin, pdMS_TO_TICKS(600), pdMS_TO_TICKS(1000)};

    xTaskCreate(LED_Task, "Task1", 256, &led1, 1, &xHandleTask1);
    xTaskCreate(LED_Task, "Task2", 256, &led2, 1, &xHandleTask2);
    xTaskCreate(LED_Task, "Task3", 256, &led3, 1, &xHandleTask3);
    xTaskCreate(LED_Task, "Task4", 256, &led4, 1, &xHandleTask4);
    xTaskCreate(LED_Task, "Task5", 256, &led5, 1, &xHandleTask5);
    xTaskCreate(LED_Task, "Task6", 256, &led6, 1, &xHandleTask6);
}

void LED_Task(void *pvParameters)
{
    LED_Params *params = (LED_Params*) pvParameters;
    TickType_t xLastWakeTime = xTaskGetTickCount();

    while(1)
    {
        HAL_GPIO_WritePin(params->GPIO_Port, params->GPIO_Pin, GPIO_PIN_RESET);
        vTaskDelay(params->onTime);
        HAL_GPIO_WritePin(params->GPIO_Port, params->GPIO_Pin, GPIO_PIN_SET);
        vTaskDelayUntil(&xLastWakeTime, params->period);
    }
}

소스코드(Commits: led_blink)
https://github.com/yhunterr/STM32_RTOS_STUDY/commits/main/