| 계층 | 설명 |
|---|---|
| Application Tasks | 사용자가 작성하는 Task들로 구성됩니다. LED 제어, 센서 읽기, 통신 처리 등 실제 애플리케이션 로직이 이 계층에 구현됩니다. 각 Task는 독립적으로 동작하며 우선순위를 가집니다. |
| FreeRTOS API Layer | 사용자에게 제공되는 API 함수들입니다. task.h(Task 관리), queue.h(Queue 통신), semphr.h(Semaphore/Mutex), timers.h(Software Timer) 등의 헤더 파일로 구성됩니다. 이 계층을 통해 Kernel 기능에 접근합니다. |
| FreeRTOS Kernel | RTOS의 핵심 구성요소입니다. Scheduler(Task 스케줄링), Task Manager(Task 생성/삭제/상태 관리), Memory Manager(동적 메모리 할당), IPC 메커니즘(Queue, Semaphore 구현) 등이 포함됩니다. 하드웨어 독립적인 코드로 작성되어 있습니다. |
| Port Layer | 특정 하드웨어 아키텍처에 맞춘 포팅 코드입니다. ARM Cortex-M4의 경우 Context Switch 구현, SysTick 타이머 설정, 인터럽트 우선순위 관리 등이 포함됩니다. port.c와 portmacro.h 파일로 구성됩니다. |
| Hardware (STM32) | 실제 마이크로컨트롤러 하드웨어입니다. CPU 코어, 메모리, 주변장치(GPIO, UART, Timer 등), 인터럽트 컨트롤러 등이 포함됩니다. FreeRTOS는 이 계층 위에서 동작합니다. |
1. Kernel (커널)
2. Scheduler (스케줄러)
3. Task Manager
4. IPC (Inter-Process Communication)
5. Memory Manager
FreeRTOS/
├── Source/
│ ├── tasks.c # Task 관리
│ ├── queue.c # Queue 구현
│ ├── list.c # 내부 리스트 구조
│ ├── timers.c # Software Timer
│ ├── event_groups.c # Event Group
│ ├── stream_buffer.c # Stream Buffer
│ ├── portable/
│ │ ├── GCC/
│ │ │ └── ARM_CM4F/ # Cortex-M4F 포팅 코드
│ │ │ ├── port.c
│ │ │ └── portmacro.h
│ │ └── MemMang/
│ │ ├── heap_1.c # 가장 간단한 메모리 관리
│ │ ├── heap_2.c
│ │ ├── heap_3.c
│ │ ├── heap_4.c # 권장 (조각 모음 지원)
│ │ └── heap_5.c
│ └── include/
│ ├── FreeRTOS.h
│ ├── task.h
│ ├── queue.h
│ ├── semphr.h
│ ├── timers.h
│ └── event_groups.h
└── Demo/ # 각종 예제 프로젝트
tasks.c
queue.c
port.c
FreeRTOS는 우선순위 기반 선점형 스케줄링을 사용합니다.
핵심 원칙:
1. 항상 가장 높은 우선순위의 Ready Task가 실행됨
2. 같은 우선순위면 Round-robin으로 실행 (Time Slice)
3. 높은 우선순위 Task가 Ready 되면 즉시 선점
// 우선순위 범위: 0 ~ (configMAX_PRIORITIES - 1)
// 숫자가 클수록 높은 우선순위
/* 1. 우선 순위 정의 */
#define IDLE_PRIORITY 0 // 가장 낮음 (Idle Task)
#define LED_PRIORITY 1
#define BUTTON_PRIORITY 2
#define SENSOR_PRIORITY 2 // 같은 우선순위 가능
#define UART_PRIORITY 3 // 가장 높음
/* 태스크 함수 프로토타입 선언 */
void vUART_Task(void *pvParameters);
void vButton_Task(void *pvParameters);
void vSensor_Task(void *pvParameters);
void vLED_Task(void *pvParameters);
int main(void) {
/* 하드웨어 초기화 코드 (생략) */
// HAL_Init();
// SystemClock_Config();
/* 2. xTaskCreate를 이용한 태스크 생성 및 우선순위 부여 */
// UART 태스크: 우선순위 3 (가장 높음, 중요 데이터 전송)
xTaskCreate(vUART_Task, "UART", 128, NULL, UART_priority, NULL);
// 버튼 & 센서 태스크: 우선순위 2 (동일 우선순위, 1ms씩 번갈아 실행)
xTaskCreate(vButton_Task, "BUTTON", 128, NULL, BUTTON_PRIORITY, NULL);
xTaskCreate(vSensor_Task, "SENSOR", 128, NULL, SENSOR_priority, NULL);
// LED 태스크: 우선순위 1 (가장 낮음, 여유 있을 때만 실행)
xTaskCreate(vLED_Task, "LED", 128, NULL, LED_priority, NULL);
/* 3. 스케줄러 시작 */
vTaskStartScheduler();
while(1); // 스케줄러가 시작되면 여기까지 내려오지 않습니다.
}
/* 태스크 구현 예시 */
void vUART_Task(void *pvParameters) {
for(;;) {
// UART 송수신 로직
vTaskDelay(pdMS_TO_TICKS(10)); // 10ms 블록 상태 (다른 태스크에 기회 넘김)
}
}
void vLED_Task(void *pvParameters) {
for(;;) {
// LED 토글 로직
vTaskDelay(pdMS_TO_TICKS(500));
}
}
// 나머지 Task 함수들도 유사한 구조로 구현...
}
태스크 우선순위는 #define으로 고정된 값을 정의해두고, 태스크를 생성하는 함수인 xTaskCreate()의 인자로 전달해 부여합니다. 이 때 단순히 숫자를 직접 입력해도 되지만, 가독성과 유지보수성을 위해 #define을 사용하는 것이 관례입니다.
RTOS에서는 태스크가 종료되지 않고 계속 실행돼야 하기 때문에 모든 태스크는 while(1) 무한 루프를 가집니다.
C 버전:
#include "FreeRTOS.h"
#include "task.h"
void vTaskHigh(void *pvParameters) {
while(1) {
// 높은 우선순위 작업
process_urgent_data();
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void vTaskMedium(void *pvParameters) {
while(1) {
// 중간 우선순위 작업
process_normal_data();
vTaskDelay(pdMS_TO_TICKS(200));
}
}
void vTaskLow(void *pvParameters) {
while(1) {
// 낮은 우선순위 작업
background_task();
vTaskDelay(pdMS_TO_TICKS(500));
}
}
int main(void) {
xTaskCreate(vTaskHigh, "High", 128, NULL, 3, NULL);
xTaskCreate(vTaskMedium, "Med", 128, NULL, 2, NULL);
xTaskCreate(vTaskLow, "Low", 128, NULL, 1, NULL);
vTaskStartScheduler();
while(1);
}
C++ 버전:
#include "FreeRTOS.h"
#include "task.h"
class PriorityTask : public Task {
protected:
uint32_t delayMs;
public:
PriorityTask(const char* name, UBaseType_t priority, uint32_t delay)
: Task(name, 128, priority), delayMs(delay) {}
};
class HighPriorityTask : public PriorityTask {
public:
HighPriorityTask() : PriorityTask("High", 3, 100) {}
void run() override {
while(1) {
processUrgentData();
vTaskDelay(pdMS_TO_TICKS(delayMs));
}
}
private:
void processUrgentData() {
// 높은 우선순위 작업
}
};
class MediumPriorityTask : public PriorityTask {
public:
MediumPriorityTask() : PriorityTask("Medium", 2, 200) {}
void run() override {
while(1) {
processNormalData();
vTaskDelay(pdMS_TO_TICKS(delayMs));
}
}
private:
void processNormalData() {
// 중간 우선순위 작업
}
};
class LowPriorityTask : public PriorityTask {
public:
LowPriorityTask() : PriorityTask("Low", 1, 500) {}
void run() override {
while(1) {
backgroundTask();
vTaskDelay(pdMS_TO_TICKS(delayMs));
}
}
private:
void backgroundTask() {
// 낮은 우선순위 작업
}
};
int main(void) {
HighPriorityTask highTask;
MediumPriorityTask mediumTask;
LowPriorityTask lowTask;
highTask.create();
mediumTask.create();
lowTask.create();
vTaskStartScheduler();
while(1);
}
시간축 →
0ms : High, Med, Low 모두 Ready
→ High 실행 (우선순위 3)
100ms : High가 vTaskDelay() → Blocked
→ Med 실행 (우선순위 2)
200ms : High Ready (100ms 지남)
→ High가 Med를 선점하여 실행
300ms : High가 다시 Blocked
Med가 vTaskDelay() → Blocked
→ Low 실행 (우선순위 1)
┌─────────────┐
│ Running │ ← 현재 실행 중
└─────────────┘
↕
┌─────────────┐
│ Ready │ ← 실행 준비됨, CPU 대기
└─────────────┘
↕
┌─────────────┐
│ Blocked │ ← 이벤트/시간 대기
└─────────────┘
↕
┌─────────────┐
│ Suspended │ ← 명시적으로 정지됨
└─────────────┘
Running → Blocked
Blocked → Ready
Ready → Running
Running → Ready
Any → Suspended
Suspended → Ready
C 버전:
void vTask1(void *pvParameters) {
while(1) {
// Running 상태
do_work();
// Blocked 상태로 전이 (100ms)
vTaskDelay(pdMS_TO_TICKS(100));
// → 100ms 후 Ready 상태
// → 스케줄러가 선택하면 Running 상태
}
}
TaskHandle_t xTask2Handle;
void vTask2(void *pvParameters) {
while(1) {
do_something();
// Task2를 Suspended 상태로
vTaskSuspend(NULL); // NULL = 자기 자신
// vTaskResume()이 호출될 때까지 여기서 멈춤
do_after_resume();
}
}
void vTask3(void *pvParameters) {
while(1) {
vTaskDelay(pdMS_TO_TICKS(1000));
// Task2를 깨움 (Suspended → Ready)
vTaskResume(xTask2Handle);
}
}
int main(void) {
xTaskCreate(vTask1, "Task1", 128, NULL, 1, NULL);
xTaskCreate(vTask2, "Task2", 128, NULL, 2, &xTask2Handle);
xTaskCreate(vTask3, "Task3", 128, NULL, 1, NULL);
vTaskStartScheduler();
while(1);
}
C++ 버전:
class Task1 : public Task {
public:
Task1() : Task("Task1", 128, 1) {}
void run() override {
while(1) {
// Running 상태
doWork();
// Blocked 상태로 전이 (100ms)
vTaskDelay(pdMS_TO_TICKS(100));
// → 100ms 후 Ready 상태
// → 스케줄러가 선택하면 Running 상태
}
}
private:
void doWork() {
// 작업 수행
}
};
class Task2 : public Task {
public:
Task2() : Task("Task2", 128, 2) {}
void run() override {
while(1) {
doSomething();
// Task2를 Suspended 상태로
vTaskSuspend(taskHandle);
// vTaskResume()이 호출될 때까지 여기서 멈춤
doAfterResume();
}
}
private:
void doSomething() { }
void doAfterResume() { }
};
class Task3 : public Task {
private:
Task2& task2;
public:
Task3(Task2& t2) : Task("Task3", 128, 1), task2(t2) {}
void run() override {
while(1) {
vTaskDelay(pdMS_TO_TICKS(1000));
// Task2를 깨움 (Suspended → Ready)
task2.resume();
}
}
};
// Task 클래스에 resume 메서드 추가 필요
class Task {
// ... 기존 코드 ...
public:
void resume() {
if(taskHandle != nullptr) {
vTaskResume(taskHandle);
}
}
};
int main(void) {
Task1 task1;
Task2 task2;
Task3 task3(task2);
task1.create();
task2.create();
task3.create();
vTaskStartScheduler();
while(1);
}
Tick이란?
configTICK_RATE_HZ 설정:
// FreeRTOSConfig.h
#define configTICK_RATE_HZ 1000 // 1000Hz = 1ms
매 1ms마다:
1. SysTick 인터럽트 발생
2. Tick 카운터 증가
3. Blocked Task 중 Timeout된 Task를 Ready로 전환
4. 필요하면 Context Switch
같은 우선순위의 Task들이 여러 개 있을 때:
C 버전:
// configUSE_TIME_SLICING이 1로 설정되어 있으면
// 같은 우선순위 Task들은 Round-robin으로 실행
void vTaskA(void *pvParameters) {
while(1) {
// CPU 집중 작업 (Delay 없음)
for(int i = 0; i < 1000000; i++);
}
}
void vTaskB(void *pvParameters) {
while(1) {
// CPU 집중 작업
for(int i = 0; i < 1000000; i++);
}
}
// 같은 우선순위로 생성
xTaskCreate(vTaskA, "A", 128, NULL, 2, NULL);
xTaskCreate(vTaskB, "B", 128, NULL, 2, NULL);
// 결과: TaskA와 TaskB가 매 Tick마다 번갈아 실행
C++ 버전:
class CPUIntensiveTask : public Task {
protected:
char taskId;
public:
CPUIntensiveTask(char id, const char* name)
: Task(name, 128, 2), taskId(id) {}
void run() override {
while(1) {
// CPU 집중 작업 (Delay 없음)
for(volatile int i = 0; i < 1000000; i++);
}
}
};
class TaskA : public CPUIntensiveTask {
public:
TaskA() : CPUIntensiveTask('A', "TaskA") {}
};
class TaskB : public CPUIntensiveTask {
public:
TaskB() : CPUIntensiveTask('B', "TaskB") {}
};
int main(void) {
TaskA taskA;
TaskB taskB;
// 같은 우선순위로 생성
taskA.create();
taskB.create();
vTaskStartScheduler();
// 결과: TaskA와 TaskB가 매 Tick마다 번갈아 실행
while(1);
}
C 버전:
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
/* ========== 기본 설정 ========== */
// CPU 클럭 주파수 (Hz)
#define configCPU_CLOCK_HZ ( SystemCoreClock )
// Tick 주파수 (Hz) - 보통 1000 (1ms)
#define configTICK_RATE_HZ 1000
// 최대 우선순위 (0 ~ 이 값-1)
#define configMAX_PRIORITIES 5
// Idle Task Stack 크기 (words)
#define configMINIMAL_STACK_SIZE 128
// 전체 Heap 크기 (bytes)
#define configTOTAL_HEAP_SIZE ( 10 * 1024 )
/* ========== 스케줄링 관련 ========== */
// Preemption 사용 (1 = 선점형)
#define configUSE_PREEMPTION 1
// Time Slicing 사용 (같은 우선순위 Round-robin)
#define configUSE_TIME_SLICING 1
// Idle Task Yield (Idle Task가 자발적으로 양보)
#define configIDLE_SHOULD_YIELD 1
/* ========== 기능 활성화 ========== */
// Queue 사용
#define configUSE_QUEUE_SETS 0
// Mutex 사용
#define configUSE_MUTEXES 1
// Recursive Mutex 사용
#define configUSE_RECURSIVE_MUTEXES 1
// Counting Semaphore 사용
#define configUSE_COUNTING_SEMAPHORES 1
// Software Timer 사용
#define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY 3
#define configTIMER_QUEUE_LENGTH 10
#define configTIMER_TASK_STACK_DEPTH 128
/* ========== Hook 함수 ========== */
// Idle Hook
#define configUSE_IDLE_HOOK 0
// Tick Hook
#define configUSE_TICK_HOOK 0
// Malloc 실패 Hook
#define configUSE_MALLOC_FAILED_HOOK 1
// Stack Overflow 검사
#define configCHECK_FOR_STACK_OVERFLOW 2
/* ========== Cortex-M 특화 설정 ========== */
// 인터럽트 우선순위 비트 수 (STM32F4는 4비트)
#define configPRIO_BITS 4
// Kernel 인터럽트 우선순위
#define configKERNEL_INTERRUPT_PRIORITY ( 15 << (8 - configPRIO_BITS) )
// 최대 Syscall 인터럽트 우선순위
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( 5 << (8 - configPRIO_BITS) )
/* ========== API 함수 포함 ========== */
#define INCLUDE_vTaskPrioritySet 1
#define INCLUDE_uxTaskPriorityGet 1
#define INCLUDE_vTaskDelete 1
#define INCLUDE_vTaskSuspend 1
#define INCLUDE_vTaskDelayUntil 1
#define INCLUDE_vTaskDelay 1
#endif /* FREERTOS_CONFIG_H */
C++ 버전:
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H
// C++에서도 동일한 설정 사용
// 하지만 추가적인 타입 안정성을 위해 constexpr 사용 가능
namespace FreeRTOSConfig {
// 컴파일 타임 상수로 정의
constexpr uint32_t CPU_CLOCK_HZ = 84000000;
constexpr uint32_t TICK_RATE_HZ = 1000;
constexpr uint32_t MAX_PRIORITIES = 5;
constexpr uint32_t MINIMAL_STACK_SIZE = 128;
constexpr uint32_t TOTAL_HEAP_SIZE = 10 * 1024;
// Task 우선순위를 enum class로 정의
enum class Priority : UBaseType_t {
Idle = 0,
Low = 1,
Normal = 2,
High = 3,
Critical = 4
};
}
// FreeRTOS C 매크로도 여전히 필요
#define configCPU_CLOCK_HZ ( SystemCoreClock )
#define configTICK_RATE_HZ 1000
#define configMAX_PRIORITIES 5
#define configMINIMAL_STACK_SIZE 128
#define configTOTAL_HEAP_SIZE ( 10 * 1024 )
#define configUSE_PREEMPTION 1
#define configUSE_TIME_SLICING 1
#define configIDLE_SHOULD_YIELD 1
#define configUSE_MUTEXES 1
#define configUSE_RECURSIVE_MUTEXES 1
#define configUSE_COUNTING_SEMAPHORES 1
#define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY 3
#define configTIMER_QUEUE_LENGTH 10
#define configTIMER_TASK_STACK_DEPTH 128
#define configUSE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
#define configUSE_MALLOC_FAILED_HOOK 1
#define configCHECK_FOR_STACK_OVERFLOW 2
#define configPRIO_BITS 4
#define configKERNEL_INTERRUPT_PRIORITY ( 15 << (8 - configPRIO_BITS) )
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( 5 << (8 - configPRIO_BITS) )
#define INCLUDE_vTaskPrioritySet 1
#define INCLUDE_uxTaskPriorityGet 1
#define INCLUDE_vTaskDelete 1
#define INCLUDE_vTaskSuspend 1
#define INCLUDE_vTaskDelayUntil 1
#define INCLUDE_vTaskDelay 1
#endif /* FREERTOS_CONFIG_H */
configCPU_CLOCK_HZ
configTICK_RATE_HZ
configMAX_PRIORITIES
configTOTAL_HEAP_SIZE
configCHECK_FOR_STACK_OVERFLOW
현재 실행 중인 Task를 멈추고 다른 Task로 전환하는 과정
우선순위에 따라 선점(Preemption, 강제 전환), 또는 순차적/자발적 전환(Yield/Block)으로 나뉘지만 태스크 종료 후 다음 태스크로 전환되는 일련의 동작을 컨텍스트 스위칭이라고 합니다.
저장해야 할 것:
여기서 Task 우선순위를 각각 0,1,2,3,4로 부여한 태스크들이 있다고 했을 때 4의 우선순위를 가진 태스크의 실행 시간이 100ms로 매우 짧고, 따라서 하위 우선순위를 가진 태스크들이 실행될 만큼 충분한 blocked-time을 가지지 못 한다면 프로그램 실행 내내 4의 우선순위를 가진 태스크만 실행되고 나머지 태스크는 실행될 시간을 갖지 못하는 기아상태(Starvation)에 빠질 수 있습니다.
일반적으로 기아상태(Starvation)를 유발하지 않도록 태스크 매니징을 하는 것이 설계자로써의 역할이지만 때에 따라 Fail-Safe나 비상 정지(Emergency Stop) 로직을 설계하기 위해 의도적으로 기아상태를 시스템 제어 도구로 활용할 수 있습니다
PendSV는 컨텍스트 스위칭을 안전하게 처리하기 위한 전용 인터럽트이며, 로직은 다음과 같습니다.
1. 스케쥴러가 태스크 전환이 필요하다고 판단하면 PendSV를 Pending 상태로 만듦.
2. 현재 실행 중인 모든 하드웨어 인터럽트(UART, DMA, Timer 등)가 끝날 때까지 기다림.
3. 더 이상 실행할 인터럽트가 없을 때, 가장 낮은 순위인 PendSV가 실행되며 안전하게 컨텍스트 스위칭
C 버전 설명:
// SysTick Interrupt Handler
void SysTick_Handler(void) {
// Tick 증가 및 스케줄링 필요 여부 확인
if(xTaskIncrementTick() != pdFALSE) {
// Context Switch 필요
// 하지만 여기서 바로 하지 않고 PendSV에 요청
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
}
}
// PendSV Interrupt Handler (가장 낮은 우선순위)
void PendSV_Handler(void) {
// 여기서 실제 Context Switch 수행
// 1. 현재 Task의 레지스터를 Stack에 저장
// 2. 다음 실행할 Task 선택
// 3. 새 Task의 레지스터를 복원
}
C++ 버전 - Context Switch 래퍼:
class ContextSwitcher {
public:
static void requestSwitch() {
// PendSV 요청
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;
}
static void disableInterrupts() {
taskENTER_CRITICAL();
}
static void enableInterrupts() {
taskEXIT_CRITICAL();
}
};
// 사용 예
class CriticalTask : public Task {
public:
CriticalTask() : Task("Critical", 128, 3) {}
void run() override {
while(1) {
// Critical Section 시작
ContextSwitcher::disableInterrupts();
// 공유 자원 접근
sharedData++;
// Critical Section 종료
ContextSwitcher::enableInterrupts();
vTaskDelay(pdMS_TO_TICKS(100));
}
}
private:
static volatile int sharedData;
};
| 용어 | 설명 |
|---|---|
| Kernel | RTOS의 핵심, Task 관리 및 스케줄링 담당 |
| Scheduler | 실행할 Task를 선택하는 구성요소 |
| Preemptive | 높은 우선순위가 낮은 우선순위를 선점 |
| Tick | RTOS의 시간 단위, 보통 1ms |
| Time Slice | 같은 우선순위 Task에게 할당되는 시간 |
| Context Switch | Task 전환 과정 |
| Ready | 실행 준비된 상태, CPU 대기 중 |
| Blocked | 이벤트나 시간을 대기하는 상태 |
| Suspended | 명시적으로 정지된 상태 |
학습 일자: 2026년 1월 10일
작성자: RTOS 강의 시리즈
다음 강의: Day 3 - 개발 환경 구축