STM32F107 micro C-OS 3 RTOS : Round Robin Scheduling

카테고리 없음

2019. 4. 23. 04:10

라운드 로빈 스케쥴링 (Round Robin Scheduling)은 같은 우선순위(Priority)를 가지는 태스크들에게 각각 시간할당(Time Quanta)를 분배하고 시분할 방식으로 태스크 전환이 일어나게끔 하는 정책이다.

 

이번 포스팅에서는 U-ART(범용 비동기 송수신기)와 보드에 LED를 사용하여 이러한 RR방식을 실습해 보려고 한다.

 

다음과 같이 네 개의 태스크를 만들것이다.

Task Name Priority Time Quanta UART Character
AppTaskStart APP_TASK_START_PRIO(2) X X
AppTask1 APP_TASK_PRIO(3) 2 Tick *
AppTask2 APP_TASK_PRIO(3) 4 Tick @
AppTask3 APP_TASK_PRIO(3) 1 Tick #

AppTask1, 2, 3는 AppTaskStart 내의 AppTaskCreate()함수에서 만들 예정이다.

 

먼저 AppTask1, 2, 3를 위한 TCB, Stack, 그리고 Function Prototype을 만들어 준다.

...
// Stack Size Configuration
#define APP_TASK_STK_SIZE 128

...
// Priority Configuration
#define APP_TASK_PRIO 3

...
// TCB Objects
static OS_TCB AppTask1_TCB;
static OS_TCB AppTask2_TCB;
static OS_TCB AppTask3_TCB;

...
// Stack Objects
static CPU_STK AppTask1_Stk[APP_TASK_STK_SIZE];
static CPU_STK AppTask2_Stk[APP_TASK_STK_SIZE];
static CPU_STK AppTask3_Stk[APP_TASK_STK_SIZE];

...
// Function Prototypes
static  void  AppTask1          (void *p_arg);
static  void  AppTask2          (void *p_arg);
static  void  AppTask3          (void *p_arg);

...

그리고 UART를 사용하기 위해서 아래와 같이 Configuration을 해 주어야 한다. USART_BaudRate = 9600으로 설정하였으므로 Putty상에서도 Speed를 9600으로 맞춰줘야 한다.

static void USART_CNF() {
  GPIO_InitTypeDef GPIO_InitStructure;
  USART_InitTypeDef USART_InitStructure;
  
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO | RCC_APB2Periph_USART1, ENABLE);
  
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  
  GPIO_Init(GPIOA, &GPIO_InitStructure);
  
  USART_InitStructure.USART_BaudRate = 9600;
  USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  USART_InitStructure.USART_StopBits = USART_StopBits_1;
  USART_InitStructure.USART_Parity = USART_Parity_No;
  USART_InitStructure.USART_Mode = USART_Mode_Tx;
  USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  
  USART_Init(USART1, &USART_InitStructure);
  USART_Cmd(USART1, ENABLE);
}

STM32F107 Reference Manual을 참조했을 때 아래와 같이 APB2에 대한 clk 인가 및 USART1(UART를 위한), GPIOD(LED를 위한)의 출력 설정이 필요하기 때문이다. (APB2의 clk 인가 및 LED 출력설정은 되어있다고 가정)

main함수에서 OSTaskCreate()를 이용하여 AppTaskStart를 만들어주게 되면 아래의 함수가 실행되는데, 아래와 같이 Kernel Tick을 초기화 해주어야 RR 스케쥴링이 정상동작한다. RR 스케쥴링은 Tick을 근본으로 하여 동작하기 때문이다.(Micrium Documentation을 참조하자.)

static  void  AppTaskStart (void *p_arg)
{
    CPU_INT32U  cpu_clk_freq;
    CPU_INT32U  cnts;
    OS_ERR      err;
  
   (void)p_arg;

    BSP_Init();                                                 
    USART_CNF();
    CPU_Init();
    
    // Kernel Tick Initialization 부분
    cpu_clk_freq = BSP_CPU_ClkFreq();
    cnts         = cpu_clk_freq / (CPU_INT32U)OSCfg_TickRate_Hz;
    OS_CPU_SysTickInit(cnts);
    
    #if OS_CFG_STAT_TASK_EN > 0u
    OSStatTaskCPUUsageInit(&err);                               
    #endif
    
    CPU_IntDisMeasMaxCurReset();
    
    // 세 개의 Task 만듬
    AppTaskCreate();                                            

    BSP_LED_Off(0);
    while (DEF_TRUE) {                                          
        OSTimeDlyHMSM(0, 0, 0, 250, 
                      OS_OPT_TIME_HMSM_STRICT, 
                      &err);
    }
}

AppTaskCreate() 내에서는 다음과 같이 세 개의 Task를 만든다.

...

#define TASK1_CLOCK_TICK 2
#define TASK2_CLOCK_TICK 4
#define TASK3_CLOCK_TICK 1

...

static  void  AppTaskCreate (void)
{
    OS_ERR  err;
    
    OSTaskCreate((OS_TCB     *)&AppTask1_TCB, 
                 (CPU_CHAR   *)"App Task1 Start",
                 (OS_TASK_PTR )AppTask1, 
                 (void       *)0,
                 (OS_PRIO     )APP_TASK_PRIO,
                 (CPU_STK    *)&AppTask1_Stk[0],
                 (CPU_STK_SIZE)APP_TASK_STK_SIZE / 10,
                 (CPU_STK_SIZE)APP_TASK_STK_SIZE,
                 (OS_MSG_QTY  )0,
                 (OS_TICK     )TASK1_CLOCK_TICK, // Time Quanta 설정 부분
                 (void       *)0,
                 (OS_OPT      )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
                 (OS_ERR     *)&err);
    
    // create AppTask3
    OSTaskCreate((OS_TCB     *)&AppTask3_TCB, 
                 (CPU_CHAR   *)"App Task3 Start",
                 (OS_TASK_PTR )AppTask3, 
                 (void       *)0,
                 (OS_PRIO     )APP_TASK_PRIO,
                 (CPU_STK    *)&AppTask3_Stk[0],
                 (CPU_STK_SIZE)APP_TASK_STK_SIZE / 10,
                 (CPU_STK_SIZE)APP_TASK_STK_SIZE,
                 (OS_MSG_QTY  )0,
                 (OS_TICK     )TASK3_CLOCK_TICK, // Time Quanta 설정 부분
                 (void       *)0,
                 (OS_OPT      )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
                 (OS_ERR     *)&err);
    
    // create AppTask2
    OSTaskCreate((OS_TCB     *)&AppTask2_TCB, 
                 (CPU_CHAR   *)"App Task2 Start",
                 (OS_TASK_PTR )AppTask2, 
                 (void       *)0,
                 (OS_PRIO     )APP_TASK_PRIO,
                 (CPU_STK    *)&AppTask2_Stk[0],
                 (CPU_STK_SIZE)APP_TASK_STK_SIZE / 10,
                 (CPU_STK_SIZE)APP_TASK_STK_SIZE,
                 (OS_MSG_QTY  )0,
                 (OS_TICK     )TASK2_CLOCK_TICK, // Time Quanta 설정 부분
                 (void       *)0,
                 (OS_OPT      )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
                 (OS_ERR     *)&err);
}

태스크를 만드는 시점에서 위와 같이 OSTaskCreate()의 매개변수로 RR스케쥴링 시의 Time Quanta를 지정해 줄수 있다.

세 개의 태스크들이 만들어지면 아래와 같이 각 태스크의 함수가 시분할 방식으로 동작하게 된다. 물론, AppTaskStart는 250ms의 주기로 세 개의 태스크보다 우선순위가 높으므로 먼저 실행된다.

...

static const int THRESHOLD = 500000;

static int Task1_Count = 0;
static int Task2_Count = 0;
static int Task3_Count = 0;

...

static void AppTask1(void *p_arg) {
    while (DEF_TRUE) {
        ++Task1_Count;
        if(Task1_Count == THRESHOLD) {
            Task1_Count = 0;
            MyLedToggle(1);
        }
        USART_SendData(USART1, (u16)'*');
    }
}

static void AppTask2(void *p_arg) {
    while (DEF_TRUE) {
        ++Task2_Count;
        if(Task2_Count == THRESHOLD) {
            Task2_Count = 0;
            MyLedToggle(2);
        }
        USART_SendData(USART1, (u16)'@');
    }
}

static void AppTask3(void *p_arg) {
    while (DEF_TRUE) {
        ++Task3_Count;
        if(Task3_Count == THRESHOLD) {
            Task3_Count = 0;
            MyLedToggle(3);
        }
        
        USART_SendData(USART1, (u16)'#');
    }
}

USART_SendData는 첫 번째 인자로 전달된 USART1포트를 통해 두 번째 인자로 전달된 문자를 전송하게 된다.

각각의 Task는 THRESHOLD에 도달할 때마다 해당하는 LED를 Toggle하게 된다. 동작 모습은 아래와 같다.

 

 

 

Putty 관련 설정은 아래를 참조하자.

Serial line은 COM3로 설정되어 있는데 이는 장치관리자에 들어가서 U-ART의 번호를 확인하면 된다.