RTOS #8

홍태준·2026년 1월 22일

RTOS

목록 보기
8/20
post-thumbnail

Week 2 Day 3: Task 상태와 전이

학습 목표

  • FreeRTOS의 4가지 Task 상태를 이해한다
  • Task 상태 간 전이 조건과 흐름을 파악한다
  • vTaskDelay()와 vTaskDelayUntil()의 차이를 이해하고 적절히 사용할 수 있다
  • 주기적 Task를 정확하게 구현할 수 있다
  • 상태 전이 다이어그램을 읽고 분석할 수 있다

1. Task 상태의 이해

1.1 4가지 기본 상태

FreeRTOS의 모든 Task는 다음 4가지 상태 중 하나에 있습니다.

상태영문설명
실행RunningCPU에서 현재 실행 중인 상태
준비Ready실행 가능하지만 대기 중인 상태
대기Blocked이벤트를 기다리는 상태
정지Suspended명시적으로 정지된 상태

중요: 멀티코어가 아닌 단일 코어 시스템에서는 오직 하나의 Task만 Running 상태가 될 수 있습니다.

tip:
MCU에서 테스크는 CS에서 얘기하는 프로세스(Process)보단 쓰레드(Thread)에 가깝습니다
MCU는 단일 주소 공간에 대해 각각의 테스크가 stack 영역을 할당받아 실행 흐름을 유지합니다.
이 때 인터럽트가 발생하면 현재 실행 중인 테스크와는 별개의 스택을 사용해 긴급한 처리를 수행합니다.

1.2 각 상태의 상세 설명

1) Running (실행) 상태

// Running 상태: CPU에서 현재 이 코드를 실행 중
void vMyTask(void *pvParameters) {
    while(1) {
        // 이 순간 이 Task는 Running 상태
        printf("Task 실행 중!\n");
        
        // 다른 상태로 전이될 때까지 계속 실행
        process_data();
    }
}

특징:

  • CPU를 점유하고 있는 상태
  • 단일 코어에서는 1개 Task만 가능
  • 가장 높은 우선순위의 Ready Task가 선택됨

2) Ready (준비) 상태

void vReadyTask(void *pvParameters) {
    while(1) {
        // 이 Task는 Ready 상태에서 대기 중
        // 더 높은 우선순위 Task가 실행 중이라면
        // 이 Task는 Running으로 전이되기를 기다림
        
        printf("준비 완료, 실행 대기 중...\n");
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

특징:

  • 실행 가능하지만 CPU를 기다리는 상태
  • 스케줄러의 Ready List에 등록됨
  • 우선순위에 따라 실행 순서가 결정됨

3) Blocked (대기) 상태

void vBlockedTask(void *pvParameters) {
    TickType_t xLastWakeTime = xTaskGetTickCount();
    
    while(1) {
        printf("작업 수행\n");
        
        // vTaskDelay() 호출 시 Blocked 상태로 전이
        // 지정된 시간 동안 대기
        vTaskDelay(pdMS_TO_TICKS(1000));  // 1초 동안 Blocked
        
        // 1초 후 자동으로 Ready 상태로 전이
    }
}

특징:

  • 특정 이벤트나 시간을 기다리는 상태
  • CPU를 사용하지 않음 (전력 절약)
  • 대기 조건이 만족되면 자동으로 Ready로 전이

Blocked 상태로 전이되는 경우:

  • vTaskDelay() / vTaskDelayUntil() 호출
  • Queue 대기 (xQueueReceive() 타임아웃)
  • Semaphore 대기 (xSemaphoreTake() 타임아웃)
  • Event Group 대기
  • Notification 대기

4) Suspended (정지) 상태

TaskHandle_t xTaskHandle;

void vSuspendableTask(void *pvParameters) {
    while(1) {
        printf("Task 실행 중\n");
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

void vControlTask(void *pvParameters) {
    vTaskDelay(pdMS_TO_TICKS(3000));
    
    // 다른 Task를 Suspended 상태로 전이
    printf("Task 정지\n");
    vTaskSuspend(xTaskHandle);
    
    vTaskDelay(pdMS_TO_TICKS(3000));
    
    // Suspended 상태에서 Ready 상태로 복구
    printf("Task 재개\n");
    vTaskResume(xTaskHandle);
    
    vTaskDelete(NULL);
}

int main(void) {
    xTaskCreate(vSuspendableTask, "Suspendable", 128, NULL, 2, &xTaskHandle);
    xTaskCreate(vControlTask, "Control", 128, NULL, 3, NULL);
    
    vTaskStartScheduler();
    while(1);
}

특징:

  • 명시적으로 정지된 상태
  • vTaskSuspend() 호출 시 전이
  • vTaskResume() 호출 전까지 복구 불가
  • 스케줄러가 고려하지 않음

2. 상태 전이 다이어그램

2.1 전체 상태 전이 흐름

                    생성
                     ↓
            ┌─────────────────┐
            │     Ready       │ ←─────────────┐
            │   (준비 완료)    │              │
            └─────────────────┘               │
                 ↓         ↑                 │
         스케줄러 선택    선점됨               │
                 ↓         ↑                 │
            ┌─────────────────┐               │
            │    Running      │              │
            │   (실행 중)      │              │
            └─────────────────┘               │
                 ↓         ↑                 │
         Delay/대기    이벤트 발생             │
                 ↓         ↑                 │
            ┌─────────────────┐                │
            │    Blocked      │              │
            │   (대기 중)      │              │
            └─────────────────┘                │
                                             │
            ┌─────────────────┐               │
            │   Suspended     │ ──────────────┘
            │   (정지됨)       │   vTaskResume()
            └─────────────────┘
                 ↑
         vTaskSuspend()

2.2 주요 전이 조건

Running → Ready

void vHighPriorityTask(void *pvParameters) {
    while(1) {
        // 이 Task가 Ready되면 낮은 우선순위 Task를
        // Running → Ready로 전이시킴 (선점)
        printf("높은 우선순위 Task 실행\n");
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

void vLowPriorityTask(void *pvParameters) {
    while(1) {
        // Running 상태에서 실행 중
        printf("낮은 우선순위 Task 실행\n");
        
        // 높은 우선순위 Task가 Ready되면
        // 이 Task는 즉시 Ready로 전이됨
        for(volatile int i = 0; i < 100000; i++);
    }
}

Running → Blocked

void vTaskWithDelay(void *pvParameters) {
    while(1) {
        printf("Running: 작업 수행\n");
        process_data();
        
        // vTaskDelay() 호출 순간:
        // Running → Blocked로 전이
        vTaskDelay(pdMS_TO_TICKS(1000));
        
        // 1초 후 자동으로 Blocked → Ready
        // 우선순위에 따라 Ready → Running
    }
}

Blocked → Ready

void vTimerTask(void *pvParameters) {
    while(1) {
        printf("타이머 시작 (Running)\n");
        
        // Blocked 상태로 전이
        vTaskDelay(pdMS_TO_TICKS(5000));
        
        // 5초 후:
        // 1) Blocked → Ready 자동 전이
        // 2) 스케줄러가 선택하면 Ready → Running
        printf("타이머 완료 (다시 Running)\n");
    }
}

Any State → Suspended

void vAnyTask(void *pvParameters) {
    while(1) {
        printf("Task 실행 중\n");
        
        // 어떤 상태에서든 Suspended로 전이 가능
        // 다른 Task가 vTaskSuspend(thisHandle) 호출 시
        
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

2.3 상태 전이 실습 코드

C 버전:

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

TaskHandle_t xTask1Handle, xTask2Handle;

void vTask1(void *pvParameters) {
    while(1) {
        printf("[Task1] Running: 작업 수행 중...\n");
        
        // 1초 동안 Blocked 상태
        printf("[Task1] Running → Blocked (1초 대기)\n");
        vTaskDelay(pdMS_TO_TICKS(1000));
        
        // Blocked → Ready → Running
        printf("[Task1] Blocked → Ready → Running\n");
        
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void vTask2(void *pvParameters) {
    vTaskDelay(pdMS_TO_TICKS(3000));
    
    while(1) {
        printf("\n[Task2] Task1을 Suspended 상태로 전이\n");
        vTaskSuspend(xTask1Handle);
        
        vTaskDelay(pdMS_TO_TICKS(3000));
        
        printf("[Task2] Task1을 Suspended → Ready로 복구\n");
        vTaskResume(xTask1Handle);
        
        vTaskDelay(pdMS_TO_TICKS(3000));
    }
}

int main(void) {
    xTaskCreate(vTask1, "Task1", 128, NULL, 2, &xTask1Handle);
    xTaskCreate(vTask2, "Task2", 128, NULL, 3, &xTask2Handle);
    
    vTaskStartScheduler();
    while(1);
}

C++ 버전:

#include "FreeRTOS.h"
#include "task.h"
#include <iostream>

class Task {
protected:
    TaskHandle_t taskHandle;
    const char* taskName;
    uint16_t stackSize;
    UBaseType_t priority;
    
    virtual void run() = 0;
    
    static void taskEntry(void* pvParameters) {
        Task* task = static_cast<Task*>(pvParameters);
        task->run();
    }
    
public:
    Task(const char* name, uint16_t stack, UBaseType_t prio)
        : taskName(name), stackSize(stack), priority(prio), taskHandle(nullptr) {}
    
    void create() {
        xTaskCreate(taskEntry, taskName, stackSize, this, priority, &taskHandle);
    }
    
    void suspend() { vTaskSuspend(taskHandle); }
    void resume() { vTaskResume(taskHandle); }
    
    TaskHandle_t getHandle() const { return taskHandle; }
    
    virtual ~Task() {}
};

class StateTransitionTask : public Task {
public:
    StateTransitionTask() : Task("StateTask", 128, 2) {}
    
    void run() override {
        while(1) {
            std::cout << "[StateTask] Running: 작업 수행 중..." << std::endl;
            
            std::cout << "[StateTask] Running → Blocked (1초 대기)" << std::endl;
            vTaskDelay(pdMS_TO_TICKS(1000));
            
            std::cout << "[StateTask] Blocked → Ready → Running" << std::endl;
            vTaskDelay(pdMS_TO_TICKS(1000));
        }
    }
};

class ControlTask : public Task {
private:
    StateTransitionTask& target;
    
public:
    ControlTask(StateTransitionTask& t) : Task("Control", 128, 3), target(t) {}
    
    void run() override {
        vTaskDelay(pdMS_TO_TICKS(3000));
        
        while(1) {
            std::cout << "\n[Control] Task를 Suspended 상태로 전이" << std::endl;
            target.suspend();
            
            vTaskDelay(pdMS_TO_TICKS(3000));
            
            std::cout << "[Control] Task를 Suspended → Ready로 복구" << std::endl;
            target.resume();
            
            vTaskDelay(pdMS_TO_TICKS(3000));
        }
    }
};

int main(void) {
    StateTransitionTask stateTask;
    ControlTask controlTask(stateTask);
    
    stateTask.create();
    controlTask.create();
    
    vTaskStartScheduler();
    while(1);
}

3. vTaskDelay()와 vTaskDelayUntil() 차이

3.1 vTaskDelay() - 상대적 지연

함수 원형:

void vTaskDelay(const TickType_t xTicksToDelay);

동작 방식:

  • 현재 시점부터 지정된 시간만큼 대기
  • 상대적인 시간 지연
  • 실행 시간이 누적되어 주기가 틀어질 수 있음

사용 예제:

void vDelayTask(void *pvParameters) {
    while(1) {
        printf("Task 시작: %lu ms\n", xTaskGetTickCount());
        
        // 작업 수행 (예: 50ms 소요)
        process_data();  // 50ms
        
        // 1000ms 대기 (현재 시점부터)
        vTaskDelay(pdMS_TO_TICKS(1000));
        
        // 실제 주기 = 작업시간(50ms) + 대기시간(1000ms) = 1050ms
    }
}

문제점 - 주기 누적 오차:

void vDriftingTask(void *pvParameters) {
    while(1) {
        printf("Tick: %lu\n", xTaskGetTickCount());
        
        // 목표: 1000ms 주기
        expensive_operation();  // 실행 시간이 가변적 (10~100ms)
        
        vTaskDelay(pdMS_TO_TICKS(1000));
        
        // 실제 주기:
        // 1010ms, 1050ms, 1020ms, ... (불규칙)
    }
}

타이밍 다이어그램:

목표 주기: 1000ms
실제:
0ms     작업(50ms)         Delay(1000ms)         1050ms
|-------████--------------|-------...
        ↑                  ↑
      작업 시간          다음 시작
      
누적 오차: +50ms, +100ms, +150ms, ...

3.2 vTaskDelayUntil() - 절대적 지연

함수 원형:

BaseType_t vTaskDelayUntil(TickType_t *pxPreviousWakeTime, 
                           const TickType_t xTimeIncrement);

동작 방식:

  • 절대 시간 기준으로 다음 깨어날 시간 계산
  • 작업 시간을 자동으로 보상
  • 정확한 주기 유지 (작업 시간 포함)

사용 예제:

void vPeriodicTask(void *pvParameters) {
    TickType_t xLastWakeTime;
    const TickType_t xPeriod = pdMS_TO_TICKS(1000);  // 1000ms 주기
    
    // 현재 Tick을 초기 기준점으로 설정
    xLastWakeTime = xTaskGetTickCount();
    
    while(1) {
        printf("정확한 Tick: %lu\n", xTaskGetTickCount());
        
        // 작업 수행 (실행 시간 가변적)
        process_data();  // 10~100ms
        
        // 다음 깨어날 시간 = xLastWakeTime + 1000ms
        // 작업 시간이 자동으로 보상됨
        vTaskDelayUntil(&xLastWakeTime, xPeriod);
        
        // 정확히 1000ms 주기 유지
    }
}

동작 원리:

// vTaskDelayUntil() 내부 동작 (의사 코드)
void vTaskDelayUntil(TickType_t *pxPreviousWakeTime, 
                     const TickType_t xTimeIncrement) {
    TickType_t xTimeToWake;
    
    // 다음 깨어날 절대 시간 계산
    xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;
    
    // 현재 시간과 비교하여 필요한 대기 시간 계산
    TickType_t xCurrentTime = xTaskGetTickCount();
    TickType_t xTicksToWait = xTimeToWake - xCurrentTime;
    
    // 대기
    vTaskDelay(xTicksToWait);
    
    // 다음 주기를 위해 기준 시간 업데이트
    *pxPreviousWakeTime = xTimeToWake;
}

타이밍 다이어그램:

목표 주기: 1000ms (정확히 유지)

0ms     작업(50ms)    Delay(950ms)      1000ms    작업(80ms)   Delay(920ms)     2000ms
|-------████-----------|-------...-------|--------████---------|-------...-------|
        ↑              ↑                          ↑             ↑
      작업 시간      다음 시작                  작업 시간    다음 시작
      (자동 보상)                              (자동 보상)

누적 오차: 0ms (항상 정확)

3.3 비교 실습 코드

C 버전:

#include "FreeRTOS.h"
#include "task.h"
#include <stdio.h>
#include <stdlib.h>

// 가변 작업 시간 시뮬레이션
void variable_work(void) {
    TickType_t workTime = (rand() % 50) + 10;  // 10~60ms
    vTaskDelay(pdMS_TO_TICKS(workTime));
}

// vTaskDelay() 사용 (상대적 지연)
void vDelayTask(void *pvParameters) {
    int cycle = 0;
    TickType_t startTime = xTaskGetTickCount();
    
    while(cycle < 10) {
        TickType_t currentTime = xTaskGetTickCount();
        printf("[Delay] Cycle %d: %lu ms (오차: %ld ms)\n", 
               cycle, 
               currentTime,
               (long)(currentTime - startTime - cycle * 1000));
        
        variable_work();  // 가변 작업 시간
        
        vTaskDelay(pdMS_TO_TICKS(1000));  // 1초 대기
        cycle++;
    }
    
    vTaskDelete(NULL);
}

// vTaskDelayUntil() 사용 (절대적 지연)
void vDelayUntilTask(void *pvParameters) {
    TickType_t xLastWakeTime;
    const TickType_t xPeriod = pdMS_TO_TICKS(1000);
    int cycle = 0;
    TickType_t startTime = xTaskGetTickCount();
    
    xLastWakeTime = xTaskGetTickCount();
    
    while(cycle < 10) {
        TickType_t currentTime = xTaskGetTickCount();
        printf("[DelayUntil] Cycle %d: %lu ms (오차: %ld ms)\n", 
               cycle, 
               currentTime,
               (long)(currentTime - startTime - cycle * 1000));
        
        variable_work();  // 가변 작업 시간
        
        vTaskDelayUntil(&xLastWakeTime, xPeriod);  // 정확한 주기
        cycle++;
    }
    
    vTaskDelete(NULL);
}

int main(void) {
    printf("=== vTaskDelay() 테스트 ===\n");
    xTaskCreate(vDelayTask, "Delay", 256, NULL, 2, NULL);
    
    vTaskDelay(pdMS_TO_TICKS(12000));  // 첫 번째 Task 완료 대기
    
    printf("\n=== vTaskDelayUntil() 테스트 ===\n");
    xTaskCreate(vDelayUntilTask, "DelayUntil", 256, NULL, 2, NULL);
    
    vTaskStartScheduler();
    while(1);
}

예상 출력:

=== vTaskDelay() 테스트 ===
[Delay] Cycle 0: 0 ms (오차: 0 ms)
[Delay] Cycle 1: 1045 ms (오차: 45 ms)
[Delay] Cycle 2: 2110 ms (오차: 110 ms)
[Delay] Cycle 3: 3152 ms (오차: 152 ms)
...

=== vTaskDelayUntil() 테스트 ===
[DelayUntil] Cycle 0: 12000 ms (오차: 0 ms)
[DelayUntil] Cycle 1: 13000 ms (오차: 0 ms)
[DelayUntil] Cycle 2: 14000 ms (오차: 0 ms)
[DelayUntil] Cycle 3: 15000 ms (오차: 0 ms)
...

C++ 버전:

#include "FreeRTOS.h"
#include "task.h"
#include <iostream>
#include <cstdlib>

class Task {
protected:
    TaskHandle_t taskHandle;
    const char* taskName;
    uint16_t stackSize;
    UBaseType_t priority;
    
    virtual void run() = 0;
    
    static void taskEntry(void* pvParameters) {
        Task* task = static_cast<Task*>(pvParameters);
        task->run();
        vTaskDelete(nullptr);
    }
    
public:
    Task(const char* name, uint16_t stack, UBaseType_t prio)
        : taskName(name), stackSize(stack), priority(prio), taskHandle(nullptr) {}
    
    void create() {
        xTaskCreate(taskEntry, taskName, stackSize, this, priority, &taskHandle);
    }
    
    virtual ~Task() {}
};

void variableWork() {
    TickType_t workTime = (rand() % 50) + 10;
    vTaskDelay(pdMS_TO_TICKS(workTime));
}

class DelayTestTask : public Task {
public:
    DelayTestTask() : Task("DelayTest", 256, 2) {}
    
    void run() override {
        int cycle = 0;
        TickType_t startTime = xTaskGetTickCount();
        
        while(cycle < 10) {
            TickType_t currentTime = xTaskGetTickCount();
            std::cout << "[Delay] Cycle " << cycle << ": " << currentTime 
                      << " ms (오차: " << (currentTime - startTime - cycle * 1000) 
                      << " ms)" << std::endl;
            
            variableWork();
            vTaskDelay(pdMS_TO_TICKS(1000));
            cycle++;
        }
    }
};

class DelayUntilTestTask : public Task {
public:
    DelayUntilTestTask() : Task("DelayUntilTest", 256, 2) {}
    
    void run() override {
        TickType_t xLastWakeTime = xTaskGetTickCount();
        const TickType_t xPeriod = pdMS_TO_TICKS(1000);
        int cycle = 0;
        TickType_t startTime = xTaskGetTickCount();
        
        while(cycle < 10) {
            TickType_t currentTime = xTaskGetTickCount();
            std::cout << "[DelayUntil] Cycle " << cycle << ": " << currentTime 
                      << " ms (오차: " << (currentTime - startTime - cycle * 1000) 
                      << " ms)" << std::endl;
            
            variableWork();
            vTaskDelayUntil(&xLastWakeTime, xPeriod);
            cycle++;
        }
    }
};

int main(void) {
    std::cout << "=== vTaskDelay() 테스트 ===" << std::endl;
    DelayTestTask delayTask;
    delayTask.create();
    
    vTaskDelay(pdMS_TO_TICKS(12000));
    
    std::cout << "\n=== vTaskDelayUntil() 테스트 ===" << std::endl;
    DelayUntilTestTask delayUntilTask;
    delayUntilTask.create();
    
    vTaskStartScheduler();
    while(1);
}

3.4 선택 가이드라인

상황권장 함수이유
주기적 센서 읽기vTaskDelayUntil()정확한 샘플링 주기 필요
주기적 제어 신호vTaskDelayUntil()제어 주기 정확도 중요
단순 대기vTaskDelay()정확한 주기 불필요
타임아웃vTaskDelay()상대적 시간만 필요
데이터 수집vTaskDelayUntil()시간 동기화 필요
LED 깜빡임vTaskDelay()정확한 주기 불필요

4. 실습: 주기적 Task 구현

실습 1: 센서 데이터 수집 (vTaskDelayUntil)

목표: 정확한 100ms 주기로 센서 값 읽기

C 버전:

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

// 센서 값 시뮬레이션
float read_sensor(void) {
    return (float)(xTaskGetTickCount() % 100) / 10.0f;
}

void vSensorTask(void *pvParameters) {
    TickType_t xLastWakeTime;
    const TickType_t xPeriod = pdMS_TO_TICKS(100);  // 100ms 주기
    int sampleCount = 0;
    
    // 초기 시간 설정
    xLastWakeTime = xTaskGetTickCount();
    
    printf("센서 Task 시작: 100ms 주기\n");
    
    while(1) {
        // 센서 읽기
        float sensorValue = read_sensor();
        TickType_t currentTime = xTaskGetTickCount();
        
        printf("[샘플 %d] 시간: %lu ms, 센서 값: %.1f\n", 
               sampleCount, currentTime, sensorValue);
        
        sampleCount++;
        
        // 정확히 100ms마다 깨어남
        vTaskDelayUntil(&xLastWakeTime, xPeriod);
    }
}

int main(void) {
    xTaskCreate(vSensorTask, "Sensor", 256, NULL, 2, NULL);
    
    vTaskStartScheduler();
    while(1);
}

예상 출력:

센서 Task 시작: 100ms 주기
[샘플 0] 시간: 0 ms, 센서 값: 0.0
[샘플 1] 시간: 100 ms, 센서 값: 0.0
[샘플 2] 시간: 200 ms, 센서 값: 0.0
[샘플 3] 시간: 300 ms, 센서 값: 0.0
...

실습 2: 다중 주기 Task

목표: 서로 다른 주기를 가진 여러 Task 동시 실행

C 버전:

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

// 빠른 주기 Task (10ms)
void vFastTask(void *pvParameters) {
    TickType_t xLastWakeTime = xTaskGetTickCount();
    const TickType_t xPeriod = pdMS_TO_TICKS(10);
    
    while(1) {
        printf("[Fast-10ms] Tick: %lu\n", xTaskGetTickCount());
        vTaskDelayUntil(&xLastWakeTime, xPeriod);
    }
}

// 중간 주기 Task (50ms)
void vMediumTask(void *pvParameters) {
    TickType_t xLastWakeTime = xTaskGetTickCount();
    const TickType_t xPeriod = pdMS_TO_TICKS(50);
    
    while(1) {
        printf("  [Medium-50ms] Tick: %lu\n", xTaskGetTickCount());
        vTaskDelayUntil(&xLastWakeTime, xPeriod);
    }
}

// 느린 주기 Task (100ms)
void vSlowTask(void *pvParameters) {
    TickType_t xLastWakeTime = xTaskGetTickCount();
    const TickType_t xPeriod = pdMS_TO_TICKS(100);
    
    while(1) {
        printf("    [Slow-100ms] Tick: %lu\n", xTaskGetTickCount());
        vTaskDelayUntil(&xLastWakeTime, xPeriod);
    }
}

int main(void) {
    xTaskCreate(vFastTask, "Fast", 256, NULL, 3, NULL);
    xTaskCreate(vMediumTask, "Medium", 256, NULL, 2, NULL);
    xTaskCreate(vSlowTask, "Slow", 256, NULL, 1, NULL);
    
    vTaskStartScheduler();
    while(1);
}

C++ 버전:

#include "FreeRTOS.h"
#include "task.h"
#include <iostream>

class Task {
protected:
    TaskHandle_t taskHandle;
    const char* taskName;
    uint16_t stackSize;
    UBaseType_t priority;
    
    virtual void run() = 0;
    
    static void taskEntry(void* pvParameters) {
        Task* task = static_cast<Task*>(pvParameters);
        task->run();
    }
    
public:
    Task(const char* name, uint16_t stack, UBaseType_t prio)
        : taskName(name), stackSize(stack), priority(prio), taskHandle(nullptr) {}
    
    void create() {
        xTaskCreate(taskEntry, taskName, stackSize, this, priority, &taskHandle);
    }
    
    virtual ~Task() {}
};

class PeriodicTask : public Task {
protected:
    TickType_t period;
    const char* label;
    
public:
    PeriodicTask(const char* name, const char* lbl, uint16_t periodMs, UBaseType_t prio)
        : Task(name, 256, prio), period(pdMS_TO_TICKS(periodMs)), label(lbl) {}
    
    void run() override {
        TickType_t xLastWakeTime = xTaskGetTickCount();
        
        while(1) {
            std::cout << label << " Tick: " << xTaskGetTickCount() << std::endl;
            vTaskDelayUntil(&xLastWakeTime, period);
        }
    }
};

int main(void) {
    PeriodicTask fastTask("Fast", "[Fast-10ms]", 10, 3);
    PeriodicTask mediumTask("Medium", "  [Medium-50ms]", 50, 2);
    PeriodicTask slowTask("Slow", "    [Slow-100ms]", 100, 1);
    
    fastTask.create();
    mediumTask.create();
    slowTask.create();
    
    vTaskStartScheduler();
    while(1);
}

실습 3: 실시간 데이터 로거

목표: 정확한 주기로 데이터를 수집하고 기록

C 버전:

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

#define MAX_SAMPLES 100

typedef struct {
    TickType_t timestamp;
    float value;
} DataSample;

DataSample dataBuffer[MAX_SAMPLES];
int sampleIndex = 0;

// 데이터 수집 Task (정확한 50ms 주기)
void vDataCollectorTask(void *pvParameters) {
    TickType_t xLastWakeTime = xTaskGetTickCount();
    const TickType_t xPeriod = pdMS_TO_TICKS(50);
    
    while(1) {
        if(sampleIndex < MAX_SAMPLES) {
            // 데이터 수집
            dataBuffer[sampleIndex].timestamp = xTaskGetTickCount();
            dataBuffer[sampleIndex].value = read_sensor();
            
            printf("[수집] 샘플 %d: 시간=%lu, 값=%.2f\n",
                   sampleIndex,
                   dataBuffer[sampleIndex].timestamp,
                   dataBuffer[sampleIndex].value);
            
            sampleIndex++;
        } else {
            // 수집 완료
            printf("\n데이터 수집 완료 (총 %d 샘플)\n", MAX_SAMPLES);
            vTaskDelete(NULL);
        }
        
        vTaskDelayUntil(&xLastWakeTime, xPeriod);
    }
}

// 데이터 분석 Task (1초마다)
void vDataAnalyzerTask(void *pvParameters) {
    TickType_t xLastWakeTime = xTaskGetTickCount();
    const TickType_t xPeriod = pdMS_TO_TICKS(1000);
    
    while(1) {
        if(sampleIndex > 0) {
            // 평균 계산
            float sum = 0;
            for(int i = 0; i < sampleIndex; i++) {
                sum += dataBuffer[i].value;
            }
            float average = sum / sampleIndex;
            
            printf("[분석] 현재까지 평균: %.2f (샘플 수: %d)\n", 
                   average, sampleIndex);
        }
        
        vTaskDelayUntil(&xLastWakeTime, xPeriod);
    }
}

int main(void) {
    xTaskCreate(vDataCollectorTask, "Collector", 512, NULL, 3, NULL);
    xTaskCreate(vDataAnalyzerTask, "Analyzer", 512, NULL, 2, NULL);
    
    vTaskStartScheduler();
    while(1);
}

C++ 버전:

#include "FreeRTOS.h"
#include "task.h"
#include <iostream>
#include <vector>

struct DataSample {
    TickType_t timestamp;
    float value;
};

class DataLogger {
private:
    std::vector<DataSample> samples;
    size_t maxSamples;
    
public:
    DataLogger(size_t max) : maxSamples(max) {
        samples.reserve(max);
    }
    
    bool addSample(TickType_t time, float value) {
        if(samples.size() < maxSamples) {
            samples.push_back({time, value});
            return true;
        }
        return false;
    }
    
    size_t getSampleCount() const { return samples.size(); }
    
    float getAverage() const {
        if(samples.empty()) return 0.0f;
        float sum = 0;
        for(const auto& s : samples) {
            sum += s.value;
        }
        return sum / samples.size();
    }
    
    const DataSample& getSample(size_t index) const {
        return samples[index];
    }
};

class Task {
protected:
    TaskHandle_t taskHandle;
    const char* taskName;
    uint16_t stackSize;
    UBaseType_t priority;
    
    virtual void run() = 0;
    
    static void taskEntry(void* pvParameters) {
        Task* task = static_cast<Task*>(pvParameters);
        task->run();
    }
    
public:
    Task(const char* name, uint16_t stack, UBaseType_t prio)
        : taskName(name), stackSize(stack), priority(prio), taskHandle(nullptr) {}
    
    void create() {
        xTaskCreate(taskEntry, taskName, stackSize, this, priority, &taskHandle);
    }
    
    virtual ~Task() {}
};

class DataCollectorTask : public Task {
private:
    DataLogger& logger;
    
public:
    DataCollectorTask(DataLogger& log) 
        : Task("Collector", 512, 3), logger(log) {}
    
    void run() override {
        TickType_t xLastWakeTime = xTaskGetTickCount();
        const TickType_t xPeriod = pdMS_TO_TICKS(50);
        
        while(1) {
            TickType_t currentTime = xTaskGetTickCount();
            float value = read_sensor();
            
            if(logger.addSample(currentTime, value)) {
                std::cout << "[수집] 샘플 " << logger.getSampleCount() - 1
                          << ": 시간=" << currentTime
                          << ", 값=" << value << std::endl;
            } else {
                std::cout << "\n데이터 수집 완료" << std::endl;
                vTaskDelete(taskHandle);
            }
            
            vTaskDelayUntil(&xLastWakeTime, xPeriod);
        }
    }
};

class DataAnalyzerTask : public Task {
private:
    DataLogger& logger;
    
public:
    DataAnalyzerTask(DataLogger& log)
        : Task("Analyzer", 512, 2), logger(log) {}
    
    void run() override {
        TickType_t xLastWakeTime = xTaskGetTickCount();
        const TickType_t xPeriod = pdMS_TO_TICKS(1000);
        
        while(1) {
            if(logger.getSampleCount() > 0) {
                std::cout << "[분석] 현재까지 평균: " << logger.getAverage()
                          << " (샘플 수: " << logger.getSampleCount() << ")" 
                          << std::endl;
            }
            
            vTaskDelayUntil(&xLastWakeTime, xPeriod);
        }
    }
};

int main(void) {
    DataLogger logger(100);
    
    DataCollectorTask collector(logger);
    DataAnalyzerTask analyzer(logger);
    
    collector.create();
    analyzer.create();
    
    vTaskStartScheduler();
    while(1);
}

5. 고급 주제: 실행 시간 측정

5.1 Task 실행 시간 모니터링

C 버전:

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

void vMonitoredTask(void *pvParameters) {
    TickType_t xLastWakeTime = xTaskGetTickCount();
    const TickType_t xPeriod = pdMS_TO_TICKS(100);
    
    while(1) {
        TickType_t startTime = xTaskGetTickCount();
        
        // 작업 수행
        complex_calculation();
        
        TickType_t endTime = xTaskGetTickCount();
        TickType_t executionTime = endTime - startTime;
        
        printf("[모니터] 실행 시간: %lu ms (주기: 100ms)\n", executionTime);
        
        if(executionTime > xPeriod) {
            printf("경고: 실행 시간이 주기를 초과했습니다!\n");
        }
        
        vTaskDelayUntil(&xLastWakeTime, xPeriod);
    }
}

5.2 주기 위반 감지

C 버전:

void vDeadlineMonitorTask(void *pvParameters) {
    TickType_t xLastWakeTime = xTaskGetTickCount();
    const TickType_t xPeriod = pdMS_TO_TICKS(100);
    TickType_t xMaxExecutionTime = pdMS_TO_TICKS(80);  // 80ms 제한
    int missedDeadlines = 0;
    
    while(1) {
        TickType_t startTime = xTaskGetTickCount();
        
        // 작업
        process_data();
        
        TickType_t executionTime = xTaskGetTickCount() - startTime;
        
        if(executionTime > xMaxExecutionTime) {
            missedDeadlines++;
            printf("⚠️  데드라인 위반! 실행: %lu ms (제한: %lu ms)\n",
                   executionTime, xMaxExecutionTime);
            printf("   누적 위반 횟수: %d\n", missedDeadlines);
        }
        
        vTaskDelayUntil(&xLastWakeTime, xPeriod);
    }
}

C vs C++ 비교 정리

주기적 Task 구현 비교

측면C 방식C++ 방식
시간 관리지역 변수로 xLastWakeTime 관리멤버 변수로 캡슐화
주기 설정함수 내부에서 직접 설정생성자 파라미터로 설정
재사용성함수 복사 필요상속으로 재사용
데이터 공유전역 변수 또는 구조체클래스 멤버로 안전하게 공유
코드 구조절차적, 평면적계층적, 모듈화

실전 선택 가이드

C 방식을 선택할 때:

  • 단순한 주기적 작업
  • 리소스 제약이 심한 환경
  • FreeRTOS 공식 예제 참고

C++ 방식을 선택할 때:

  • 복잡한 상태 관리
  • 여러 주기의 Task 체계적 관리
  • 코드 재사용성 중요

학습 정리

오늘 배운 핵심 내용

  1. 4가지 Task 상태

    • Running, Ready, Blocked, Suspended
    • 각 상태의 의미와 전이 조건
  2. 상태 전이

    • Running ↔ Ready: 스케줄링, 선점
    • Running → Blocked: Delay, 대기
    • Blocked → Ready: 이벤트 발생
    • Any → Suspended: 명시적 정지
  3. 지연 함수

    • vTaskDelay(): 상대적 지연
    • vTaskDelayUntil(): 절대적 지연 (주기적 작업 권장)
  4. 주기적 Task 구현

    • 정확한 주기 유지의 중요성
    • 실행 시간 모니터링
    • 데드라인 관리

핵심 개념

개념설명
Task 상태Running, Ready, Blocked, Suspended
상태 전이조건에 따른 자동/수동 상태 변경
vTaskDelay()현재 시점부터 상대적 대기
vTaskDelayUntil()절대 시간 기준 주기적 실행
주기 정확도실행 시간 자동 보상

실습 과제

과제 1: 상태 전이 로거

모든 상태 전이를 로깅하는 Task 구현

요구사항:

  • Ready → Running 감지
  • Running → Blocked 기록
  • Suspended 상태 추적
  • 전이 횟수 통계

과제 2: 다중 주기 센서 시스템

3개의 서로 다른 주기를 가진 센서 Task

요구사항:

  • 센서1: 10ms 주기
  • 센서2: 50ms 주기
  • 센서3: 100ms 주기
  • vTaskDelayUntil() 사용
  • 주기 정확도 검증

과제 3: 적응형 주기 Task

시스템 부하에 따라 주기를 동적으로 조정

요구사항:

  • 정상: 100ms 주기
  • 고부하: 200ms 주기
  • 저부하: 50ms 주기
  • 부하 측정 및 주기 자동 조정

profile
당신의 코딩 메이트

0개의 댓글