RTOS #4

홍태준·2026년 1월 14일

RTOS

목록 보기
4/20
post-thumbnail

Week 1 Day 4: Task의 개념

학습 목표

  • Task 함수의 구조와 특징 이해
  • Task 파라미터 전달 방법 학습
  • Task와 일반 함수의 차이점 파악
  • Multi-task 프로그램 작성 및 실행

1. Task 함수의 구조

Task 함수의 기본 형태

Task는 무한 루프를 포함하는 특별한 함수입니다, 기본 구조는 다음과 같습니다.

C 버전:

void vTaskFunction(void *pvParameters) {
    // 초기화 코드 (한 번만 실행)
    // 변수 선언, 하드웨어 설정 등
    int counter = 0;
    
    // 무한 루프 (Task의 핵심)
    while(1) {
        // Task가 수행할 작업
        // 주기적 실행 영역(RTOS 특성상 주로 무한 루프로 작성)
        counter++;
        
        // 주기적으로 다른 Task에게 CPU 양보
        // 정확힌 블로킹(Blocking)/지연(Delay)
        vTaskDelay(pdMS_TO_TICKS(100));
    }
    
    // 여기는 절대 실행되지 않음
    // Task를 종료하려면 vTaskDelete() 사용
}

C++ 버전:

class MyTask : public Task {
private:
    int counter;
    
public:
    MyTask() : Task("MyTask", 128, 1), counter(0) {}
    
    void run() override {
        // 초기화 코드 (한 번만 실행)
        // 멤버 변수는 생성자에서 초기화 가능
        
        // 무한 루프
        while(1) {
            // Task가 수행할 작업
            counter++;
            
            // 주기적으로 다른 Task에게 CPU 양보
            vTaskDelay(pdMS_TO_TICKS(100));
        }
    }
};

Task 함수의 필수 요소

요소설명중요도
void 포인터 파라미터Task에 데이터를 전달하는 방법필수
무한 루프 (while(1))Task는 종료되지 않고 계속 실행필수
vTaskDelay()다른 Task에게 CPU 양보, 없으면 독점권장
초기화 코드루프 전에 실행되는 설정 코드선택

Task가 무한 루프를 사용하는 이유

// 잘못된 예 - Task가 종료됨
void vBadTask(void *pvParameters) {
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
    // Task 함수가 return하면 예측 불가능한 동작 발생
    // 정확힌 1회 사용 후 종료되지만, 시스템상에서 크래시(Crash)가 발생하거나 비정상 동작을 할 수 있음
    // FreeRTOS는 Task가 종료될 것을 기대하지 않음
}

// 올바른 예 - 무한 루프
void vGoodTask(void *pvParameters) {
    while(1) {
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
        vTaskDelay(pdMS_TO_TICKS(500));
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

// Task를 종료해야 한다면
void vTaskWithEnd(void *pvParameters) {
    int workCount = 0;
    
    while(workCount < 10) {
        // 작업 수행
        workCount++;
        vTaskDelay(pdMS_TO_TICKS(100));
    }
    
    // Task를 안전하게 삭제
    // vTaskDelete(NULL) 호출시 스케쥴러가 해당 Task를 '삭제 대기 목록'으로 옮기고 더 이상 CPU를 할당하지 않음
    vTaskDelete(NULL);  // NULL = 자기 자신
}

2. Task 생성 함수 (xTaskCreate)

xTaskCreate() 상세 분석

xTaskCreate()는 vTaskDelete()와 같이 빌트인 함수입니다. 아래와 같이 파라미터를 전달받아 동작합니다.

C 버전:

BaseType_t xTaskCreate(
    TaskFunction_t pvTaskCode,      // Task 함수 포인터
    const char * const pcName,      // Task 이름 (디버깅용)_읽기 전용
    uint16_t usStackDepth,          // Stack 크기 (words 단위)
    void *pvParameters,             // Task에 전달할 파라미터
    UBaseType_t uxPriority,         // 우선순위 (0 ~ configMAX_PRIORITIES-1)
    TaskHandle_t *pvCreatedTask     // Task 핸들 저장 (NULL 가능)
);

// 사용 예제
// typedef void * TaskHandle_t ;
// Task의 모든 정보를 담고 있는 포인터입니다. Task의 모든 기능을 담고 있는 리모컨 같은 역할을 합니다.

TaskHandle_t xLEDTaskHandle;

int main(void) {
    BaseType_t xReturned; // BaseType_t: 함수의 성공/실패 여부를 반환받을 때 이용할 수 있는 "타입" 입니다
    
    xReturned = xTaskCreate(
        vLEDTask,           // Task 함수
        "LED",              // Task 이름
        128,                // Stack: 128 words = 512 bytes
        NULL,               // 파라미터 없음
        1,                  // 우선순위 1
        &xLEDTaskHandle     // 핸들 저장
    );
    
    if(xReturned == pdPASS) {
        // Task 생성 성공
        vTaskStartScheduler(); // Task 스케쥴러를 시작하는 빌트인 함수입니다.
    } else {
        // Task 생성 실패 (메모리 부족)
        while(1);
    }
}

C++ 버전:

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) {}
    
    bool create() {
        BaseType_t xReturned = xTaskCreate(
            taskEntry,
            taskName,
            stackSize,
            this,           // Task 객체 자신을 파라미터로 전달
            priority,
            &taskHandle
        );
        
        return (xReturned == pdPASS);
    }
    
    TaskHandle_t getHandle() const { return taskHandle; }
    
    virtual ~Task() {}
};

// 사용 예제
class LEDTask : public Task {
public:
    LEDTask() : Task("LED", 128, 1) {}
    
    void run() override {
        while(1) {
            // LED 제어
            vTaskDelay(pdMS_TO_TICKS(500));
        }
    }
};

int main(void) {
    LEDTask ledTask;
    
    if(ledTask.create()) {
        vTaskStartScheduler();
    } else {
        // 생성 실패 처리
        while(1);
    }
}

Stack 크기 결정 방법

스택의 크기 결정 방법을 알기에 앞서, RTOS에선 각 Task가 스택을 할당받아 사용하기 때문에 각 Task별로 스택의 사이즈를 정해줘야합니다(xTaskCreate()의 인자로 스택 사이즈 입력). 여기서 주의할 점은 StackOverflow로, Task 내부에 너무 큰 배열을 지역 변수로 선언하면, 할당된 스택 크기를 넘어서 다른 Task의 영역을 침범할 수 있기 때문에 주의해야합니다.

// Stack 크기 계산 가이드라인
// 스택 메모리의 할당량만 정해주면 FreeRTOS 커널이 스스로 메모리 주소 결정

// 1. 작은 Task (LED 깜빡임, 간단한 센서 읽기)
#define SMALL_TASK_STACK    128     // 512 bytes

// 2. 중간 Task (간단한 계산, 작은 버퍼)
#define MEDIUM_TASK_STACK   256     // 1024 bytes

// 3. 큰 Task (UART 통신, 복잡한 계산)
#define LARGE_TASK_STACK    512     // 2048 bytes

// 4. 매우 큰 Task (파일 처리, 네트워크)
#define XLARGE_TASK_STACK   1024    // 4096 bytes

// Stack 크기에 영향을 주는 요소:
// - 지역 변수 크기
// - 함수 호출 깊이
// - 인터럽트에서 사용하는 Stack
// - printf() 같은 라이브러리 함수 사용

// 예제: Stack 사용량이 많은 Task
void vHeavyTask(void *pvParameters) {
    char buffer[512];       // 512 bytes
    int array[100];         // 400 bytes
    // 함수 호출 오버헤드  // ~100 bytes
    // 총 약 1000 bytes 필요
    // → Stack 크기: 최소 256 words (1024 bytes)
    
    while(1) {
        // 작업 수행
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

3. Task 파라미터 전달

단일 값 전달

C 버전:

// 정수 전달
void vTaskWithInt(void *pvParameters) {
    int value = (int)pvParameters;  // void* → int 변환
    
    while(1) {
        printf("Received value: %d\n", value);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

int main(void) {
    int number = 42;
    
    xTaskCreate(
        vTaskWithInt,
        "IntTask",
        128,
        (void*)number,      // int → void* 변환
        1,
        NULL
    );
    
    vTaskStartScheduler();
    while(1);
}

C++ 버전:

class ParameterTask : public Task {
private:
    int value;
    
public:
    ParameterTask(const char* name, int val)
        : Task(name, 128, 1), value(val) {}
    
    void run() override {
        while(1) {
            printf("Received value: %d\n", value);
            vTaskDelay(pdMS_TO_TICKS(1000));
        }
    }
};

int main(void) {
    ParameterTask task1("Task1", 42);
    ParameterTask task2("Task2", 100);
    
    task1.create();
    task2.create();
    
    vTaskStartScheduler();
    while(1);
}

구조체 전달

C 버전:

// Task 파라미터 구조체 정의
typedef struct {
    uint8_t ledPin;
    uint32_t delayMs;
    const char* message;
} TaskParams_t;

void vConfigurableTask(void *pvParameters) { 
    // 파라미터를 구조체로 변환
    TaskParams_t *params = (TaskParams_t*)pvParameters;
    
    while(1) {
        printf("%s\n", params->message);
        HAL_GPIO_TogglePin(GPIOA, params->ledPin);
        vTaskDelay(pdMS_TO_TICKS(params->delayMs));
    }
}

int main(void) {
    // 구조체는 전역 또는 static으로 선언해야 함
    // (Task가 실행되는 동안 유지되어야 하므로)
    static TaskParams_t task1Params = {
        .ledPin = GPIO_PIN_5,
        .delayMs = 500,
        .message = "Task 1 running"
    };
    
    static TaskParams_t task2Params = {
        .ledPin = GPIO_PIN_6,
        .delayMs = 1000,
        .message = "Task 2 running"
    };
    
    xTaskCreate(vConfigurableTask, "Task1", 128, &task1Params, 1, NULL);
    xTaskCreate(vConfigurableTask, "Task2", 128, &task2Params, 1, NULL);
    
    vTaskStartScheduler();
    while(1);
}

C++ 버전:

struct TaskParams {
    uint8_t ledPin;
    uint32_t delayMs;
    const char* message;
};

class ConfigurableTask : public Task {
private:
    TaskParams params;
    
public:
    ConfigurableTask(const char* name, const TaskParams& p)
        : Task(name, 128, 1), params(p) {}
    
    void run() override {
        while(1) {
            printf("%s\n", params.message);
            HAL_GPIO_TogglePin(GPIOA, params.ledPin);
            vTaskDelay(pdMS_TO_TICKS(params.delayMs));
        }
    }
};

int main(void) {
    TaskParams params1 = {GPIO_PIN_5, 500, "Task 1 running"};
    TaskParams params2 = {GPIO_PIN_6, 1000, "Task 2 running"};
    
    ConfigurableTask task1("Task1", params1);
    ConfigurableTask task2("Task2", params2);
    
    task1.create();
    task2.create();
    
    vTaskStartScheduler();
    while(1);
}

주의사항: 지역 변수 전달 금지

// 잘못된 예 - 절대 하지 말 것!
void wrong_example(void) {
    int localVar = 100;  // 지역 변수
    
    xTaskCreate(
        vTask,
        "Task",
        128,
        &localVar,       // 위험! 함수가 return되면 메모리 해제됨
        1,
        NULL
    );
}

// 올바른 예 1 - static 변수 사용
void correct_example1(void) {
    static int staticVar = 100;  // static 변수
    
    xTaskCreate(
        vTask,
        "Task",
        128,
        &staticVar,      // 안전: 프로그램 종료까지 유지
        1,
        NULL
    );
}

// 올바른 예 2 - 전역 변수 사용
int globalVar = 100;

void correct_example2(void) {
    xTaskCreate(
        vTask,
        "Task",
        128,
        &globalVar,      // 안전: 전역 변수
        1,
        NULL
    );
}

// 올바른 예 3 - 동적 할당 (주의 필요)
void correct_example3(void) {
    int *heapVar = (int*)pvPortMalloc(sizeof(int));
    *heapVar = 100;
    
    xTaskCreate(
        vTask,
        "Task",
        128,
        heapVar,         // 안전: Heap 메모리
        1,
        NULL
    );
    
    // 주의: Task에서 사용이 끝나면 vPortFree() 필요
}

4. Task vs 일반 함수

차이점 비교

특성Task일반 함수
실행 방식독립적, 병렬적 실행순차적 호출
종료무한 루프, 종료하지 않음return으로 종료
Stack독립적인 Stack 공간호출자의 Stack 공유
우선순위우선순위 지정 가능우선순위 개념 없음
스케줄링스케줄러가 관리직접 호출로만 실행
메모리Stack + TCBStack만 사용
Context고유한 Context 보유호출자 Context 공유

비교 예제

C 버전:

// 일반 함수 방식 (Bare-metal)
void blink_led_function(void) {
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
    HAL_Delay(500);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
    HAL_Delay(500);
}

int main_baremetal(void) {
    while(1) {
        blink_led_function();    // 순차적 실행
        read_sensor_function();  // LED가 끝나야 실행됨
        // LED와 센서를 동시에 처리할 수 없음
    }
}

// Task 방식 (RTOS)
void vLEDTask(void *pvParameters) {
    while(1) {
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
        vTaskDelay(pdMS_TO_TICKS(500));
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

void vSensorTask(void *pvParameters) {
    while(1) {
        read_sensor();           // LED Task와 독립적으로 실행
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

int main_rtos(void) {
    xTaskCreate(vLEDTask, "LED", 128, NULL, 1, NULL);
    xTaskCreate(vSensorTask, "Sensor", 128, NULL, 2, NULL);
    vTaskStartScheduler();
    
    // LED와 센서가 동시에 처리됨!
    while(1);
}

C++ 버전:

// 일반 클래스 방식
class LEDController {
public:
    void blink() {
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
        HAL_Delay(500);
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
        HAL_Delay(500);
    }
};

class SensorReader {
public:
    void read() {
        // 센서 읽기
        HAL_Delay(100);
    }
};

int main_baremetal_cpp(void) {
    LEDController led;
    SensorReader sensor;
    
    while(1) {
        led.blink();      // 순차적
        sensor.read();    // 순차적
    }
}

// Task 클래스 방식
class LEDTask : public Task {
public:
    LEDTask() : Task("LED", 128, 1) {}
    
    void run() override {
        while(1) {
            HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
            vTaskDelay(pdMS_TO_TICKS(500));
            HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
            vTaskDelay(pdMS_TO_TICKS(500));
        }
    }
};

class SensorTask : public Task {
public:
    SensorTask() : Task("Sensor", 128, 2) {}
    
    void run() override {
        while(1) {
            // 센서 읽기
            vTaskDelay(pdMS_TO_TICKS(100));
        }
    }
};

int main_rtos_cpp(void) {
    LEDTask ledTask;
    SensorTask sensorTask;
    
    ledTask.create();
    sensorTask.create();
    
    vTaskStartScheduler();
    while(1);
}

5. 첫 Multi-task 프로그램

프로젝트: 3개의 독립적인 LED Task

목표:

  • LED1 (PA5): 500ms 간격으로 깜빡임
  • LED2 (PA6): 1000ms 간격으로 깜빡임
  • LED3 (PA7): 2000ms 간격으로 깜빡임

C 버전:

#include "main.h"
#include "FreeRTOS.h"
#include "task.h"

// LED Task 파라미터 구조체
typedef struct {
    GPIO_TypeDef* port;
    uint16_t pin;
    uint32_t delayMs;
} LEDTaskParams_t;

// 공통 LED Task 함수
void vLEDTask(void *pvParameters) {
    LEDTaskParams_t *params = (LEDTaskParams_t*)pvParameters;
    
    while(1) {
        HAL_GPIO_WritePin(params->port, params->pin, GPIO_PIN_SET);
        vTaskDelay(pdMS_TO_TICKS(params->delayMs));
        
        HAL_GPIO_WritePin(params->port, params->pin, GPIO_PIN_RESET);
        vTaskDelay(pdMS_TO_TICKS(params->delayMs));
    }
}

// Task 파라미터 (static 또는 전역으로 선언)
static LEDTaskParams_t led1Params = {GPIOA, GPIO_PIN_5, 500};
static LEDTaskParams_t led2Params = {GPIOA, GPIO_PIN_6, 1000};
static LEDTaskParams_t led3Params = {GPIOA, GPIO_PIN_7, 2000};

void GPIO_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    GPIO_InitStruct.Pin = GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

int main(void) {
    HAL_Init();
    SystemClock_Config();
    GPIO_Init();
    
    // 3개의 LED Task 생성
    xTaskCreate(vLEDTask, "LED1", 128, &led1Params, 1, NULL);
    xTaskCreate(vLEDTask, "LED2", 128, &led2Params, 1, NULL);
    xTaskCreate(vLEDTask, "LED3", 128, &led3Params, 1, NULL);
    
    // 스케줄러 시작
    vTaskStartScheduler();
    
    while(1);
}

C++ 버전:

#include "main.h"
#include "FreeRTOS.h"
#include "task.h"

// GPIO 래퍼 클래스
class GPIO {
private:
    GPIO_TypeDef* port;
    uint16_t pin;
    
public:
    GPIO(GPIO_TypeDef* p, uint16_t pn) : port(p), pin(pn) {}
    
    void init() {
        GPIO_InitTypeDef GPIO_InitStruct = {0};
        
        if(port == GPIOA) __HAL_RCC_GPIOA_CLK_ENABLE();
        
        GPIO_InitStruct.Pin = pin;
        GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
        HAL_GPIO_Init(port, &GPIO_InitStruct);
    }
    
    void set() {
        HAL_GPIO_WritePin(port, pin, GPIO_PIN_SET);
    }
    
    void reset() {
        HAL_GPIO_WritePin(port, pin, GPIO_PIN_RESET);
    }
};

// Task 베이스 클래스
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() {}
};

// LED Task 클래스
class LEDTask : public Task {
private:
    GPIO& led;
    uint32_t delayMs;
    
public:
    LEDTask(const char* name, GPIO& ledPin, uint32_t delay)
        : Task(name, 128, 1), led(ledPin), delayMs(delay) {}
    
    void run() override {
        while(1) {
            led.set();
            vTaskDelay(pdMS_TO_TICKS(delayMs));
            
            led.reset();
            vTaskDelay(pdMS_TO_TICKS(delayMs));
        }
    }
};

int main(void) {
    HAL_Init();
    SystemClock_Config();
    
    // GPIO 객체 생성 및 초기화
    GPIO led1(GPIOA, GPIO_PIN_5);
    GPIO led2(GPIOA, GPIO_PIN_6);
    GPIO led3(GPIOA, GPIO_PIN_7);
    
    led1.init();
    led2.init();
    led3.init();
    
    // LED Task 객체 생성
    LEDTask ledTask1("LED1", led1, 500);
    LEDTask ledTask2("LED2", led2, 1000);
    LEDTask ledTask3("LED3", led3, 2000);
    
    // Task 생성
    ledTask1.create();
    ledTask2.create();
    ledTask3.create();
    
    // 스케줄러 시작
    vTaskStartScheduler();
    
    while(1);
}

실행 타이밍 분석

시간축 (ms) →
0    500  1000 1500 2000 2500 3000 3500 4000
|-----|-----|-----|-----|-----|-----|-----|
LED1: █─█─█─█─█─█─█─█─█─  (500ms 주기)
LED2: ██──██──██──██────  (1000ms 주기)
LED3: ████────████────── (2000ms 주기)

█ = ON
─ = OFF

6. Task 우선순위 실습

우선순위에 따른 실행 순서

C 버전:

void vHighPriorityTask(void *pvParameters) {
    int count = 0;
    
    while(1) {
        printf("[HIGH] Count: %d\n", count++);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void vMediumPriorityTask(void *pvParameters) {
    int count = 0;
    
    while(1) {
        printf("[MEDIUM] Count: %d\n", count++);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void vLowPriorityTask(void *pvParameters) {
    int count = 0;
    
    while(1) {
        printf("[LOW] Count: %d\n", count++);
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

int main(void) {
    HAL_Init();
    SystemClock_Config();
    UART_Init();
    
    // 우선순위를 다르게 설정
    xTaskCreate(vHighPriorityTask, "High", 128, NULL, 3, NULL);
    xTaskCreate(vMediumPriorityTask, "Med", 128, NULL, 2, NULL);
    xTaskCreate(vLowPriorityTask, "Low", 128, NULL, 1, NULL);
    
    vTaskStartScheduler();
    while(1);
}

// 출력 결과:
// [HIGH] Count: 0
// [MEDIUM] Count: 0
// [LOW] Count: 0
// (1초 후)
// [HIGH] Count: 1      ← 가장 먼저 실행
// [MEDIUM] Count: 1
// [LOW] Count: 1

C++ 버전:

class PriorityTask : public Task {
private:
    const char* level;
    int count;
    
public:
    PriorityTask(const char* name, const char* lvl, UBaseType_t prio)
        : Task(name, 128, prio), level(lvl), count(0) {}
    
    void run() override {
        while(1) {
            printf("[%s] Count: %d\n", level, count++);
            vTaskDelay(pdMS_TO_TICKS(1000));
        }
    }
};

int main(void) {
    HAL_Init();
    SystemClock_Config();
    UART_Init();
    
    PriorityTask highTask("High", "HIGH", 3);
    PriorityTask mediumTask("Medium", "MEDIUM", 2);
    PriorityTask lowTask("Low", "LOW", 1);
    
    highTask.create();
    mediumTask.create();
    lowTask.create();
    
    vTaskStartScheduler();
    while(1);
}

7. Task 상태 모니터링

vTaskList() 활용

// FreeRTOSConfig.h에 추가
#define configUSE_TRACE_FACILITY    1
#define configUSE_STATS_FORMATTING_FUNCTIONS    1

void vMonitorTask(void *pvParameters) {
    char buffer[512];
    
    while(1) {
        printf("\n=== Task List ===\n");
        vTaskList(buffer);
        printf("%s\n", buffer);
        
        vTaskDelay(pdMS_TO_TICKS(5000));
    }
}

// 출력 예:
// Name          State   Priority    Stack    Num
// LED1          R       1           100      1
// LED2          B       1           100      2
// LED3          B       1           100      3
// Monitor       R       2           200      4
// IDLE          R       0           50       5
//
// State: R=Running, B=Blocked, S=Suspended, D=Deleted

실습 과제

과제 1: 가변 주기 LED Task

목표: 버튼을 누를 때마다 LED 깜빡임 주기가 변하는 Task 작성

요구사항:

  • LED Task: 초기 500ms 주기
  • Button Task: 버튼 입력 감지
  • 버튼을 누를 때마다 주기 변경: 500ms → 1000ms → 2000ms → 500ms (순환)

힌트:

// 전역 변수로 주기 공유
volatile uint32_t ledPeriod = 500;

void vLEDTask(void *pvParameters) {
    while(1) {
        HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
        vTaskDelay(pdMS_TO_TICKS(ledPeriod));  // 가변 주기
    }
}

void vButtonTask(void *pvParameters) {
    while(1) {
        if(button_pressed()) {
            // ledPeriod 변경
        }
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

과제 2: Multi-sensor 읽기

목표: 여러 센서를 각각의 Task로 읽기

요구사항:

  • Temperature Task: 100ms마다 온도 읽기 (우선순위 2)
  • Humidity Task: 200ms마다 습도 읽기 (우선순위 2)
  • Display Task: 1000ms마다 센서 값 출력 (우선순위 1)

과제 3: Task 생성 실패 처리

목표: 메모리 부족 시 Task 생성 실패 처리

요구사항:

  • 의도적으로 큰 Stack 크기 설정 (메모리 초과 유도)
  • xTaskCreate() 반환값 확인
  • 실패 시 LED로 에러 표시

힌트:

if(xTaskCreate(...) != pdPASS) {
    // 에러 처리: 빨간 LED 켜기
    while(1) {
        HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_13);
        HAL_Delay(100);
    }
}

요약

오늘 배운 핵심 내용

  1. Task 구조: 무한 루프를 포함하는 특별한 함수
  2. xTaskCreate(): Task 생성 함수와 파라미터
  3. 파라미터 전달: void* 포인터를 통한 데이터 전달
  4. Task vs 함수: 독립적 실행 vs 순차적 호출
  5. Multi-task: 여러 Task의 동시 실행

핵심 용어 정리

용어설명
Task Functionvoid* 파라미터를 받고 무한 루프를 포함하는 함수
Stack DepthTask가 사용할 Stack 크기 (words 단위)
Task HandleTask를 식별하고 제어하기 위한 핸들
TCBTask Control Block, Task 정보 저장 구조체
pvParametersTask에 전달되는 void* 파라미터

복습 문제

  1. Task 함수가 반드시 무한 루프를 포함해야 하는 이유는?
  2. xTaskCreate()의 각 파라미터가 의미하는 것은?
  3. Task Stack 크기를 결정할 때 고려해야 할 요소는?
  4. 지역 변수를 Task 파라미터로 전달하면 안 되는 이유는?
  5. Task와 일반 함수의 가장 큰 차이점 3가지는?
  6. 같은 우선순위를 가진 3개의 Task가 있을 때 어떻게 실행되는가?
  7. C와 C++로 Task를 구현할 때 파라미터 전달 방식의 차이는?
  8. vTaskDelay()를 호출하지 않으면 어떤 문제가 발생하는가?

profile
당신의 코딩 메이트

0개의 댓글