091

[C/C++] ESP32에서 RTOS 사용하여 3가지 작업 유연하게 처리하기 본문

Programming Language/C&C++

[C/C++] ESP32에서 RTOS 사용하여 3가지 작업 유연하게 처리하기

공구일 2025. 4. 13. 19:38
728x90

01. RTOS(Real-Time Operating System)

 

- RTOS : 실시간 운영체제로, 여러 개의 작업을 스케쥴링을 통해 동시에 실행되도록 관리해줌. loop() 내부에서 너무 다양한 기능을 구현하다보면 버벅이거나 응답이 느려지므로 RTOS를 이용하여 여러 작업을 동시에 처리하는 시스템으로 만들어줌

-> ESP32에는 FreeRTOS가 기본적으로 내장되어있고 Task를 적절히 Core 0 또는 Core 1에 배치해줌

 

- ESP32의 듀얼 코어 구조 : ESP32에는 Core 0과 Core 1로 두개의 코어가 있음. 코어는 명령을 실제로 처리하는 CPU의 뇌처럼 생각하면 편함 -> Core 0은 시스템 전용으로 WiFi, 블루투스, 백그라운드 OS 작업을 하고, Core 1은 사용자 전용으로, setup(), loop(), 그리고 기본 Task들을 실행함 

=> 이후에 배우는 Task의 경우 기본적으로 Core 1에서 실행되지만 고정함수(xTaskCreatePinnedToCore)를 통해 명시적으로 핀을 할당할 수 있음

 

- RTOS 사용 :

• 다중 센서 데이터 읽기 + 디스플레이 + 제어

• 즉시 반응해야하는 시스템

• 명확한 작업 분담

• LCD 깜빡임 및 UART 반응 속도 문제 해결

 

02. RTOS 사용하여 3가지 작업 유연하게 처리하기

- 하드웨어 :

-> 이전 글에서 사용됐던 스위치 하드웨어와 완전히 일치 : 관련설명글 ->

(2025.04.09 - [Programming Language/C&C++] - [C/C++] ESP32에서 3가지 작업(LCD,Switch,UART) 반복처리하기)

- 소프트웨어 :

#include <Wire.h>              // Include the I2C communication library
#include "LCD1602_Display.h"   // Include the LCD1602_Display class

#define LED_PIN 4
#define SWITCH_PIN 5
#define PASSWORD "S113003391"

LCD1602_Display lcd(0x27, 16, 2);  // Create LCD object with I2C address 0x27
bool userMode = false;
bool romoteMode = false;
String lastText = "";  // 상태 변경 전 마지막 화면 텍스트를 저장

void setup() {
    Serial.begin(115200);  // Initialize Serial Monitor
    Wire.begin(21, 22);    // Initialize I2C communication (SDA=21, SCL=22)
    lcd.init();  // Initialize the LCD display
    lcd.displayText("Secure", 0, 0);  // 초기 메시지 표시

    xTaskCreate(Task1, "LCD", 2048, NULL, 1, NULL);
    xTaskCreate(Task2, "GPIO", 2048, NULL, 1, NULL);
    xTaskCreate(Task3, "UART", 2048, NULL, 1, NULL);
}

void loop() {
    // Nothing here, tasks are handled in the background
}

void Task1(void *pvParameters) { // LCD
    while (1) {
        String displayText = "";

        if (userMode) {
            if (!romoteMode) {
                displayText = "Local Control";
            } else {
                displayText = "Remote Control";
            }
        } else {
            displayText = "Secure";
        }

        // 상태가 변경될 때만 텍스트 업데이트
        if (displayText != lastText) {
            lcd.clearScreen();  // 화면 지우지 않고 텍스트만 바꿈
            lcd.displayText(displayText, 0, 0);
            lastText = displayText;  // 마지막 텍스트 상태 저장
        }

        delay(500);  // 너무 자주 업데이트 하지 않도록 잠시 대기
        vTaskDelay(100/ portTICK_PERIOD_MS);
    }
}

void Task2(void *pvParameters) { // GPIO 스위치
    pinMode(LED_PIN, OUTPUT);
    pinMode(SWITCH_PIN, INPUT);
    while (1) {
        if (userMode && !romoteMode) { // Local Mode
            int switchState = digitalRead(SWITCH_PIN);
            digitalWrite(LED_PIN, switchState); // 스위치 상태에 따라 LED 상태 제어
            delay(1000); // 1초 대기
        }
        vTaskDelay(100/ portTICK_PERIOD_MS);
    }
}

void Task3(void *pvParameters) { // UART 입력 기반 -> 비밀번호, ON/OFF
    while (1) {
        if (Serial.available()) {
            String input = Serial.readStringUntil('\n');
            input.trim();

            if (!userMode) {
                if (input == PASSWORD) {
                    userMode = true;
                } else {
                    Serial.println("Wrong Password");
                }
            } else {
                if (input == "LMode") {
                    romoteMode = false;
                } else if (input == "RMode") {
                    romoteMode = true;
                } else {
                    Serial.println("Please Type LMode or RMode");
                }

                if (romoteMode) { // Remote Mode
                    if (input == "ON") digitalWrite(LED_PIN, HIGH);
                    else if (input == "OFF") digitalWrite(LED_PIN, LOW);
                }
            }
        }
        vTaskDelay(100/ portTICK_PERIOD_MS);
    }
}

- 이전 글에서 구현했던 세가지 기능을 Task1,2,3으로 나누어 구현해주었음, Task1은 LCD 화면에 어떤 식으로 뜰지 구현해주고, Task2는 GPIO는 스위치의 ON/OFF에 따라 LED 불빛을 조절해줌, 두 장치 간 데이터를 TX(송신)과 RX(수신) 라인을 통해 전송하는 방식인 UART는 입력 기반으로 Serial로 ON, OFF를 받아야하므로 여기서 RMode를 구현해줌

- 태스크와 관련되 함수들 :

 xTaskCreate(Task1, "LCD", 2048, NULL, 1, NULL); : 태스크를 등록하는 함수

xTaskCreate(실행 함수의 이름, 태스크 이름, 스택 사이즈, 태스크에 넘길 매개변수, 우선순위, 태스크 핸들 저장할 포인터)

 

 void Task1(void *pvParameters) { ... } : FreeRTOS의 본체로, while(1) 안에 반복할 함수를 넣어주면 됨

• vTaskDelay(100 / portTICK_PERIOD_MS); : Task를 정한 시간만큼 쉬게 해주는 함수로, Task들은 CPU를 번갈아가며 쓰는 멀티태스킹 시스템인데, 각 테스크가 무한 루프(while(1))을 돌면서 너무 오랫동안 CPU를 독점하고 있으면 안되기 때문에 시간을 정해 일시적으로 쉬게 만들어줌 

-> portTICK_PERIOD_MS : FreeRTOS에서 1틱(Tick)이 몇 밀리 초인지 나타내는 상수로, RTOS 내부적으로 시간 Tick 단위로 관리하고, 밀리초 단위로 쉬고 싶을 때 정확한 계산을 위해 사용되는 상수( 1Tick = 1ms, 10ms 처럼 FreeRTOSConfig.h에 FreeRTOS 설정값인 configTICK_RATE_HZ로 선언, configTICK_RATE_HZ가 1000일 경우 1Tick = 1ms/ 100일 경우 1Tick = 10ms임)

void Task1(void *pvParameters) {
  while (1) {
    // LCD 상태 체크해서 화면에 보여줌
    vTaskDelay(100 / portTICK_PERIOD_MS);  // 잠시 쉬었다가 다음 루프
  }
}

 

728x90