본문 바로가기

ESP32

FreeRTOS를 사용한 ESP32: 세마포어 시작하기

반응형

 

FreeRTOS를 사용한 ESP32: 세마포어 시작하기(Arduino IDE)

 

이 가이드에서는 Arduino IDE를 사용하여 ESP32에서 FreeRTOS 세마포어를 사용하는 방법을 소개합니다. 세마포어는 작업을 동기화하고 이벤트를 관리할 수 있는 신호(또는 플래그)와 같습니다. 이벤트 발생 또는 리소스 사용 가능 여부를 나타내는 데 사용할 수 있습니다. 큐와 달리 세마포어는 데이터를 전달하지 않습니다.

 

 

ESP32 FreeRTOS 세마포어 Arduino IDE 시작 가이드

 

세마포어에는 이진 세마포어 와 카운팅 세마포어 , 두 가지 유형이 있습니다 . 이 튜토리얼에서는 두 가지 유형의 세마포어가 어떻게 작동하는지 보여주는 두 가지 예제를 만들고 살펴보겠습니다.

 

FreeRTOS를 처음 사용하시나요? 이 튜토리얼부터 시작해 보세요: ESP32 with FreeRTOS (Arduino IDE) - 시작 가이드: 작업 생성

 

 

세마포어 소개

 

세마포어는 FreeRTOS에서 작업을 조정하는 데 사용되는 신호 전달 도구입니다. 일반적으로 이벤트 발생 또는 리소스 사용 가능 여부를 나타내는 데 사용됩니다. 큐와 달리 세마포어는 데이터를 전달하지 않고, 특정 이벤트 발생 시 동작을 트리거하는 데 사용되는 "카운트", "플래그", "신호"(또는 원하는 대로)만 전달합니다.

 

 

이진 세마포어가 작동하는 방식의 간단한 예

 

 

이 기능을 사용하면 작업이 버튼 눌림이나 동작 감지와 같은 이벤트를 기다리거나 이벤트 발생을 알릴 수 있습니다. 따라서 인터럽트 및 작업 동기화와 관련된 시나리오에서 특히 유용합니다.

 

세마포어는 데이터를 저장하지 않으므로 큐보다 메모리를 적게 소모하며 작업 간의 가벼운 이벤트 신호 전달에 이상적입니다.

 

세마포어에는 두 가지 유형이 있습니다.

 

  • Binary 이진 세마포어 : 단일 이벤트에 신호를 보냅니다. 비어 있거나(0) 가득 찼을(1) 수 있는 동기화 도구입니다. 작업이 진행되기 전에 대기 중임을 나타내는 신호와 같습니다.

 

  • 카운팅 세마포어 : 여러 이벤트를 추적합니다(동일한 이벤트가 여러 번 발생할 수 있음). 사용자가 정의한 최대 개수까지 이벤트 대기열을 구성하는 것과 같습니다. FreeRTOS 대기열과는 달리, 카운팅 세마포어는 데이터를 전달하지 않습니다. 동기화 신호만 전달합니다. 이 동작 방식은 이 예제의 뒷부분에서 더 잘 이해할 수 있을 것입니다.

 

세마포어 기본 함수

 

ESP32를 Arduino IDE에서 사용할 때 사용되는 이진 세마포어와 계수 세마포어의 기본 함수는 다음과 같습니다 . 다음에는 실제 예제를 통해 이러한 함수들을 살펴보겠습니다.

 

이진 세마포어 생성

 

이진 세마포어를 생성하려면 xSemaphoreCreateBinary() 함수를 사용하십시오. 성공 시 SemaphoreHandle_t 핸들을 반환하고, 생성 실패 시 NULL을 반환합니다.

 

카운팅 세마포어 만들기

 

카운팅 세마포어를 생성하려면 xSemaphoreCreateCounting() 함수를 사용하십시오. 성공 시 SemaphoreHandle_t 핸들을 반환하고, 생성 실패 시 NULL을 반환합니다. 인자로 최대 카운트 값을 전달하십시오.

 

세마포어 가져오기(세마포어에서 가져오기)

 

작업에서 xSemaphoreTake(semaphore, timeout) 함수를 사용하여 세마포어를 대기하거나 획득합니다. 이진 세마포어의 경우, 세마포어가 사용 가능해질 때까지(상태 1) 차단되며, 획득 시 0으로 설정됩니다.

카운팅 세마포어의 경우 카운트가 0보다 크면 카운트를 감소시키고, 카운트가 0이면 차단합니다. timeout 매개변수는 대기 시간(틱 단위)을 지정합니다. portMAX_DELAY는 무기한 대기함을 의미합니다. 즉, 세마포어 값을 획득할 수 있을 때까지 작업이 차단됩니다.

 

세마포어 제공

 

세마포어를 해제하려면 작업 내에서는 xSemaphoreGive() 함수를, 인터럽트 서비스 루틴(ISR)에서는 xSemaphoreGiveFromISR() 함수를 사용하십시오.

이진 세마포어의 경우 상태를 1로 설정하여 대기 중인 태스크의 차단 해제를 수행합니다(세마포어가 이미 1인 경우 무시됨). 카운팅 세마포어의 경우 카운트를 최대값(maxCount)까지 증가시켜 대기 중인 태스크의 차단 해제를 수행합니다. (최대값에 도달한 경우 무시됨)

 

예제 1: 바이너리 세마포어 - 버튼을 눌러 LED 토글

 

이 섹션에서는 바이너리 세마포어의 작동 방식과 실제 애플리케이션에서 구현하는 방법을 보여주는 간단한 예제를 만들어 보겠습니다. 이 예제에서는 푸시 버튼을 누르면 LED가 한 번 켜집니다. 버튼이 눌렸다는 신호를 보내기 위해 세마포어를 사용합니다. 동시에, 여러 작업을 동시에 실행할 수 있음을 보여주기 위해 다른 작업이 LED를 깜빡이도록 하겠습니다.

 

 

푸시 버튼 1개와 LED 2개가 있는 ESP32 - 바이너리 세마포어를 보여주는 예

 

그러면, 예시를 간략히 살펴보겠습니다.

 

  • 푸시버튼에 인터럽트를 추가해 보겠습니다. 푸시버튼이 눌리면 해당 ISR이 세마포어에 인터럽트를 전달하고, 세마포어를 1로 설정합니다.
  • 또 다른 작업이 있습니다.LED토글태스크()세마포어가 LED 상태를 토글할 때까지 대기합니다. ISR에서 세마포어를 받으면 이 작업이 실행되고 세마포어는 0으로 재설정됩니다. 세마포어가 1로 설정되고 버튼을 누르면 이 작업이 다시 실행됩니다.
  • 동시에 우리는 또 다른 작업을 가지고 있습니다.LEDBlinkTask()그러면 다른 LED의 밝기가 증가하거나 감소합니다.

 

필요한 부품

 

이 예제에 필요한 부품 목록은 다음과 같습니다.

 

ESP32 보드 - 최고의 ESP32 개발 보드를 읽어보세요

2개의 LED

2x 220옴 저항기 또는 유사한 값

푸시버튼

브레드보드

점퍼 와이어

 

 

회로 배선

 

다음 회로를 배선하세요.

 

  • 빨간색 LED가 연결됨GPIO 2
  • 파란색 LED가 연결됨GPIO 4
  • 푸시 버튼이 연결됨GPIO 23

 

다음의 개략도를 따라가보세요.

 

 

2개의 LED와 1개의 푸시 버튼이 있는 ESP32 회로도

 

 

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

 

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp32-freertos-semaphores-arduino/
  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 BUTTON_PIN 23
#define LED1_PIN 2   // Toggled LED
#define LED2_PIN 4   // Blinking LED

#define DEBOUNCE_DELAY 200

SemaphoreHandle_t buttonSemaphore = NULL;

volatile uint32_t lastInterruptTime = 0;

void IRAM_ATTR buttonISR() {
  uint32_t currentTime = millis();
  if (currentTime - lastInterruptTime > DEBOUNCE_DELAY) {
    BaseType_t higherPriorityTaskWoken = pdFALSE;
    xSemaphoreGiveFromISR(buttonSemaphore, &higherPriorityTaskWoken);
    lastInterruptTime = currentTime;
    if (higherPriorityTaskWoken) {
      portYIELD_FROM_ISR();
    }
  }
}

void LEDToggleTask(void *parameter) {
  pinMode(LED1_PIN, OUTPUT);
  bool ledState = false;
  for (;;) {
    if (xSemaphoreTake(buttonSemaphore, portMAX_DELAY)) {
      ledState = !ledState;
      digitalWrite(LED1_PIN, ledState ? HIGH : LOW);
      Serial.print("LEDToggleTask: LED1 ");
      Serial.println(ledState ? "ON" : "OFF");
    }
  }
}

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

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

  // Defining the button as an interrupt
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonISR, FALLING);

  buttonSemaphore = xSemaphoreCreateBinary();
  if (buttonSemaphore == NULL) {
    Serial.println("Failed to create semaphore!");
    while (1);
  }

  xTaskCreatePinnedToCore(
    LEDToggleTask,          // Task function
    "LEDToggleTask",        // Task name
    3000,                   // Stack size
    NULL,                   // Task parameters
    2,                      // Higher priority
    NULL,                   // Task handle
    1                       // Core ID
  );

  xTaskCreatePinnedToCore(
    LEDBlinkTask,           // Task function
    "LEDBlinkTask",         // Task name
    3000,                   // Stack size
    NULL,                   // Task parameters
    1,                      // Medium priority
    NULL,                   // Task handle
    1                       // Core ID
  );
}

void loop() {
  
}

 

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

 

푸시 버튼, 토글 LED, 깜박이는 LED에 대한 핀을 정의하는 것부터 시작합니다.

 

#define BUTTON_PIN 23

#define LED1_PIN 2 // Toggled LED

#define LED2_PIN 4 // Blinking LED

 

푸시버튼의 디바운스 지연을 밀리초 단위로 정의합니다.

 

#define DEBOUNCE_DELAY 200

 

buttonSemaphore라는 세마포어에 대한 핸들을 생성합니다.

 

SemaphoreHandle_t buttonSemaphore = NULL;

 

 

setup( )

 

먼저 setup() 함수를 설명한 후 작업을 분석해 보겠습니다.

먼저 푸시버튼을 인터럽트로 설정하고 해당 콜백 함수(ISR)를 지정합니다. 이 경우 buttonISR 함수로 호출됩니다.

 

pinMode(BUTTON_PIN, INPUT_PULLUP);

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

 

이전에 생성한 buttonSemaphore 핸들을 사용하여 xSemaphoreCreateBinary() 함수로 이진 세마포어를 생성합니다.

 

buttonSemaphore = xSemaphoreCreateBinary();
if (buttonSemaphore == NULL) {
  Serial.println("Failed to create semaphore!");
  while (1);
}

 

 

그런 다음, 서로 다른 우선순위로 LedToggleTask와 LEDBlinkTask를 생성합니다.

 

xTaskCreatePinnedToCore(
  LEDToggleTask,          // Task function
  "LEDToggleTask",        // Task name
  3000,                   // Stack size
  NULL,                   // Task parameters
  2,                      // Higher priority
  NULL,                   // Task handle
  1                       // Core ID
);

xTaskCreatePinnedToCore(
  LEDBlinkTask,           // Task function
  "LEDBlinkTask",         // Task name
  3000,                   // Stack size
  NULL,                   // Task parameters
  1,                      // Medium priority
  NULL,                   // Task handle
  1                       // Core ID
);

 

 

 

buttonISR()

 

다음 줄들은 buttonISR() 함수를 생성합니다.

 

void IRAM_ATTR buttonISR() {
  uint32_t currentTime = millis();
  if (currentTime - lastInterruptTime > DEBOUNCE_DELAY) {
    BaseType_t higherPriorityTaskWoken = pdFALSE;
    xSemaphoreGiveFromISR(buttonSemaphore, &higherPriorityTaskWoken);
    lastInterruptTime = currentTime;
    if (higherPriorityTaskWoken) {
      portYIELD_FROM_ISR();
    }
  }
}

 

버튼을 누르면,버튼ISR()함수가 실행됩니다. 유효한 눌림이 발생하면 FreeRTOS 세마포어 함수를 사용하여 다른 작업에 버튼 이벤트가 발생했음을 알립니다.

 

세마포어 제공

 

다음 줄은 핵심 세마포어 동작입니다. 이 코드는 세마포어를 "양도"합니다. 이 세마포어는 프로그램의 다른 부분(일반적으로 FreeRTOS 태스크—본 예제에서는 LEDToggleTask)에 버튼이 눌렸음을 알리는 신호 역할을 합니다.

 

xSemaphoreGiveFromISR(buttonSemaphore, &higherPriorityTaskWoken);

 

 

higherPriorityTaskWoken 및 portYIELD_FROM_ISR

 

higherPriorityTaskWoken 변수는 세마포어를 부여할 경우 현재 실행 중인 작업보다 우선순위가 높은 작업이 깨어나는지 확인하는 데 사용됩니다. 해당 작업이 깨어나는 경우, 포트 인터럽트 종료 직후 시스템이 즉시 해당 우선순위 높은 작업으로 전환하도록 portYIELD_FROM_ISR()을 호출합니다. 본 예제에서는 LEDToggleTask로 즉시 전환하고자 합니다.

 

다시 말해서:

 

  • 기본적으로 higherPriorityTaskWoken은 세마포어 언블록킹 시 더 중요한 작업이 있는지 확인하는 데 사용됩니다.
  • xSemaphoreGiveFromISR()에 전달하여 값을 업데이트할 수 있도록 합니다.
  • pdTRUE인 경우 portYIELD_FROM_ISR()을 호출하여 FreeRTOS가 즉시 해당 작업으로 전환하도록 합니다.

 

FreeRTOS는 이러한 방식으로 작업 스케줄링에 문제를 일으키지 않고 인터럽트를 통해 우선순위가 높은 작업을 안전하게 트리거합니다.

 

LEDToggleTask

 

LEDToggleTask()는 세마포어에 값이 있을 때 LED1의 상태를 전환합니다.

 

void LEDToggleTask(void *parameter) {
  pinMode(LED1_PIN, OUTPUT);
  bool ledState = false;
  for (;;) {
    if (xSemaphoreTake(buttonSemaphore, portMAX_DELAY)) {
      ledState = !ledState;
      digitalWrite(LED1_PIN, ledState ? HIGH : LOW);
      Serial.print("LEDToggleTask: LED1 ");
      Serial.println(ledState ? "ON" : "OFF");
    }
  }
}

 

 

LEDToggleTask() 작업이 실행되면 LED 핀을 출력으로 설정하고 무한 루프를 시작합니다. 루프 내부에서는 xSemaphoreTake(buttonSemaphore, portMAX_DELAY)를 사용하여 세마포어를 대기합니다. portMAX_DELAY는 세마포어에 값이 있을 때까지(버튼이 눌릴 때까지) 작업이 무기한 대기함을 의미합니다.

 

 

if (xSemaphoreTake(buttonSemaphore, portMAX_DELAY)) {

 

세마포어를 수신하면 작업은 LED 상태를 전환하고 이를 직렬 모니터에 출력합니다.

 

if (xSemaphoreTake(buttonSemaphore, portMAX_DELAY)) {
  ledState = !ledState;
  digitalWrite(LED1_PIN, ledState ? HIGH : LOW);
  Serial.print("LEDToggleTask: LED1 ");
  Serial.println(ledState ? "ON" : "OFF");

 

LEDBlinkTask

 

다른 작업 외에도, LEDBlinkTask라는 작업이 독립적으로 동시에 실행되며 LED를 무한히 깜빡입니다.

 

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

 

데모

 

보드에 코드를 업로드하세요. 업로드 후 시리얼 모니터를 115200 통신 속도로 열어주세요. ESP32 RST 버튼을 눌러 코드 실행을 시작하세요.

 

 

2개의 LED와 푸시 버튼이 있는 ESP32 - 푸시 버튼을 누르면 LED 상태가 전환됩니다.

 

GPIO 4에 연결된 LED는 250밀리초마다 깜빡입니다. GPIO 2에 연결된 LED의 상태를 전환하려면 푸시버튼을 누르세요. 아래 짧은 동영상에서 간단한 데모를 확인할 수 있습니다.

 

직렬 모니터에서도 비슷한 결과가 나올 것입니다.

 

 

ESP32 바이너리 세마포어 예제 - 직렬 모니터 데모

 

예제 2: 세마포어 카운팅

 

이 섹션에서는 카운팅 세마포어의 작동 방식을 보여주는 간단한 예제를 만들어 보겠습니다. 최대 5까지 카운트할 수 있는 카운팅 세마포어를 만들어 보겠습니다. 이 세마포어는 최대 5번의 버튼 누름을 필요로 합니다. 이 세마포어를 사용하여 세마포어에 저장된 값만큼 LED를 깜빡이는 또 다른 작업이 있습니다. 세마포어에서 값이 소모되면 다른 값을 추가할 수 있습니다.

 

요약하자면, 이 프로젝트가 어떻게 진행되는지 간략하게 설명하면 다음과 같습니다.

 

  • 푸시 버튼에 인터럽트를 연결합니다. 푸시 버튼이 눌리면 인터럽트 서비스 루틴(ISR)이 세마포어를 부여합니다. 최대 카운트는 5입니다.
  • LEDBlinkTask는 세마포어를 기다립니다. 세마포어를 받을 때마다 LED를 깜빡입니다. 세마포어에 현재 사용 가능한 카운트 수만큼 LED가 깜빡입니다.
  • LEDBlinkTask가 세마포어의 값을 소비하면, 푸시버튼 누름으로 추가된 새 카운트를 위한 "공간"이 생깁니다.
  • 동시에 LEDFadeTask라는 다른 태스크가 다른 LED를 페이드 처리합니다. 이 태스크는 FreeRTOS의 멀티태스킹 처리 능력을 보여주기 위해 사용됩니다.

 

 

필요 부품 및 배선도

 

이전 예와 동일합니다.

 

코드

 

다음 코드를 Arduino IDE에 복사합니다.

 

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp32-freertos-semaphores-arduino/
  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 BUTTON_PIN 23
#define LED1_PIN 2    // Blinking LED
#define LED2_PIN 4    // Fading LED

#define DEBOUNCE_DELAY 200 // debounce for the pushbutton in milliseconds

#define SEMAPHORE_MAX_COUNT 5

SemaphoreHandle_t buttonSemaphore = NULL;
volatile uint32_t lastInterruptTime = 0;

void IRAM_ATTR buttonISR() {
  uint32_t currentTime = millis();
  if (currentTime - lastInterruptTime > DEBOUNCE_DELAY) {
    BaseType_t higherPriorityTaskWoken = pdFALSE;
    if (xSemaphoreGiveFromISR(buttonSemaphore, &higherPriorityTaskWoken) == pdTRUE) {
      Serial.println("buttonISR: Gave semaphore token");
    } else {
      Serial.println("buttonISR: Semaphore full");
    }
    lastInterruptTime = currentTime;
    if (higherPriorityTaskWoken) {
      portYIELD_FROM_ISR();
    }
  }
}

void LEDBlinkTask(void *parameter) {
  pinMode(LED1_PIN, OUTPUT);
  for (;;) {
    
    // Get and print the current semaphore count
    UBaseType_t count = uxSemaphoreGetCount(buttonSemaphore);
    Serial.print("LEDBlinkTask: Current semaphore count = ");
    Serial.println(count);

    if (xSemaphoreTake(buttonSemaphore, portMAX_DELAY)) {
      Serial.println("LEDBlinkTask: Blinking LED1 for button press");
      vTaskDelay(500 / portTICK_PERIOD_MS);
      digitalWrite(LED1_PIN, HIGH);
      vTaskDelay(1000 / portTICK_PERIOD_MS);
      digitalWrite(LED1_PIN, LOW);
      vTaskDelay(500 / portTICK_PERIOD_MS);
    }
  }
}

void LEDFadeTask(void *parameter) {
  pinMode(LED2_PIN, OUTPUT);
  for (;;) {
    // Fade up (0 to 255)
    for (int duty = 0; duty <= 255; duty += 5) {
      analogWrite(LED2_PIN, duty);
      if (duty % 50 == 0) {  // Print every 10th step
        Serial.print("LEDFadeTask: Fading LED2, duty=");
        Serial.println(duty);
      }
      vTaskDelay(50 / portTICK_PERIOD_MS);
    }
    // Fade down (255 to 0)
    for (int duty = 255; duty >= 0; duty -= 5) {
      analogWrite(LED2_PIN, duty);
      if (duty % 50 == 0 || duty == 255) {
        Serial.print("LEDFadeTask: Fading LED2, duty=");
        Serial.println(duty);
      }
      vTaskDelay(50 / portTICK_PERIOD_MS);
    }
  }
}

void setup() {
  Serial.begin(115200);  // Higher baud rate
  delay(1000);
  Serial.println("Starting FreeRTOS: Counting Semaphore");

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

  buttonSemaphore = xSemaphoreCreateCounting(SEMAPHORE_MAX_COUNT, 0);
  if (buttonSemaphore == NULL) {
    Serial.println("Failed to create semaphore!");
    while (1);
  }

  xTaskCreatePinnedToCore(
    LEDBlinkTask,           // Task function
    "LEDBlinkTask",         // Task name
    3000,                   // Stack size
    NULL,                   // Task parameters
    2,                      // Higher priority
    NULL,                   // Task handle
    1                       // Core ID
  );

  xTaskCreatePinnedToCore(
    LEDFadeTask,            // Task function
    "LEDFadeTask",          // Task name
    3000,                   // Stack size
    NULL,                   // Task parameters
    1,                      // Lower priority
    NULL,                   // Task handle
    1                       // Core ID
  );
}
void loop() {}

 

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

 

이 코드는 이전 코드와 매우 유사합니다. 카운팅 세마포어와 관련된 중요한 부분만 살펴보겠습니다.

 

카운팅 세마포어 만들기

 

setup() 함수에서 최대 카운트 5(SEMAPHORE_MAX_COUNT)로 시작하여 0부터 카운팅 세마포어를 생성합니다. 이를 위해 xSemaphoreCreateCounting() 함수를 사용합니다.

 

buttonSemaphore = xSemaphoreCreateCounting(SEMAPHORE_MAX_COUNT, 0);
if (buttonSemaphore == NULL) {
  Serial.println("Failed to create semaphore!");
  while (1);
}

 

작업 생성

 

설정() 함수 내에서 작업을 생성하고 코어에 할당합니다. LEDBlinkTask는 LEDFadeTask보다 우선순위가 높습니다.

 

xTaskCreatePinnedToCore(
  LEDBlinkTask,           // Task function
  "LEDBlinkTask",         // Task name
  3000,                   // Stack size
  NULL,                   // Task parameters
  2,                      // Higher priority
  NULL,                   // Task handle
  1                       // Core ID
);

xTaskCreatePinnedToCore(
  LEDFadeTask,            // Task function
  "LEDFadeTask",          // Task name
  3000,                   // Stack size
  NULL,                   // Task parameters
  1,                      // Lower priority
  NULL,                   // Task handle
  1                       // Core ID
);

 

버튼 ISR 및 카운팅 세마포어

 

푸시버튼을 누르면 buttonISR() 함수가 실행됩니다. 유효한 버튼 누름이 감지되면 카운팅 세마포어에 전달합니다. 세마포어는 최대 5회까지 카운트됩니다. 이전 예제에서 사용한 것과 동일한 함수 xSemaphoreGiveFromISR()를 사용합니다.

 

if (xSemaphoreGiveFromISR(buttonSemaphore, &higherPriorityTaskWoken) == pdTRUE) {
  Serial.println("buttonISR: Gave semaphore token");
} else {
  Serial.println("buttonISR: Semaphore full");
}

 

LEDBlinkTask 및 세마포어 가져오기

 

LEDBlinkTask는 세마포어에 카운트가 있을 때까지 무기한 대기합니다. 세마포어에 카운트가 있으면 이를 획득하여 LED를 깜빡입니다.

 

if (xSemaphoreTake(buttonSemaphore, portMAX_DELAY)) {
  Serial.println("LEDBlinkTask: Blinking LED1 for button press");
  vTaskDelay(500 / portTICK_PERIOD_MS);
  digitalWrite(LED1_PIN, HIGH);
  vTaskDelay(1000 / portTICK_PERIOD_MS);
  digitalWrite(LED1_PIN, LOW);
  vTaskDelay(500 / portTICK_PERIOD_MS);
}

 

세마포어가 있으므로 세마포어에서 현재 사용할 수 있는 카운트만큼 LED가 깜박입니다.

 

세마포어에서 데이터를 가져올 때마다 새로운 카운트를 추가할 수 있는 새로운 공간이 생깁니다(푸시 버튼을 눌러서).

 

이 작업 내에서 uxSemaphoreGetCount() 함수를 호출하고 세마포어 핸들을 인수로 전달하여 현재 세마포어 카운트를 출력합니다.

 

// Get and print the current semaphore count
UBaseType_t count = uxSemaphoreGetCount(buttonSemaphore);
Serial.print("LEDBlinkTask: Current semaphore count = ");
Serial.println(count);

 

 

LEDFade Task

 

동시에, 다른 LED를 단순히 페이드시키는 또 다른 독립적인 작업인 LEDFadeTask가 있습니다.

 

void LEDFadeTask(void *parameter) {
  pinMode(LED2_PIN, OUTPUT);
  for (;;) {
    // Fade up (0 to 255)
    for (int duty = 0; duty <= 255; duty += 5) {
      analogWrite(LED2_PIN, duty);
      if (duty % 50 == 0) {  // Print every 10th step
        Serial.print("LEDFadeTask: Fading LED2, duty=");
        Serial.println(duty);
      }
      vTaskDelay(50 / portTICK_PERIOD_MS);
    }
    // Fade down (255 to 0)
    for (int duty = 255; duty >= 0; duty -= 5) {
      analogWrite(LED2_PIN, duty);
      if (duty % 50 == 0 || duty == 255) {
        Serial.print("LEDFadeTask: Fading LED2, duty=");
        Serial.println(duty);
      }
      vTaskDelay(50 / portTICK_PERIOD_MS);
    }
  }
}

 

데모

 

보드에 코드를 업로드하세요. 업로드 후 시리얼 모니터를 115200 통신 속도로 열어주세요. ESP32 RST 버튼을 눌러 코드 실행을 시작하세요.

 

 

2개의 LED와 푸시 버튼이 있는 ESP32 - 푸시 버튼을 누르면 LED 상태가 전환됩니다.

 

GPIO 4에 연결된 LED는 지속적으로 어두워지거나 밝아질 것입니다.

 

세마포어 큐에서 버튼이 눌린 횟수만큼 GPIO 2에 연결된 LED가 깜빡이도록 푸시버튼을 여러 번 누르십시오.

 

아래의 짧은 영상에서 간단한 시연을 볼 수 있습니다.

 

직렬 모니터에서도 비슷한 결과가 나올 것입니다. 세마포어 카운트는 LED가 깜빡일수록 감소합니다(푸시 버튼을 계속 누르지 않으면).

 

 

ESP32 카운팅 세마포어 - 직렬 모니터 예제 데모

 

마무리하기

 

이 가이드에서는 FreeRTOS 바이너리와 카운팅 세마포어에 대해 알아보고 Arduino IDE로 프로그래밍된 ESP32로 이를 구현하는 방법을 알아보았습니다.

 

세마포어를 사용하면 리소스를 사용할 수 있을 때, 이벤트가 발생할 때 또는 작업에서 다른 작업을 실행해야 하는 지점을 알리는 신호를 보내 작업을 동기화할 수 있습니다.

 

세마포어의 작동 방식을 보여주는 두 가지 간단한 예를 살펴보았습니다. 이는 세마포어와 여러 작업을 주고받는 훨씬 더 복잡한 애플리케이션에도 적용될 수 있습니다.

 

ESP32 스케치에서 FreeRTOS 프로그래밍을 구현하는 데 이 튜토리얼이 도움이 되었기를 바랍니다. 이 FreeRTOS 시리즈에 대한 더 많은 튜토리얼이 준비되어 있으니 관심 있으신 분들은 참고해 주세요.

 

 

길고 긴 연휴가 끝났다. 주옥같은 시간이 돌아왔다. 매일 조금씩 전진하도록 한다.

 

반응형