본 포스팅은 취준 목적으로 RTOS를 공부하는 과정에서 작성된 내용입니다. 부족한 내용에 대해선 clauude 및 구글링, 서적 레퍼런싱을 통해 추후 업데이트 할 예정입니다
Bare-metal은 운영체제 없이 마이크로컨트롤러에서 직접 코드를 실행하는 방식입니다.
간단한 Bare-metal 예제:
C 버전:
#include "stm32f4xx.h"
void delay(volatile uint32_t count) {
while(count--);
}
int main(void) {
// LED 초기화
RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;
GPIOD->MODER |= GPIO_MODER_MODER12_0;
while(1) {
GPIOD->ODR ^= GPIO_ODR_OD12; // LED 토글
delay(1000000);
// 버튼을 읽어야 한다면?
// UART 데이터를 처리해야 한다면?
// 센서 값을 읽어야 한다면?
// 모두 여기서 순차적으로 처리해야 함!
}
}
C++ 버전:
#include "stm32f4xx.h"
class LED {
private:
volatile uint32_t* odr_reg;
uint32_t pin_mask;
public:
LED(volatile uint32_t* odr, uint32_t pin) : odr_reg(odr), pin_mask(1 << pin) {
// LED 초기화
RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;
GPIOD->MODER |= (1 << (pin * 2));
}
void toggle() {
*odr_reg ^= pin_mask;
}
void delay(volatile uint32_t count) {
while(count--);
}
};
int main(void) {
LED led(&GPIOD->ODR, 12);
while(1) {
led.toggle();
led.delay(1000000);
// 버튼을 읽어야 한다면?
// UART 데이터를 처리해야 한다면?
// 센서 값을 읽어야 한다면?
// 모두 여기서 순차적으로 처리해야 함!
}
}
Bare-metal의 특징:
장점:
단점:
C 버전:
int main(void) {
init_led();
init_button();
init_uart();
init_sensor();
while(1) {
// 모든 작업을 순차적으로 처리
if(button_pressed()) {
handle_button();
}
if(uart_data_available()) {
handle_uart(); // 이 함수가 오래 걸리면?
}
if(sensor_ready()) {
read_sensor(); // 센서 읽기가 지연되면?
}
update_led(); // LED 업데이트가 늦어질 수 있음!
}
}
C++ 버전:
class System {
private:
LED led;
Button button;
UART uart;
Sensor sensor;
public:
System() {
led.init();
button.init();
uart.init();
sensor.init();
}
void run() {
while(1) {
// 모든 작업을 순차적으로 처리
if(button.isPressed()) {
button.handle();
}
if(uart.dataAvailable()) {
uart.handle(); // 이 함수가 오래 걸리면?
}
if(sensor.isReady()) {
sensor.read(); // 센서 읽기가 지연되면?
}
led.update(); // LED 업데이트가 늦어질 수 있음!
}
}
};
int main(void) {
System system;
system.run();
}
문제점:
1. 한 작업이 오래 걸리면 다른 작업이 지연됨
2. 긴급한 작업을 우선 처리할 수 없음
3. 정확한 타이밍 제어가 어려움
RTOS는 실시간으로 여러 작업을 관리하고 우선순위에 따라 실행하는 운영체제입니다.
RTOS를 사용한 같은 예제:
C 버전:
#include "FreeRTOS.h"
#include "task.h"
// LED 제어 Task
void vLEDTask(void *pvParameters) {
while(1) {
toggle_led();
vTaskDelay(pdMS_TO_TICKS(500)); // 500ms 대기
}
}
// 버튼 처리 Task
void vButtonTask(void *pvParameters) {
while(1) {
if(button_pressed()) {
handle_button();
}
vTaskDelay(pdMS_TO_TICKS(10)); // 10ms 대기
}
}
// UART 처리 Task (높은 우선순위)
void vUARTTask(void *pvParameters) {
while(1) {
if(uart_data_available()) {
handle_uart(); // 긴급 처리
}
vTaskDelay(pdMS_TO_TICKS(1));
}
}
// 센서 읽기 Task
void vSensorTask(void *pvParameters) {
while(1) {
read_sensor();
process_sensor_data();
vTaskDelay(pdMS_TO_TICKS(100)); // 100ms마다
}
}
int main(void) {
init_hardware();
// Task 생성 (우선순위 설정 가능)
xTaskCreate(vUARTTask, "UART", 128, NULL, 3, NULL); // 높은 우선순위
xTaskCreate(vSensorTask, "Sensor", 128, NULL, 2, NULL);
xTaskCreate(vButtonTask, "Button", 128, NULL, 2, NULL);
xTaskCreate(vLEDTask, "LED", 128, NULL, 1, NULL); // 낮은 우선순위
// 스케줄러 시작 - 여기서부터 RTOS가 Task들을 관리
vTaskStartScheduler();
while(1); // 여기는 실행되지 않음
}
C++ 버전:
#include "FreeRTOS.h"
#include "task.h"
// Task를 클래스로 추상화
class Task {
protected:
TaskHandle_t taskHandle;
const char* taskName;
uint16_t stackSize;
UBaseType_t priority;
// 순수 가상 함수 - 각 Task가 구현해야 함
virtual void run() = 0;
// Task 진입점 (static)
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:
LED& led;
public:
LEDTask(LED& ledRef) : Task("LED", 128, 1), led(ledRef) {}
void run() override {
while(1) {
led.toggle();
vTaskDelay(pdMS_TO_TICKS(500));
}
}
};
// Button Task 클래스
class ButtonTask : public Task {
private:
Button& button;
public:
ButtonTask(Button& btnRef) : Task("Button", 128, 2), button(btnRef) {}
void run() override {
while(1) {
if(button.isPressed()) {
button.handle();
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
};
// UART Task 클래스
class UARTTask : public Task {
private:
UART& uart;
public:
UARTTask(UART& uartRef) : Task("UART", 128, 3), uart(uartRef) {}
void run() override {
while(1) {
if(uart.dataAvailable()) {
uart.handle();
}
vTaskDelay(pdMS_TO_TICKS(1));
}
}
};
// Sensor Task 클래스
class SensorTask : public Task {
private:
Sensor& sensor;
public:
SensorTask(Sensor& sensorRef) : Task("Sensor", 128, 2), sensor(sensorRef) {}
void run() override {
while(1) {
sensor.read();
sensor.process();
vTaskDelay(pdMS_TO_TICKS(100));
}
}
};
int main(void) {
// 하드웨어 객체 생성
LED led;
Button button;
UART uart;
Sensor sensor;
// Task 객체 생성
LEDTask ledTask(led);
ButtonTask buttonTask(button);
UARTTask uartTask(uart);
SensorTask sensorTask(sensor);
// Task 생성
ledTask.create();
buttonTask.create();
uartTask.create();
sensorTask.create();
// 스케줄러 시작
vTaskStartScheduler();
while(1);
}
1. Task (태스크)
main() 함수가 동시에 실행되는 것처럼 동작2. Scheduler (스케줄러)
3. Context Switching (문맥 전환)
"빠르다"가 아니라 "정해진 시간 안에 반드시 완료"를 의미합니다.
예시:
Hard Real-Time (경성 실시간)
데드라인을 한 번이라도 못 지키면 치명적인 결과 발생
예시:
Soft Real-Time (연성 실시간)
데드라인을 가끔 못 지켜도 시스템이 정상 동작
예시:
RTOS 사용을 권장:
Bare-metal로 충분:
┌─────────────────────────────────────┐
│ 사용자 Application │
│ (Task1, Task2, Task3, ...) │
├─────────────────────────────────────┤
│ RTOS Kernel │
│ - Scheduler │
│ - Task Management │
│ - Synchronization (Mutex, Queue)│
│ - Memory Management │
├─────────────────────────────────────┤
│ Hardware Abstraction │
│(Timer, Interrupt, Context Switch)│
├─────────────────────────────────────┤
│ STM32 Hardware │
└─────────────────────────────────────┘
1. Task Management (작업 관리)
2. Scheduler (스케줄러)
3. Synchronization (동기화)
4. Timing (타이밍)
시장에는 여러 RTOS가 있습니다:
무료 오픈소스 (MIT 라이선스)
가장 널리 사용됨 (산업 표준)
풍부한 문서와 예제
STM32 공식 지원 (STM32CubeMX 통합)
작은 메모리 풋프린트 (4-10KB)
활발한 커뮤니티
요구사항:
Bare-metal의 문제:
C 버전:
#include "stm32f4xx_hal.h"
// 하드웨어 제어 함수들
void read_temperature(void) {
HAL_Delay(20); // 20ms 소요 (CPU가 여기서 멈춤)
}
void check_uart(void) {
// 데이터가 있다면 처리하는데 50ms 소요
if (UART_Data_Ready()) {
HAL_Delay(50);
}
}
void check_button(void) {
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET) {
// 버튼 처리 (1ms)
}
}
int main(void) {
HAL_Init();
SystemClock_Config();
while (1) {
read_temperature(); // 1번: 20ms 대기
check_uart(); // 2번: 데이터 있으면 50ms 대기
check_button(); // 3번: 여기서 버튼을 눌러도 앞선 70ms 동안은 무시됨
// 총 지연 시간: 약 71~72ms
}
}
C++ 버전:
class Sensor {
public:
void read() { HAL_Delay(20); }
};
class UartManager {
public:
void check() {
if (hasData()) { HAL_Delay(50); }
}
private:
bool hasData() { /* 체크 로직 */ return true; }
};
class Button {
public:
void check() {
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET) {
// 처리
}
}
};
// 객체 생성
Sensor tempSensor;
UartManager uart;
Button userButton;
int main(void) {
HAL_Init();
while (1) {
tempSensor.read(); // 객체 메서드 호출 (20ms)
uart.check(); // 객체 메서드 호출 (50ms)
userButton.check(); // 객체 메서드 호출
// C 버전과 마찬가지로 순차 실행의 한계(지연)가 동일하게 발생
}
}
RTOS 해결:
C 버전:
/* 버튼 태스크: 높은 우선순위 */
void vButtonTask(void *pvParameters) {
for(;;) {
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET) {
// 버튼 눌림 처리 (즉각 반응)
}
// 10ms마다 체크, 그동안은 다른 태스크에 CPU 양보
vTaskDelay(pdMS_TO_TICKS(10));
}
}
/* UART 통신 태스크: 중간 우선순위 */
void vUartTask(void *pvParameters) {
for(;;) {
check_uart(); // 데이터 처리 (최대 50ms 소요)
// 처리가 끝나면 다른 작업이 실행될 수 있도록 휴식
vTaskDelay(pdMS_TO_TICKS(5));
}
}
/* 온도 센서 태스크: 낮은 우선순위 */
void vTempTask(void *pvParameters) {
for(;;) {
read_temperature(); // 20ms 소요
vTaskDelay(pdMS_TO_TICKS(100)); // 100ms마다 측정
}
}
C++ 버전:
class TaskBase {
public:
virtual void run() = 0;
void start(const char* name, uint16_t stackDepth, UBaseType_t priority) {
xTaskCreate(taskWrapper, name, stackDepth, this, priority, NULL);
}
private:
static void taskWrapper(void* param) {
static_cast<TaskBase*>(param)->run();
}
};
// 버튼 제어 클래스
class ButtonTask : public TaskBase {
public:
void run() override {
for(;;) {
if (button.isPressed()) { /* 처리 */ }
vTaskDelay(pdMS_TO_TICKS(10)); // 10ms 대기
}
}
};
// UART 제어 클래스
class UartTask : public TaskBase {
public:
void run() override {
for(;;) {
uart.process(); // 오래 걸리는 작업
vTaskDelay(pdMS_TO_TICKS(5));
}
}
};
// 메인 함수에서 객체 생성 및 시작
ButtonTask btnTask;
UartTask uTask;
btnTask.start("Btn", 128, 3); // 우선순위 높음
uTask.start("Uart", 256, 1); // 우선순위 낮음
요구사항:
Bare-metal의 문제:
C 버전
volatile bool data_received = false;
uint8_t rx_buffer[64];
// UART 수신 인터럽트 콜백
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
data_received = true; // 플래그 설정
}
void main() {
while(1) {
if(data_received) {
if(parse_frame(rx_buffer)) { // 1. 파싱
process_command(); // 2. 처리
send_response(); // 3. 응답
}
data_received = false;
}
// 이 아래에 다른 작업(센서 읽기 등)이 있다면
// 통신 처리가 끝날 때까지 모두 대기해야 함
}
}
C++ 버전(싱글톤/클래스 중심):
class ProtocolManager {
public:
void poll() {
if(m_isNewData) {
handle();
m_isNewData = false;
}
}
void setDataReady() { m_isNewData = true; }
private:
bool m_isNewData = false;
void handle() { /* 파싱 -> 처리 -> 응답 */ }
};
ProtocolManager protocol;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
protocol.setDataReady();
}
void main() {
while(1) {
protocol.poll();
otherTask.run(); // 여전히 순차 실행의 한계 존재
}
}
RTOS방식(Queue/Semaphore 사용):
C 버전:
QueueHandle_t xProtocolQueue;
// 인터럽트: 데이터만 큐에 넣고 즉시 종료
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(xProtocolQueue, &rx_data, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
// 통신 전용 태스크: 데이터가 올 때까지 'Block' 상태로 대기 (CPU 점유 0%)
void vProtocolTask(void *pvParameters) {
uint8_t data;
for(;;) {
if(xQueueReceive(xProtocolQueue, &data, portMAX_DELAY)) {
parse(); // 파싱 중에도 우선순위 높은 다른 태스크는 실행 가능
process();
send();
}
}
}
C++ 버전(Active Object 패턴):
class CommunicationTask : public TaskBase {
public:
CommunicationTask() { m_queue = xQueueCreate(10, sizeof(Packet)); }
void pushPacket(Packet p) { xQueueSend(m_queue, &p, 0); }
void run() override {
Packet p;
for(;;) {
if(xQueueReceive(m_queue, &p, portMAX_DELAY)) {
// 파싱, 처리, 응답 로직을 객체 지향적으로 수행
Processor.execute(Parser.parse(p));
}
}
}
private:
QueueHandle_t m_queue;
};
RTOS 장점:
| 용어 | 설명 |
|---|---|
| Bare-metal | OS 없이 직접 하드웨어를 제어하는 프로그래밍 방식 |
| RTOS | 실시간 운영체제, 여러 작업을 우선순위에 따라 관리 |
| Task | 독립적으로 실행되는 작은 프로그램 단위 |
| Scheduler | Task 실행 순서를 결정하는 RTOS의 핵심 구성요소 |
| Context Switch | 한 Task에서 다른 Task로 전환하는 과정 |
| Hard Real-Time | 데드라인을 반드시 지켜야 하는 시스템 |
| Soft Real-Time | 데드라인을 가끔 못 지켜도 괜찮은 시스템 |