본문 바로가기

ESP32

FreeRTOS를 사용한 ESP32 – 시작하기 Arduino IDE

반응형

 

FreeRTOS(Arduino IDE)를 사용한 ESP32 – 시작하기: 작업 만들기

 

이 튜토리얼에서는 FreeRTOS의 기본 개념을 소개하고 ESP32 및 Arduino IDE와 함께 사용하는 방법을 보여줍니다. 단일 및 다중 작업 생성, 작업 일시 중지 및 재개, ESP32의 두 코어에서 코드 실행, 각 작업에 필요한 적절한 스택 크기(메모리) 계산 방법을 배웁니다.

 

FreeRTOS는 ESP32가 여러 작업을 동시에 원활하고 효율적으로 관리하고 실행할 수 있도록 하는 실시간 운영 체제입니다. ESP32에 내장되어 있으며 Arduino 코어와 Espressif IoT 개발 프레임워크(IDF)와 완벽하게 통합되어 있습니다.

 

 

FreeRTOS를 사용한 ESP32 - 시작 가이드 작업 생성

 

이 튜토리얼에서는 다음 주제를 다루겠습니다.

 

FreeRTOS란 무엇인가

FreeRTOS가 ESP32와 함께 유용한 이유는 무엇입니까?

FreeRTOS 기본 개념

1) 작업 생성

2) 작업 일시 중지 및 재개

3) 여러 작업 생성 및 실행

4) 다중 작업 생성 및 실행(듀얼 코어)

5) 작업 메모리 사용량

 

필수 조건

 

이 튜토리얼은 아두이노 코어를 사용하여 ESP32를 프로그래밍하는 데 중점을 둡니다. 진행하기 전에 아두이노 IDE에 ESP32 아두이노 코어가 설치되어 있어야 합니다. 아직 아두이노 IDE에 ESP32를 설치하지 않았다면 다음 튜토리얼을 따라 설치하세요.

 

Arduino IDE에 ESP32 보드 설치(Windows, Mac OS X 및 Linux 지침)

 

FreeRTOS란 무엇인가요?

 

FreeRTOS는 가벼운 오픈 소스 실시간 운영 체제(RTOS)입니다. 각 작업에 고유한 우선순위 와 실행 일정을 가진 여러 작업을 동시에 실행할 수 있는 프레임워크를 제공합니다 . FreeRTOS를 사용하면 코드를 한 줄씩 실행하는 대신, ESP32가 작업 우선순위에 따라 빠르게 전환할 수 있는 독립적인 작업을 생성할 수 있습니다.

 

 

FreeRTOS 로고

 

또한 대기열 과 세마포어 와 같은 도구를 제공하여 작업 간에 원활하게 통신할 수 있습니다.

 

이렇게 하면 코드가 더 체계적이고 반응성이 좋아집니다. 특히 ESP32가 센서 읽기, HTTP 요청 처리, 인터럽트를 수신하는 동안 화면에 정보 표시 등 여러 작업을 동시에 처리할 때 더욱 그렇습니다.

 

FreeRTOS가 ESP32와 함께 유용한 이유는 무엇입니까?

 

FreeRTOS 실시간 운영체제는 ESP32에 내장되어 있으며, Espressif IDF와 Arduino 코어에 통합되어 있습니다. 다음을 지원합니다.

 

  • 작업 관리 : 작업을 생성, 중단, 재개 또는 삭제합니다(이 튜토리얼에서 다룹니다).
  • 스케줄링 : 작업에 우선순위를 지정하여 특정 순서에 따라 실행할 수 있습니다(이 튜토리얼에서 다룹니다).
  • 작업 간 통신 : 큐, 세마포어, 뮤텍스 등을 사용하면 ESP32를 충돌시키지 않고도 작업 간의 원활한 통신을 보장할 수 있습니다.
  • 듀얼 코어 지원 : ESP32의 코어 0 또는 코어 1에서 작업을 실행할 수 있습니다(이 튜토리얼에서도 다루지만 ESP32에서 듀얼 코어를 사용하는 방법에 대한 보다 자세한 가이드는 이 가이드를 확인하세요: Arduino IDE에서 ESP32 듀얼 코어를 사용하는 방법)

 

그럼, 요약하자면…

 

FreeRTOS는 멀티태스킹을 지원하여 센서 판독, Wi-Fi, 디스플레이 업데이트 등 여러 작업을 서로 방해하지 않고 실행할 수 있으므로 ESP32와 함께 사용하기에 유용합니다.

 

또한 ESP32 듀얼 코어 프로세서를 활용하여 특정 코어에 작업을 할당하여 성능을 향상시킬 수 있습니다.

 

또한 우선순위 기반 스케줄링을 사용하면 시간에 민감한 작업을 즉시 실행할 수 있으므로 ESP32 GPIO에서 감지된 외부 이벤트에 신속하게 대응해야 하는 실시간 애플리케이션에 이상적입니다.

 

FreeRTOS 기본 개념

 

실제 예제를 살펴보기 전에 FreeRTOS와 관련된 몇 가지 기본 개념을 살펴보겠습니다.

 

  • 작업 : 작업은 동시에 실행되는 독립적인 함수로, 각각 고유한 스택(할당된 메모리 사용량)과 우선순위를 갖습니다. 작업은 실행 중, 준비됨, 차단됨 또는 일시 중단됨과 같은 상태일 수 있습니다.
  • 스케줄러 : 스케줄러는 우선순위에 따라 실행할 작업을 결정합니다. 이는 선점형 스케줄러로, 우선순위가 낮은 작업을 언제든지 중단하고 우선순위가 높은 작업을 실행할 수 있으므로, 중요한 작업이 준비되는 즉시 실행됩니다.
  • 우선순위 : 숫자가 높을수록 우선순위가 높습니다(예: 1 = 낮음, 5 = 높음)

 

1) 작업 생성

 

이 예제에서는 FreeRTOS에서 1초마다 LED를 깜빡이는 작업을 생성하는 방법을 보여드리겠습니다. LED를 GPIO 2에 연결하겠습니다. 대신, LED를 연결하지 않고 ESP32 내장 LED에서 결과를 확인할 수 있습니다.

 

필요한 부품

 

이 예에서는 다음 부품이 필요합니다.

 

  • ESP32 보드 - 최고의 ESP32 개발 보드를 읽어보세요
  • LED
  • 220옴 저항기 (또는 유사한 값)
  • 브레드보드
  • 점퍼 와이어

 

회로 배선

 

다음 회로도와 같이 LED를 GPIO 2에 연결합니다.

 

 

220Ω 저항을 통해 GPIO2에 연결된 LED가 있는 ESP32

 

ESP32: FreeRTOS 작업 생성 – Arduino 코드

 

다음 코드는 GPIO 2에 연결된 LED를 1초마다 깜박이는 작업을 만듭니다.

 

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp32-freertos-arduino-tasks/
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#define LED_PIN 2

// Declare task handle
TaskHandle_t BlinkTaskHandle = NULL;

void BlinkTask(void *parameter) {
  for (;;) { // Infinite loop
    digitalWrite(LED_PIN, HIGH);
    Serial.println("BlinkTask: LED ON");
    vTaskDelay(1000 / portTICK_PERIOD_MS); // 1000ms
    digitalWrite(LED_PIN, LOW);
    Serial.println("BlinkTask: LED OFF");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    Serial.print("BlinkTask running on core ");
    Serial.println(xPortGetCoreID());
  }
}

void setup() {
  Serial.begin(115200);
  
  pinMode(LED_PIN, OUTPUT);

  xTaskCreatePinnedToCore(
    BlinkTask,         // Task function
    "BlinkTask",       // Task name
    10000,             // Stack size (bytes)
    NULL,              // Parameters
    1,                 // Priority
    &BlinkTaskHandle,  // Task handle
    1                  // Core 1
  );
}

void loop() {
  // Empty because FreeRTOS scheduler runs the task
}

 

코드는 어떻게 작동하나요?

 

먼저 LED에 연결될 GPIO를 정의합니다.

 

#define LED_PIN 2

 

작업 핸들

 

그런 다음 작업 핸들을 선언합니다. TaskHandle_t는 FreeRTOS 작업을 가리키는 변수로, 작업을 재개하거나 중지하거나 삭제하는 등의 제어가 가능합니다. 이 예제에서는 작업 핸들이 필요하지 않지만, 구현 방법을 보여주기 위해 생성합니다.

 

TaskHandle_t BlinkTaskHandle = NULL;

 

 

Task Function

 

그런 다음 작업을 만듭니다. 작업은 원하는 명령을 실행하는 함수일 뿐입니다. 다음은블링크태스크이 예제에서 사용된 함수입니다.

 

void BlinkTask(void *parameter) {
  for (;;) { // Infinite loop
    digitalWrite(LED_PIN, HIGH);
    Serial.println("BlinkTask: LED ON");
    vTaskDelay(1000 / portTICK_PERIOD_MS); // 1000ms
    digitalWrite(LED_PIN, LOW);
    Serial.println("BlinkTask: LED OFF");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    Serial.print("BlinkTask running on core ");
    Serial.println(xPortGetCoreID());
  }
}

 

이 기능은 FreeRTOS 스케줄러에서 독립적으로 실행되는 특수한 기능인 FreeRTOS 작업으로, ESP32에서 멀티태스킹을 가능하게 합니다.

 

FreeRTOS 작업은 void를 반환해야 하며 함수에 데이터를 전달하는 데 사용할 수 있는 단일 인수를 허용해야 합니다(이 경우에는 사용되지 않음).

 

void BlinkTask(void *parameter) {

 

for(;;)은 명시적으로 중지될 때까지 작업이 무한정 실행되도록 하는 무한 루프를 생성합니다. 이는 아두이노 코드에서 사용되는 loop() 함수와 유사합니다.

 

for (;;) { // Infinite loop

 

그런 다음 digitalWrite() 함수를 사용하여 LED를 켜고 끕니다. 일반적인 delay() 함수 대신 vTaskDelay()를 사용한다는 점에 유의하십시오.

 

digitalWrite(LED_PIN, HIGH);
Serial.println("BlinkTask: LED ON");
vTaskDelay(1000 / portTICK_PERIOD_MS); // 1000ms
digitalWrite(LED_PIN, LOW);
Serial.println("BlinkTask: LED OFF");
vTaskDelay(1000 / portTICK_PERIOD_MS);

 

 

vTaskDelay()

 

vTaskDelay()는 지정된 틱 수만큼 작업을 일시 중지하고 해당 시간 동안 다른 작업이 실행될 수 있도록 하는 FreeRTOS 함수입니다. 다음과 같이 코드를 차단하지 않습니다.지연(). 그vTaskDelay()이 함수는 틱을 허용합니다. ESP32에서 각 틱은 일반적으로 1ms입니다(다음으로 정의됨).포트틱_기간_MS), 그래서vTaskDelay(1000 / portTICK_PERIOD_MS)작업을 1000ms(1초) 동안 일시 중지합니다.

 

vTaskDelay(1000 / portTICK_PERIOD_MS);

 

Core IDE 받기

 

시연을 위해 작업이 실행 중인 코어 번호도 출력합니다. 해당 정보는 xPortGetCoreID() 함수를 호출하여 얻을 수 있습니다.

 

Serial.print("BlinkTask running on core ");

Serial.println(xPortGetCoreID());

 

 

Setup()

 

setup() 함수에서 시리얼 모니터를 초기화하고 LED를 출력으로 설정합니다.

 

void setup() {

  Serial.begin(115200);

  pinMode(LED_PIN, OUTPUT);

 

작업 만들기

 

이제 FreeRTOS 태스크를 실제로 생성하고 특정 코어에 할당하려면 xTaskCreatePinnedToCore() 함수를 사용해야 합니다. 이 함수는 태스크 함수, 이름, 스택 크기, 매개변수, 우선순위 및 태스크 핸들도 지정합니다.

 

xTaskCreatePinnedToCore(
  BlinkTask,      // Task function
  "BlinkTask",   // Task name
  10000,           // Stack size (bytes)
  NULL,            // Parameters
  1,                   // Priority
  &BlinkTaskHandle,  // Task handle
  1                  // Core 1
);

 

 

작업 함수는 앞서 정의한 BlinkTask입니다. 작업에 이름을 지정할 수도 있습니다. 이 경우 "BlinkTask"입니다.

 

 

 

BlinkTask, // Task function

"BlinkTask", // Task name

 

작업 스택 크기를 10000바이트로 설정했습니다. 작업 스택 크기는 작업이 실행되는 동안 변수, 함수 호출 및 임시 데이터를 저장하기 위해 할당된 메모리 양으로, ESP32 충돌 없이 작동할 수 있는 충분한 공간을 확보하는 데 사용됩니다. 스택 크기는 바이트 단위로 정의됩니다. 이후 예제에서 작업의 스택 크기를 구하는 방법을 살펴보겠습니다.

 

10000, // Stack size (bytes)

 

이 경우, 우리의 작업에는 매개변수가 없으므로 해당 매개변수를 다음과 같이 설정합니다.널(NULL).

 

NULL, // Parameters

 

우리는 작업의 우선순위를 부여합니다1숫자가 높을수록 우선순위가 높습니다. 이 경우에는 작업이 하나뿐이므로 크게 중요하지 않습니다.

 

1, // Priority

 

또한 코드 시작 부분에서 생성한 작업에 대한 작업 핸들을 정의합니다.

 

&BlinkTaskHandle, // Task handle

 

마지막으로, 작업을 실행할 코어를 정의합니다. ESP32에는 코어 0과 코어 1이라는 두 개의 코어가 있습니다. 이 예시에서는 코어 1을 사용합니다.

 

1 // Core 1

 

 

loop()

 

loop() 함수는 비어 있습니다. FreeRTOS 스케줄러가 작업을 실행하기 때문입니다. 그러나 loop()에 코드를 추가하여 원하는 다른 명령을 실행할 수도 있습니다.

 

void loop() {

 

// Empty because FreeRTOS scheduler runs the task

 

}

 

데모

 

ESP32 보드에 코드를 업로드하세요. 업로드 후 시리얼 모니터를 115200 통신 속도로 열어 보세요. 비슷한 결과가 나올 것입니다.

 

 

 

Freertos를 사용하여 단일 작업을 실행하는 ESP32 - 직렬 모니터 데모

 

동시에 LED가 1초마다 깜박여야 합니다.

 

 

ESP32 LED 깜박임(LED 꺼짐)

 

ESP32 깜박이는 LED(LED 켜짐)

 

2) 작업 일시 중지 및 재개

 

이 예제에서는 ESP32에서 FreeRTOS 작업을 일시 중지하고 다시 시작하는 방법을 알아봅니다. LED를 깜빡이는 작업을 만들고 푸시 버튼을 사용하여 제어합니다. 버튼을 누르면 작업이 일시 중지되어 깜빡임을 멈추고, 다시 누르면 작업이 다시 시작됩니다.

 

필요한 부품

 

이 예에서는 다음 부품이 필요합니다.

 

  • ESP32 보드 - 최고의 ESP32 개발 보드를 읽어보세요
  • LED
  • 220옴 저항기 (또는 유사한 값)
  • 푸시버튼
  • 브레드보드
  • 점퍼 와이어

 

회로 배선

 

이전 회로에 푸시 버튼을 추가해 보세요. 푸시 버튼을 GPIO 23에 연결했지만, 다른 적합한 GPIO를 사용해도 됩니다.

 

 

GPIO 2의 LED와 GPIO 23의 푸시 버튼에 연결된 ESP32

 

추천 자료: ESP32 핀아웃 참조: 어떤 GPIO 핀을 사용해야 할까요?

 

ESP32: FreeRTOS 작업 일시 중지 및 재개 – Arduino 코드

 

다음 코드는 깜박이는 FreeRTOS 작업을 일시 중단하거나 재개하기 위해 푸시 버튼이 눌리는 것을 수신합니다.

 

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp32-freertos-arduino-tasks/
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#define LED1_PIN 2
#define BUTTON_PIN 23

// Task handle
TaskHandle_t BlinkTaskHandle = NULL;

// Volatile variables for ISR
volatile bool taskSuspended = false;
volatile uint32_t lastInterruptTime = 0;
const uint32_t debounceDelay = 100; // debounce period

void IRAM_ATTR buttonISR() {
  // Debounce
  uint32_t currentTime = millis();
  if (currentTime - lastInterruptTime < debounceDelay) {
    return;
  }
  lastInterruptTime = currentTime;

  // Toggle task state
  taskSuspended = !taskSuspended;
  if (taskSuspended) {
    vTaskSuspend(BlinkTaskHandle);
    Serial.println("BlinkTask Suspended");
  } else {
    vTaskResume(BlinkTaskHandle);
    Serial.println("BlinkTask Resumed");
  }
}

void BlinkTask(void *parameter) {
  for (;;) { // Infinite loop
    digitalWrite(LED1_PIN, HIGH);
    Serial.println("BlinkTask: LED ON");
    vTaskDelay(1000 / portTICK_PERIOD_MS); // 1000ms
    digitalWrite(LED1_PIN, LOW);
    Serial.println("BlinkTask: LED OFF");
    Serial.print("BlinkTask running on core ");
    Serial.println(xPortGetCoreID());
    vTaskDelay(1000 / portTICK_PERIOD_MS);
  }
}

void setup() {
  Serial.begin(115200);


  // Initialize pins
  pinMode(LED1_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP); // Internal pull-up resistor

  // Attach interrupt to button
  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonISR, FALLING);

  // Create task
  xTaskCreatePinnedToCore(
    BlinkTask,         // Task function
    "BlinkTask",       // Task name
    10000,             // Stack size (bytes)
    NULL,              // Parameters
    1,                 // Priority
    &BlinkTaskHandle,  // Task handle
    1                  // Core 1
  );
}

void loop() {
  // Empty because FreeRTOS scheduler runs the task
}

 

 

코드는 어떻게 작동하나요?

 

이전 예제에서 작업을 생성하는 방법을 이미 다루었습니다. 따라서 이 섹션에서는 관련 부분만 다루겠습니다.

 

LED와 푸시 버튼에 대한 GPIO를 정의합니다.

 

#define LED1_PIN 2

#define BUTTON_PIN 23

 

ISR(푸시버튼용 인터럽트 서비스 루틴)에서 사용될 휘발성 변수를 생성합니다. taskSuspended 변수는 작업이 일시 중지되었는지 여부를 판단하는 데 사용되며, lastInterruptTime 및 debounceDelay 변수는 푸시버튼의 디바운싱을 위해 필요합니다.

 

// Volatile variables for ISR
volatile bool taskSuspended = false;
volatile uint32_t lastInterruptTime = 0;
const uint32_t debounceDelay = 100; // debounce period

 

 

푸시버튼 누름을 감지하기 위해 인터럽트를 사용합니다. 인터럽트를 사용할 때는 인터럽트 서비스 루틴(ESP32 RAM에서 실행되는 함수)을 정의해야 합니다. 이 경우 buttonISR() 함수를 생성합니다. RAM에서 실행되도록 함수 정의에 IRAM_ATTR을 추가해야 합니다.

 

void IRAM_ATTR buttonISR() {

 

추천 자료: 인터럽트와 타이머를 사용한 PIR 모션 센서가 있는 ESP32

 

먼저 푸시버튼의 디바운싱을 수행하고, 버튼 누름이 감지되면 taskSuspended 플래그 변수의 상태를 토글합니다.

 

// Debounce
uint32_t currentTime = millis();
if (currentTime - lastInterruptTime < debounceDelay) {
  return;
}
lastInterruptTime = currentTime;

// Toggle task state
taskSuspended = !taskSuspended;

 

 

작업 일시 중지 및 재개

 

그러면, 만약작업이 중단됨변수는진실, 우리는 호출합니다vTaskSuspend()함수를 호출하고 작업 핸들을 인수로 전달합니다. 여기서 작업 핸들의 용도 중 하나를 확인할 수 있습니다. 작업 핸들은 작업을 제어하기 위해 해당 작업을 참조하는 방법입니다.

 

if (taskSuspended) {

    vTaskSuspend(BlinkTaskHandle);

 

taskSuspended 변수가 false인 경우, vTaskResume() 함수를 호출하여 작업의 실행을 재개합니다.

 

} else {

    vTaskResume(BlinkTaskHandle);

 

 

Setup()

 

setup() 함수 내에서 푸시버튼을 다음과 같이 인터럽트로 선언해야 합니다:

 

attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonISR, FALLING);

 

나머지 코드는 이전 예제와 비슷합니다.

 

데모

 

ESP32 보드에 코드를 업로드하세요.

 

LED가 깜빡이기 시작합니다. 푸시 버튼을 누르면 LED가 깜빡임을 멈춥니다. 다시 누르면 LED가 다시 깜빡이기 시작합니다.

 

 

푸시 버튼으로 깜박이는 LED가 있는 ESP32로 작업을 일시 중지하고 다시 시작할 수 있습니다.

 

다음 영상을 시청하면 작동 방식을 확인할 수 있습니다.

 

동시에, 직렬 모니터에서 모든 정보를 얻어야 합니다.

 

 

 

ESP32 FreeRTOS 작업 일시 중지 및 재개 - 직렬 모니터 데모

 

3) 여러 작업 생성 및 실행

 

이 섹션에서는 여러 FreeRTOS 작업을 동시에 생성하고 실행하는 방법을 알아봅니다. 예를 들어, 두 개의 서로 다른 LED를 깜빡이는 두 개의 작업을 만들어 보겠습니다.

 

필요한 부품

 

이 예에서는 다음 부품이 필요합니다.

 

  • ESP32 보드 - 최고의 ESP32 개발 보드를 읽어보세요
  • 2개의 LED
  • 2x 220옴 저항기 (또는 유사한 값)
  • 푸시버튼
  • 브레드보드
  • 점퍼 와이어

 

회로 배선

 

ESP32에 두 개의 LED를 연결합니다. GPIO 2와 4를 사용합니다. 코드를 적절히 수정하면 다른 적합한 GPIO를 사용할 수 있습니다.

 

 

ESP32에 연결된 두 개의 LED - 한 LED는 GPIO2에 연결되고 다른 한 LED는 GPIO4에 연결됨

 

 

ESP32: 여러 작업 생성 및 실행 – Arduino 코드

 

다음 코드는 두 개의 개별 FreeRTOS 작업을 생성하며, 각 작업은 서로 다른 속도로 LED를 깜박입니다. FreeRTOS를 사용하면 두 작업을 서로 방해하지 않고 독립적으로 실행할 수 있습니다. 이 방법은 기존 Arduino 코드에서 지연이나 복잡한 타이밍 로직을 사용하는 것보다 훨씬 깔끔하고 효율적입니다.

 

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp32-freertos-arduino-tasks/
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#define LED1_PIN 2
#define LED2_PIN 4

TaskHandle_t Task1Handle = NULL;
TaskHandle_t Task2Handle = NULL;

void Task1(void *parameter) {
  pinMode(LED1_PIN, OUTPUT);
  for (;;) {
    digitalWrite(LED1_PIN, HIGH);
    Serial.println("Task1: LED1 ON");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    digitalWrite(LED1_PIN, LOW);
    Serial.println("Task1: LED1 OFF");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
  }
}

void Task2(void *parameter) {
  pinMode(LED2_PIN, OUTPUT);
  for (;;) {
    digitalWrite(LED2_PIN, HIGH);
    Serial.println("Task2: LED2 ON");
    vTaskDelay(333 / portTICK_PERIOD_MS);
    digitalWrite(LED2_PIN, LOW);
    Serial.println("Task2: LED2 OFF");
    vTaskDelay(333 / portTICK_PERIOD_MS);
  }
}

void setup() {
  Serial.begin(115200);

  xTaskCreatePinnedToCore(
    Task1,             // Task function
    "Task1",           // Task name
    10000,             // Stack size (bytes)
    NULL,              // Parameters
    1,                 // Priority
    &Task1Handle,      // Task handle
    1                  // Core 1
  );

  xTaskCreatePinnedToCore(
    Task2,            // Task function
    "Task2",          // Task name
    10000,            // Stack size (bytes)
    NULL,             // Parameters     
    1,                // Priority
    &Task2Handle,     // Task handle
    1                 // Core 1
  );
}

void loop() {
  // Empty because FreeRTOS scheduler runs the task
}

 

 

코드는 어떻게 작동하나요?

 

여러 작업을 만들고 실행하는 것은 단일 작업을 만들고 실행하는 것만큼 간단합니다.

 

먼저, 작업을 정의해야 합니다. 우리의 경우, 서로 다른 속도로 두 개의 서로 다른 LED를 깜빡이게 하는 두 가지 다른 작업이 있습니다. 이를 Task1과 Task2라고 부릅니다.

 

void Task1(void *parameter) {
  pinMode(LED1_PIN, OUTPUT);
  for (;;) {
    digitalWrite(LED1_PIN, HIGH);
    Serial.println("Task1: LED1 ON");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    digitalWrite(LED1_PIN, LOW);
    Serial.println("Task1: LED1 OFF");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
  }
}

void Task2(void *parameter) {
  pinMode(LED2_PIN, OUTPUT);
  for (;;) {
    digitalWrite(LED2_PIN, HIGH);
    Serial.println("Task2: LED2 ON");
    vTaskDelay(333 / portTICK_PERIOD_MS);
    digitalWrite(LED2_PIN, LOW);
    Serial.println("Task2: LED2 OFF");
    vTaskDelay(333 / portTICK_PERIOD_MS);
  }
}

 

 

그런 다음 setup()에서 xTaskCreatePinnedToCore를 사용하여 태스크를 생성하기만 하면 됩니다. 이 예제에서는 두 태스크 모두 ESP32의 코어 1에서 실행되며 동일한 우선순위를 가집니다.

 

xTaskCreatePinnedToCore(
  Task1,             // Task function
  "Task1",           // Task name
  10000,             // Stack size (bytes)
  NULL,              // Parameters
  1,                 // Priority
  &Task1Handle,      // Task handle
  1                  // Core 1
);

xTaskCreatePinnedToCore(
  Task2,            // Task function
  "Task2",          // Task name
  10000,            // Stack size (bytes)
  NULL,             // Parameters     
  1,                // Priority
  &Task2Handle,     // Task handle
  1                 // Core 1
);

 

 

데모

 

보드에 코드를 업로드하세요. 업로드 후 ESP32 RST 버튼을 눌러 코드 실행을 시작하세요. 두 개의 LED가 서로 다른 속도로 깜박일 것입니다.

 

 

ESP32에서 두 개의 FreeRTOS 작업 실행 - 두 개의 LED가 서로 다른 속도로 깜박임

 

다음 영상에서는 작동 방식을 더 잘 이해할 수 있습니다.

 

보시다시피, 지연이나 기타 복잡한 타이밍 계산을 사용하는 대신 FreeRTOS 작업을 생성하면 이를 달성하는 것이 매우 간단합니다.

 

4) 다른 코어에서 여러 작업 생성 및 실행(ESP32 듀얼 코어)

 

대부분의 ESP32 모델에는 코어 0과 코어 1이라는 두 개의 코어가 있습니다. 기본적으로 Arduino IDE에서 코드를 실행하면 코드는 코어 1에서 실행됩니다. 이 섹션에서는 다양한 ESP32 코어에서 다양한 작업을 실행하는 방법을 보여드리겠습니다.

 

회로 배선

 

이전 예제의 회로를 두 개의 LED로 유지합니다.

 

여러 코어의 작업

 

다음 코드는 이전 예제와 유사하지만, 각 작업이 서로 다른 코어에서 실행됩니다. 또한, 각 작업에 어떤 코어에서 실행 중인지 다시 확인하는 줄을 추가합니다.

 

 

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp32-freertos-arduino-tasks/
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#define LED1_PIN 2
#define LED2_PIN 4

TaskHandle_t Task1Handle = NULL;
TaskHandle_t Task2Handle = NULL;

void Task1(void *parameter) {
  pinMode(LED1_PIN, OUTPUT);
  for (;;) {
    digitalWrite(LED1_PIN, HIGH);
    Serial.println("Task1: LED1 ON");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    digitalWrite(LED1_PIN, LOW);
    Serial.println("Task1: LED1 OFF");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    Serial.print("Task 1 running on core ");
    Serial.println(xPortGetCoreID());
  }
}

void Task2(void *parameter) {
  pinMode(LED2_PIN, OUTPUT);
  for (;;) {
    digitalWrite(LED2_PIN, HIGH);
    Serial.println("Task2: LED2 ON");
    vTaskDelay(333 / portTICK_PERIOD_MS);
    digitalWrite(LED2_PIN, LOW);
    Serial.println("Task2: LED2 OFF");
    vTaskDelay(333 / portTICK_PERIOD_MS);
    Serial.print("Task 2 running on core ");
    Serial.println(xPortGetCoreID());
  }
}

void setup() {
  Serial.begin(115200);

  xTaskCreatePinnedToCore(
    Task1,             // Task function
    "Task1",           // Task name
    10000,             // Stack size (bytes)
    NULL,              // Parameters
    1,                 // Priority
    &Task1Handle,      // Task handle
    1                  // Core 1
  );

  xTaskCreatePinnedToCore(
    Task2,            // Task function
    "Task2",          // Task name
    10000,            // Stack size (bytes)
    NULL,             // Parameters     
    1,                // Priority
    &Task2Handle,     // Task handle
    0                 // Core 0
  );
}

void loop() {
  // Empty because FreeRTOS scheduler runs the task
}

 

 

코드는 어떻게 작동하나요?

 

이 예제의 유일한 차이점은 아래와 같이 Task2가 코어 0에서 실행되도록 정의한다는 점입니다.

 

xTaskCreatePinnedToCore(
  Task2,            // Task function
  "Task2",          // Task name
  10000,            // Stack size (bytes)
  NULL,             // Parameters     
  1,                // Priority
  &Task2Handle,     // Task handle
  0                 // Core 0
);

 

 

데모

 

ESP32에 코드를 업로드하면 이전 예제와 같은 결과가 나옵니다. 두 개의 LED가 서로 다른 속도로 깜박입니다.

 

 

ESP32에서 두 개의 FreeRTOS 작업 실행 - 두 개의 LED가 서로 다른 속도로 깜박임

 

직렬 모니터에서는 각 작업이 다른 코어에서 실행되고 있는지 실제로 확인할 수 있습니다.

 

두 개의 코어에서 작업을 실행하는 ESP32 - 직렬 모니터 데모

 

5) 작업 메모리 사용량

 

이 섹션에서는 작업에 대한 스택과 힙 사용량을 측정하여 메모리 할당을 최적화하는 방법을 살펴보겠습니다.

 

스택 과 힙은 ESP32에서 FreeRTOS 작업에 사용되는 두 가지 유형의 메모리로, 각각 프로그램의 메모리 요구 사항을 관리하는 데 고유한 역할을 합니다.

 

스택 사용량이란 정확히 무엇일까요? 스택은 각 FreeRTOS 작업의 전용 메모리 영역으로, 로컬 변수, 함수 호출 정보, 실행 중 작업 상태와 같은 임시 데이터를 저장하는 데 사용됩니다. 각 작업은 자체 스택을 가지며, 이는 작업 생성 시 할당됩니다. 이전 예제에서 각 작업에 10,000바이트의 스택을 할당하는 것을 보셨을 것입니다.

 

스택 사용량을 확인하기 위해 작업 내부에서 호출할 수 있는 함수가 있습니다.uxTaskGetStackHighWaterMark()함수입니다. 해당 함수는 사용되지 않는 할당된 스택 크기를 확인합니다.

 

힙 사용량이란 무엇인가요? 힙은 ESP32 SRAM에 있는 공유 메모리 풀로, FreeRTOS 또는 Arduino 코어에서 할당한 작업 스택, 버퍼 및 기타 런타임 데이터를 포함한 동적 메모리 할당에 사용됩니다.xPortGetFreeHeapSize()우리 코드에서 자유 힙을 결정하는 함수입니다.

 

작업 스택 크기 및 여유 힙 – 코드

 

다음 코드는 이전 프로젝트의 코드와 유사하며, 두 개의 서로 다른 LED를 깜빡이는 두 가지 함수가 있습니다. 빈 스택과 빈 힙을 확인합니다.

 

다음 코드를 보드에 업로드하세요.

 

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp32-freertos-arduino-tasks/
  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#define LED1_PIN 2
#define LED2_PIN 4

TaskHandle_t Task1Handle = NULL;
TaskHandle_t Task2Handle = NULL;

void Task1(void *parameter) {
  pinMode(LED1_PIN, OUTPUT);
  for (;;) {
    digitalWrite(LED1_PIN, HIGH);
    Serial.println("Task1: LED1 ON");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    digitalWrite(LED1_PIN, LOW);
    Serial.println("Task1: LED1 OFF");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
    Serial.printf("Task1 Stack Free: %u bytes\n", uxTaskGetStackHighWaterMark(NULL));
  }
}

void Task2(void *parameter) {
  pinMode(LED2_PIN, OUTPUT);
  for (;;) {
    digitalWrite(LED2_PIN, HIGH);
    Serial.println("Task2: LED2 ON");
    vTaskDelay(333 / portTICK_PERIOD_MS);
    digitalWrite(LED2_PIN, LOW);
    Serial.println("Task2: LED2 OFF");
    vTaskDelay(333 / portTICK_PERIOD_MS);
    Serial.printf("Task2 Stack Free: %u bytes\n", uxTaskGetStackHighWaterMark(NULL));
  }
}

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.printf("Starting FreeRTOS: Memory Usage\nInitial Free Heap: %u bytes\n", xPortGetFreeHeapSize());

  xTaskCreatePinnedToCore(
    Task1,
    "Task1",
    10000,
    NULL,
    1,
    &Task1Handle,
    1
  );

  xTaskCreatePinnedToCore(
    Task2,
    "Task2",
    10000,
    NULL,
    1,
    &Task2Handle,
    1
  );
}

void loop() {
  static uint32_t lastCheck = 0;
  if (millis() - lastCheck > 5000) {
    Serial.printf("Free Heap: %u bytes\n", xPortGetFreeHeapSize());
    lastCheck = millis();
  }
}

 

 

 

데모

 

보드에 코드를 업로드한 후에는 직렬 모니터에서 비슷한 내용을 볼 수 있습니다.

 

 

FreeRTOS를 사용한 ESP32가 작업 스택의 최고 수위를 결정합니다.

 

uxTaskGetStackHighWaterMark는 Task1에 대해 8556바이트, Task2에 대해 8552바이트의 여유 공간을 보고합니다. 이는 각 작업이 최대 시점에 10000바이트 스택 중 1444~1448바이트를 사용함을 의미합니다. 따라서 각 작업에 할당된 스택 크기를 크게 줄일 수 있습니다.

 

 

적절한 스택 크기는 작업의 최대 사용량(1444바이트)과 예상치 못한 증가를 처리하기 위한 500~1000바이트의 안전 여유를 포함해야 합니다.

 

무료 힙의 경우, 제 환경에서는 xPortGetFreeHeapSize()가 247616바이트의 여유 공간을 보고합니다(스크린샷에 표시되지 않음). 이는 스택 할당(두 작업에 20000바이트) 및 기타 시스템 리소스 할당 후 남은 힙을 나타냅니다.

 

 

마무리하기

 

이 튜토리얼은 ESP32를 활용한 FreeRTOS에 대한 자세한 소개였습니다. 단일 및 다중 작업 생성, 각 작업에 코어 할당, 작업 일시 중지 및 재개, 그리고 작업 스택 계산 방법까지 알아보았습니다.

 

ESP32와 함께 FreeRTOS를 사용하는 것은 좋은 선택입니다. 간단한 방법으로 여러 작업을 동시에 수행할 수 있고, 가장 중요한 작업을 먼저 실행하도록 우선순위를 지정할 수 있기 때문입니다.

 

ESP32를 사용한 더 많은 FreeRTOS 튜토리얼:

 

FreeRTOS 대기열을 사용한 ESP32: 작업 간 통신(Arduino IDE)

 

이 가이드가 도움이 되었기를 바랍니다.

 

 

반응형