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));
}
}
};
| 요소 | 설명 | 중요도 |
|---|---|---|
| void 포인터 파라미터 | Task에 데이터를 전달하는 방법 | 필수 |
| 무한 루프 (while(1)) | Task는 종료되지 않고 계속 실행 | 필수 |
| vTaskDelay() | 다른 Task에게 CPU 양보, 없으면 독점 | 권장 |
| 초기화 코드 | 루프 전에 실행되는 설정 코드 | 선택 |
// 잘못된 예 - 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 = 자기 자신
}
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);
}
}
스택의 크기 결정 방법을 알기에 앞서, 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));
}
}
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() 필요
}
| 특성 | Task | 일반 함수 |
|---|---|---|
| 실행 방식 | 독립적, 병렬적 실행 | 순차적 호출 |
| 종료 | 무한 루프, 종료하지 않음 | return으로 종료 |
| Stack | 독립적인 Stack 공간 | 호출자의 Stack 공유 |
| 우선순위 | 우선순위 지정 가능 | 우선순위 개념 없음 |
| 스케줄링 | 스케줄러가 관리 | 직접 호출로만 실행 |
| 메모리 | Stack + TCB | Stack만 사용 |
| 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);
}
목표:
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
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);
}
// 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
목표: 버튼을 누를 때마다 LED 깜빡임 주기가 변하는 Task 작성
요구사항:
힌트:
// 전역 변수로 주기 공유
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));
}
}
목표: 여러 센서를 각각의 Task로 읽기
요구사항:
목표: 메모리 부족 시 Task 생성 실패 처리
요구사항:
힌트:
if(xTaskCreate(...) != pdPASS) {
// 에러 처리: 빨간 LED 켜기
while(1) {
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_13);
HAL_Delay(100);
}
}
| 용어 | 설명 |
|---|---|
| Task Function | void* 파라미터를 받고 무한 루프를 포함하는 함수 |
| Stack Depth | Task가 사용할 Stack 크기 (words 단위) |
| Task Handle | Task를 식별하고 제어하기 위한 핸들 |
| TCB | Task Control Block, Task 정보 저장 구조체 |
| pvParameters | Task에 전달되는 void* 파라미터 |