본문 바로가기

ESP32

FreeRTOS를 사용한 ESP32: 소프트웨어 타이머/타이머 인터럽트

반응형

 

FreeRTOS를 사용한 ESP32: 소프트웨어 타이머/타이머 인터럽트(Arduino IDE)

 

이 가이드에서는 Arduino IDE에서 FreeRTOS 프로그래밍을 사용하여 ESP32에서 소프트웨어 타이머(타이머 인터럽트)를 사용하는 방법을 알아봅니다. 자동 리로드(주기적) 타이머와 원샷 타이머를 살펴보고, 간단한 예제를 통해 소프트웨어 타이머를 프로젝트에 쉽게 적용할 수 있도록 도와드립니다. FreeRTOS 소프트웨어 타이머를 사용하면 프로젝트에서 다양한 작업을 훨씬 더 쉽게 스케줄링할 수 있다는 것을 알게 될 것입니다.

 

 

ESP32 FreeRTOS 소프트웨어 타이머, 타이머 인터럽트

 

FreeRTOS를 처음 사용하시나요? ESP32를 활용한 다른 FreeRTOS 튜토리얼을 참고해 보세요.

  • FreeRTOS(Arduino IDE)를 사용한 ESP32 – 시작하기 : 작업 생성
  • FreeRTOS 대기열을 사용한 ESP32 : 태스크 간 통신(Arduino IDE)
  • FreeRTOS를 사용한 ESP32: 세마포어 시작하기 (Arduino IDE)

 

목차

타이머 인터럽트 / 소프트웨어 타이머

FreeRTOS 타이머 기본 사항

ESP32를 사용한 FreeRTOS 주기 타이머(자동 재로드)

1) 주기 타이머로 LED 깜박임

2) 여러 개의 LED를 서로 다른 주파수로 깜박임

ESP32를 사용한 FreeRTOS 원샷 타이머

1) 푸시 버튼으로 LED 토글하기(지연 포함)

 

타이머 인터럽트/소프트웨어 타이머

 

타이머 인터럽트를 사용하면 경과 시간을 지속적으로 확인하지 않고도 주기적으로 또는 미리 정의된 기간 후에 어떤 일이 발생하도록 하는 데 특히 유용합니다.

 

 

타이머 인터럽트 및 소프트웨어 타이머

 

소프트웨어 타이머를 사용하면 나중에 특정 시간에 실행되도록 함수를 예약할 수 있습니다. 타이머가 호출하는 함수를 콜백 함수 라고 하며 , 타이머가 시작된 후 콜백이 실행될 때까지의 간격을 타이머 주기 라고 합니다 .

 

ESP32가 포함된 FreeRTOS에서는 두 가지 유형의 타이머를 사용할 수 있습니다.

 

  • 주기적 타이머 (자동 재시작 타이머): 사용자가 정의한 x초마다 무기한으로 작동하는 타이머입니다. 이 기능을 사용하면 작업을 주기적으로 실행할 수 있습니다. 예를 들어, 1초마다 LED를 깜빡이는 기능을 사용할 수 있습니다.
  • 원샷 타이머 : 미리 정해진 시간 후에 한 번만 작동하는 타이머입니다. 이 기능을 사용하면 미리 정해진 시간 후에 특정 작업을 실행할 수 있습니다. 예를 들어, 푸시 버튼을 누른 후 5초 후에 LED를 켤 수 있습니다.

 

FreeRTOS 타이머 기본 사항

 

Arduino IDE에서 ESP32를 사용하여 FreeRTOS 타이머를 만들고 처리하는 데 대한 몇 가지 기본 개념은 다음과 같습니다.

 

타이머 만들기

 

타이머를 생성하려면 다음을 사용합니다.xTimerCreate ()함수를 실행하고 다음 매개변수를 이 순서대로 인수로 전달합니다.

  • 타이머 이름
  • 기간
  • 자동 새로 고침(pdTRUE주기 타이머의 경우 또는pd거짓원샷 타이머용)
  • timerID(콜백에 전달되는 사용자 정의 값)
  • 콜백 함수

 

예를 들어:

 

xTimerCreate(
    "BlinkTimer",                   // Timer name
    1000 / portTICK_PERIOD_MS,      // 1s period
    pdTRUE,                         // Auto-reload (periodic timer)
    NULL,                           // Timer ID
    BlinkCallback                   // Callback function
);

 

타이머 시작하기

 

타이머를 시작하려면 xTimerStart(timer, blockTime)을 사용합니다. 첫 번째 인수는 타이머 핸들러이며, 두 번째 인수는 타이머 시작 전에 대기할 초 수입니다.

 

타이머 중지

 

실행 중인 타이머를 중지하려면 xTimerStop(timer, blockTime)을 호출하기만 하면 됩니다. 인수는 이전 함수와 동일합니다.

 

타이머 재설정

 

타이머를 재설정하려면, 즉 타이머가 이미 실행 중일지라도 카운트다운을 다시 시작하려면 xTimerReset(timer, blockTime)을 호출하십시오. 인수는 이전 함수들과 동일합니다.

 

ISR(인터럽트 서비스 루틴)에서 타이머 시작

 

ISR(인터럽트 서비스 루틴 함수)에서 타이머를 시작하려면 xTimerStartFromISR(timer, &higherPriorityTaskWoken)을 호출해야 합니다.

higherPriorityTaskWoken은 pdTRUE 또는 pdFALSE일 수 있습니다. 해당 태스크가 우선순위가 높고 즉시 전환해야 할 경우 pdTRUE로 설정합니다. 이 경우 실시간 응답성을 위해 ISR에서 portYIELD_FROM_ISR()을 호출하여 즉시 해당 태스크로 전환해야 합니다.

 

ESP32를 사용한 FreeRTOS 주기 타이머

 

이제 소프트웨어 타이머의 기본 사항을 배웠으므로 Arduino IDE에서 FreeRTOS를 사용하여 ESP32로 주기적 타이머(자동 재로드)를 만들고 처리하는 방법을 보여주는 몇 가지 간단한 예를 테스트해 보겠습니다.

 

 

FreeRTOS 소프트웨어 타이머를 테스트하기 위해 ESP32에 연결된 두 개의 LED

 

1) 주기 타이머로 LED 깜박임

 

첫 번째로 살펴볼 예제는 간단한 Blink LED 스케치입니다. 이 스케치는 FreeRTOS의 주기적 타이머를 생성하여 1초마다 LED 상태를 전환합니다.

 

타이머를 실제로 사용하는 방법을 보여주는 간단하고 쉬운 예시입니다. 작동 방식은 다음과 같습니다.

  1. 1초 간격으로 타이머를 시작합니다.
  2. 타이머 기간이 지나면 콜백 함수가 실행됩니다.
  3. 콜백 함수는 LED의 상태를 전환합니다(먼저 LED를 켭니다).
  4. 타이머 기간(1초)이 지나면 콜백 함수가 다시 실행되어 LED 상태를 전환합니다(이번에는 LED를 끕니다).

 

이 동작은 프로그램이 멈추거나 타이머를 멈출 때까지 계속 반복됩니다. 깜빡이는 효과가 생성됩니다.

 

회로도

 

이 예제에서는 ESP32 내장 LED를 깜빡이겠습니다. 이 경우에는 GPIO 2에 연결되어 있습니다. 다른 ESP32 보드의 내장 LED는 다른 GPIO에 연결되어 있을 수 있습니다.

 

또는 220옴 저항을 통해 물리적인 LED를 보드에 연결할 수도 있습니다.

 

필요한 부품:

ESP32 보드

1x LED

1x 220옴 저항기 (또는 유사한 값)

브레드보드

점퍼 와이어

 

 

GPIO 2에 연결된 ESP32의 회로도

 

Code

 

다음 코드는 FreeRTOS의 주기적 타이머를 사용하여 ESP32 내장 LED를 깜빡입니다. 이 코드를 ESP32 보드에 업로드할 수 있습니다.

 

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp32-freertos-software-timers-interrupts/
  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.
*/
#include <Arduino.h>

#define LED_PIN 2

TimerHandle_t blinkTimer = NULL;

bool ledState = false;

void BlinkCallback(TimerHandle_t xTimer) {
  ledState = !ledState;
  
  if (ledState) {
    digitalWrite(LED_PIN, HIGH);
    Serial.print("LED is ");
    Serial.println("ON");
  } else {
    digitalWrite(LED_PIN, LOW);
    Serial.print("LED is ");
    Serial.println("OFF");
  }
}

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println("Starting FreeRTOS");
  Serial.println("Periodic Timer for LED Blink");

  pinMode(LED_PIN, OUTPUT);

  blinkTimer = xTimerCreate(
    "BlinkTimer",                   // Timer name
    1000 / portTICK_PERIOD_MS,      // 1s period
    pdTRUE,                         // Auto-reload (periodic timer)
    NULL,                           // Timer ID
    BlinkCallback                   // Callback function
  );
  if (blinkTimer == NULL) {
    Serial.println("Failed to create timer!");
    while (1);
  }

  xTimerStart(blinkTimer, 0); // Start timer immediately
}

void loop() {
  // Empty
}

 

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

 

먼저 LED에 연결된 GPIO를 정의하세요. 다른 GPIO 핀을 사용하는 경우 변경하세요.

 

#define LED_PIN 2

 

타이머 핸들러를 생성합니다. 핸들러는 타이머를 참조하기 위해 사용하는 이름과 같습니다. 처음에는 실제 타이머를 생성하지 않았기 때문에 NULL로 설정합니다. 타이머 핸들러의 이름을 blinkTimer라고 하겠습니다.

 

TimerHandle_t blinkTimer = NULL;

 

LED의 현재 상태를 저장할 ledState라는 변수를 생성합니다. 프로그램이 처음 실행될 때 LED는 꺼진 상태(false)입니다.

 

bool ledState = false;

 

타이머 콜백 함수

 

BlinkCallback은 주기적으로 실행하고자 하는 함수입니다. 이 함수는 TimerHandle_t 유형의 매개변수를 받아야 합니다.

 

void BlinkCallback(TimerHandle_t xTimer) {
  ledState = !ledState;
  
  if (ledState) {
    digitalWrite(LED_PIN, HIGH);
    Serial.print("LED is ");
    Serial.println("ON");
  } else {
    digitalWrite(LED_PIN, LOW);
    Serial.print("LED is ");
    Serial.println("OFF");
  }
}

 

먼저 현재 LED 상태를 반전합니다.

 

ledState = !ledState;

 

그런 다음 digitalWrite() 함수를 사용하여 LED를 켜거나 끕니다.

 

if (ledState) {
  digitalWrite(LED_PIN, HIGH);
  Serial.print("LED is ");
  Serial.println("ON");
} else {
  digitalWrite(LED_PIN, LOW);
  Serial.print("LED is ");
  Serial.println("OFF");
}

 

setup()

 

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

 

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println("Starting FreeRTOS");
  Serial.println("Periodic Timer for LED Blink");

  pinMode(LED_PIN, OUTPUT);

 

타이머 만들기

 

그런 다음 소개 섹션에서 살펴본 xTimeCreate() 함수를 사용하여 타이머 blinkTimer를 생성합니다. 이 타이머는 시작되면 1000밀리초(1초)마다 BlinkCallback 함수를 호출합니다.

 

blinkTimer = xTimerCreate(
  "BlinkTimer",                   // Timer name
  1000 / portTICK_PERIOD_MS,      // 1s period
  pdTRUE,                         // Auto-reload (periodic timer)
  NULL,                           // Timer ID
  BlinkCallback                   // Callback function
);

 

 

타이머가 성공적으로 생성되었는지 확인합니다. 생성 실패 시 NULL을 반환합니다.

 

if (blinkTimer == NULL) {
    Serial.println("Failed to create timer!");
    while (1);
}

 

 

타이머 시작

 

마지막으로 xTimerStart() 함수를 호출하여 blinkTimer를 시작할 수 있습니다. 타이머는 즉시 시작됩니다(함수의 두 번째 인수는 0입니다).

 

xTimerStart(blinkTimer, 0); // Start timer immediately

 

loop()

 

loop() 함수가 비어 있는 이유는 FreeRTOS 스케줄러가 프로그램 실행을 유지하고 타이머 인터럽트 및 태스크를 대기하기 때문입니다. 그러나 프로젝트에 필요한 경우 loop()에 필요한 코드를 추가할 수 있습니다.

 

void loop() {

 

// Empty

 

}

 

데모

 

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

 

그런 다음, 115200의 전송 속도로 직렬 모니터를 엽니다.

 

ESP32 온보드 LED가 1초마다 깜박이기 시작합니다(또는 GPIO 2에 연결된 LED).

 

 

ESP32 보드 내장 LED가 HIGH로 켜짐

 

 

ESP32 보드 내장 LED가 LOW로 꺼짐

 

직렬 모니터는 LED의 현재 상태를 보여줍니다(ESP32의 내장 LED는 반전 논리로 작동합니다).

 

 

LED의 현재 상태를 보여주는 직렬 모니터 - FreeRTOS 주기 타이머 테스트

 

보시다시피, FreeRTOS의 주기적 타이머는 프로젝트에서 매우 간단하고 유용하며 실용적입니다. 예를 들어, LED를 깜빡이는 것 외에도 센서에서 데이터를 가져오거나, 서버로 데이터를 전송하거나, microSD 카드에 데이터를 로깅하거나, 입력 상태를 확인하는 등 주기적인 작업에 사용할 수 있습니다.

 

2) 여러 개의 LED를 서로 다른 주파수로 깜박임

 

주기적 타이머를 만드는 방법을 이해하면, 동일하거나 다른 간격으로 여러 작업을 주기적으로 실행해야 하는 경우 여러 개의 타이머를 만드는 것은 매우 간단합니다.

 

그 방법을 보여드리기 위해 두 개의 LED를 서로 다른 주파수로 깜박이는 새로운 예제를 만들어 보겠습니다. 각 LED를 제어하는 ​​주기 타이머 두 개를 만들어 보겠습니다.

 

회로도

 

이 예제에서는 220Ω 저항을 통해 ESP32 보드에 두 개의 LED를 연결합니다. LED를 GPIO 2번과 4번에 연결했지만, 다른 적합한 핀을 사용해도 됩니다. 코드를 적절히 수정해야 합니다.

 

필요한 부품:

ESP32 보드

2개의 LED

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

브레드보드

점퍼 와이어

 

 

GPIO 2와 GPIO 4에 연결된 ESP32의 회로도

 

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

 

Code

 

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

 

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp32-freertos-software-timers-interrupts/
  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_RED 2
#define LED_PIN_BLUE 4

TimerHandle_t blinkTimerRed = NULL;
TimerHandle_t blinkTimerBlue = NULL;

bool ledStateRed = false;
bool ledStateBlue = false;

void BlinkRedCallback(TimerHandle_t xTimer) {
  ledStateRed = !ledStateRed;
  
  if (ledStateRed) {
    digitalWrite(LED_PIN_RED, HIGH);
    Serial.print("Red LED is ");
    Serial.println("ON");
  } else {
    digitalWrite(LED_PIN_RED, LOW);
    Serial.print("Red LED is ");
    Serial.println("OFF");
  }
}

void BlinkBlueCallback(TimerHandle_t xTimer) {
  ledStateBlue = !ledStateBlue;
  
  if (ledStateBlue) {
    digitalWrite(LED_PIN_BLUE, HIGH);
    Serial.print("Blue LED is ");
    Serial.println("ON");
  } else {
    digitalWrite(LED_PIN_BLUE, LOW);
    Serial.print("Blue LED is ");
    Serial.println("OFF");
  }
}

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println("Starting FreeRTOS");
  Serial.println("Periodic Timers for Two LEDs");

  pinMode(LED_PIN_RED, OUTPUT);
  pinMode(LED_PIN_BLUE, OUTPUT);

  blinkTimerRed = xTimerCreate(
    "BlinkTimerRed",               // Timer name
    700 / portTICK_PERIOD_MS,      // 1s period
    pdTRUE,                        // Auto-reload (periodic timer)
    NULL,                          // Timer ID
    BlinkRedCallback               // Callback function
  );
  if (blinkTimerRed == NULL) {
    Serial.println("Failed to create blinkTimerRed timer!");
    while (1);
  }

  blinkTimerBlue = xTimerCreate(
    "BlinkTimerBlue",               // Timer name
    1100 / portTICK_PERIOD_MS,      // 1s period
    pdTRUE,                         // Auto-reload (periodic timer)
    NULL,                           // Timer ID
    BlinkBlueCallback               // Callback function
  );
  if (blinkTimerBlue == NULL) {
    Serial.println("Failed to create blinkTimerBlue timer!");
    while (1);
  }

  xTimerStart(blinkTimerRed, 0); // Start timer immediately
  xTimerStart(blinkTimerBlue, 0); // Start timer immediately
}

void loop() {
  // Empty
}

 

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

 

이전 예제에서 타이머를 만드는 방법을 이해했다면 여러 개의 타이머를 만드는 것이 매우 간단하다는 것을 알 수 있을 것입니다.

 

각 LED를 제어하기 위해 GPIO를 정의합니다.

 

#define LED_PIN_RED 2

#define LED_PIN_BLUE 4

 

그런 다음 각 타이머에 대한 시간 처리기를 만듭니다. 각 타이머는 서로 다른 LED를 제어합니다.

 

TimerHandle_t blinkTimerRed = NULL;

TimerHandle_t blinkTimerBlue = NULL;

 

각 타이머가 끝날 때마다 호출되는 콜백 함수를 만들어 각 LED를 제어합니다.

 

void BlinkRedCallback(TimerHandle_t xTimer) {
  ledStateRed = !ledStateRed;
  
  if (ledStateRed) {
    digitalWrite(LED_PIN_RED, HIGH);
    Serial.print("Red LED is ");
    Serial.println("ON");
  } else {
    digitalWrite(LED_PIN_RED, LOW);
    Serial.print("Red LED is ");
    Serial.println("OFF");
  }
}

void BlinkBlueCallback(TimerHandle_t xTimer) {
  ledStateBlue = !ledStateBlue;
  
  if (ledStateBlue) {
    digitalWrite(LED_PIN_BLUE, HIGH);
    Serial.print("Blue LED is ");
    Serial.println("ON");
  } else {
    digitalWrite(LED_PIN_BLUE, LOW);
    Serial.print("Blue LED is ");
    Serial.println("OFF");
  }
}

 

 

setup() 함수 내에서 각 LED를 깜빡이게 할 타이머를 생성할 수 있습니다. 다음 타이머는 700밀리초마다 BlinkRedCallback 함수를 호출하여 빨간색 LED가 700밀리초마다 상태를 전환하도록 합니다.

 

blinkTimerRed = xTimerCreate(
  "BlinkTimerRed",                   // Timer name
  700 / portTICK_PERIOD_MS,      // 1s period
  pdTRUE,                         // Auto-reload (periodic timer)
  NULL,                           // Timer ID
  BlinkRedCallback                   // Callback function
);
if (blinkTimerRed == NULL) {
  Serial.println("Failed to create blinkTimerRed timer!");
  while (1);
}

 

 

다른 LED를 제어하기 위한 타이머도 생성합니다. 이 타이머는 1100밀리초마다 BlinkBlueCallback 함수를 호출합니다.

 

 

blinkTimerBlue = xTimerCreate(
  "BlinkTimerBlue",                   // Timer name
  1100 / portTICK_PERIOD_MS,      // 1s period
  pdTRUE,                         // Auto-reload (periodic timer)
  NULL,                           // Timer ID
  BlinkBlueCallback                   // Callback function
);
if (blinkTimerBlue == NULL) {
  Serial.println("Failed to create blinkTimerBlue timer!");
  while (1);
}

 

 

 

마지막으로 xTimerStart() 함수를 호출하여 타이머를 시작할 수 있습니다.

xTimerStart(blinkTimerRed, 0); // 타이머 즉시 시작
xTimerStart(blinkTimerBlue, 0); // 타이머 즉시 시작


loop() 함수는 비어 있습니다. FreeRTOS 스케줄러가 적절한 시점에 타이머를 실행하도록 관리하기 때문입니다. 필요한 경우 프로젝트에 필요한 다른 작업을 위해 loop()에 코드를 추가할 수 있습니다.

 

void loop() {
  // Empty
}

 

 

데모

 

이전 코드를 ESP32 보드에 업로드합니다.

 

그러면 두 개의 LED가 서로 다른 주파수로 깜박입니다.

 

 

소프트웨어 타이머를 사용하여 서로 다른 주파수로 깜박이는 두 개의 LED에 연결된 ESP32

 

또한 직렬 모니터를 열어서 결과를 확인할 수도 있습니다.

 

 

두 개의 서로 다른 LED 상태를 보여주는 직렬 모니터 - ESP32를 사용하여 여러 FreeRTOS 타이머 테스트

 

ESP32를 사용한 FreeRTOS 원샷 타이머

 

원샷 타이머는 미리 정의된 시간 후에 콜백 함수를 한 번 실행합니다. 이를 통해 미리 정의된 시간 후에 특정 작업을 실행할 수 있습니다.

 

 

푸시 버튼과 LED에 연결된 ESP32를 사용하여 FreeRTOS 원샷 타이머의 예를 테스트합니다.

 

1) 푸시 버튼으로 LED 토글하기(지연 포함)

 

원샷 타이머가 어떻게 작동하는지 보여드리기 위해 푸시 버튼을 누른 후 5초 후에 LED 상태를 전환하는 간단한 예를 만들어 보겠습니다.

 

작동 원리는 다음과 같습니다.

  • LED가 꺼져 있습니다.
  • 푸시버튼을 누르세요.
  • 5초 후에 LED가 켜집니다.
  • LED가 계속 켜져 있습니다.
  • 푸시버튼을 누르세요.
  • 5초 후에 LED가 꺼집니다.

 

회로도

 

이 예제에서는 LED와 푸시 버튼을 ESP32 GPIO에 연결합니다. LED는 GPIO 2에, 푸시 버튼은 GPIO 4에 연결합니다. 푸시 버튼에는 ESP32의 내부 풀업 저항을 사용하므로 저항을 사용하지 않습니다.

 

필요한 부품:

ESP32 보드

1x LED

1x 220옴 저항기 (또는 유사한 값)

1x 푸시 버튼

브레드보드

점퍼 와이어

 

 

푸시버튼과 LED에 연결된 ESP32 - 회로도

 

Code

 

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

 

/*
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://RandomNerdTutorials.com/esp32-freertos-software-timers-interrupts/
  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 4
#define LED_PIN 2
#define DEBOUNCE_DELAY 200
#define TIMER_DELAY 5000

TimerHandle_t toggleTimer = NULL;
volatile uint32_t lastInterruptTime = 0;
volatile bool ledState = false;

void IRAM_ATTR buttonISR() {
  uint32_t currentTime = millis();
  if (currentTime - lastInterruptTime > DEBOUNCE_DELAY) {
    BaseType_t higherPriorityTaskWoken = pdFALSE;
    xTimerStartFromISR(toggleTimer, &higherPriorityTaskWoken);
    Serial.print("buttonISR: LED will ");
    if (ledState) {
      Serial.print("turn OFF");
    } else {
      Serial.print("turn ON");
    }
    Serial.print(" in 5 seconds");
    Serial.println();
    lastInterruptTime = currentTime;
    if (higherPriorityTaskWoken) {
      portYIELD_FROM_ISR();
    }
  }
}

void ToggleCallback(TimerHandle_t xTimer) {
  ledState = !ledState;
  if (ledState) {
    digitalWrite(LED_PIN, HIGH);
    Serial.println("LED is ON");
  } else {
    digitalWrite(LED_PIN, LOW);
    Serial.println("LED is OFF");
  }
}

void setup() {
  Serial.begin(115200);
  delay(1000);
  Serial.println("Starting FreeRTOS");

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

  toggleTimer = xTimerCreate(
    "ToggleTimer",                    // Timer name
    TIMER_DELAY / portTICK_PERIOD_MS, // 5s delay
    pdFALSE,                          // One-shot
    NULL,                             // Timer ID
    ToggleCallback                   // Callback
  );
  if (toggleTimer == NULL) {
    Serial.println("Failed to create timer!");
    while (1);
  }
}

void loop() {
  // Empty
}

 

코드는 어떻게 작동합니까?

 

먼저 LED와 푸시 버튼의 GPIO를 정의합니다. 각각 GPIO 2와 4를 사용하지만, 다른 적합한 GPIO를 사용해도 됩니다.

 

#define BUTTON_PIN 4

#define LED_PIN 2

 

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

 

#define DEBOUNCE_DELAY 200

 

원샷 타이머(푸시 버튼을 누른 후 LED 상태가 변경되는 사이의 시간)에 대한 지연 시간을 정의합니다.

 

#define TIMER_DELAY 5000

 

일회성 타이머용 핸들을 생성합니다. 이를 toggleTimer라고 부릅니다.

 

TimerHandle_t toggleTimer = NULL;

 

마지막 버튼 누름 이후 얼마나 시간이 지났는지 확인하기 위한 변수를 생성합니다(오탐을 방지하기 위해).

 

volatile uint32_t lastInterruptTime = 0;

 

현재 LED 상태를 저장할 ledState라는 변수를 생성합니다.

 

volatile bool ledState = false; // Made volatile for ISR access

 

setup() 함수에서 디버깅을 위해 시리얼 모니터를 초기화합니다.

 

void setup() {

    Serial.begin(115200);

 

우리는 푸시버튼을 내부 풀업 저항을 통한 입력으로 설정했습니다.

 

pinMode(BUTTON_PIN, INPUT_PULLUP);

 

LED는 출력으로 설정됩니다.

 

pinMode(LED_PIN, OUTPUT);

 

그런 다음 푸시버튼을 하강 에지(FALLING edge)에서 인터럽트로 설정합니다. 푸시버튼이 눌리면 buttonISR 함수가 호출됩니다.

 

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

 

원샷 타이머 만들기

 

xTimerCreate() 함수를 사용하여 일회성 타이머인 toggleTimer를 생성합니다. 두 번째 인자로 5초의 지연 시간을 지정했으며, 세 번째 인자는 pdFALSE로 설정되어 초기화 후 5초 후에 트리거되는 일회성 타이머를 설정함을 나타냅니다. 보시다시피 일회성 타이머 설정은 매우 간단합니다.

 

이 경우, 트리거되면 ToggleCallback 함수를 호출합니다.

 

toggleTimer = xTimerCreate(
  "ToggleTimer",                  // Timer name
  TIMER_DELAY / portTICK_PERIOD_MS,  // 5s delay
  pdFALSE,                        // One-shot
  NULL,                           // Timer ID
  ToggleCallback                  // Callback
);
if (toggleTimer == NULL) {
  Serial.println("Failed to create timer!");
  while (1);
}



buttonISR() 함수

 

푸시버튼의 ISR(콜백 함수)은 buttonISR() 함수입니다. 먼저 유효한 푸시버튼 누름이 있는지 확인합니다.

 

void IRAM_ATTR buttonISR() {
  uint32_t currentTime = millis();
  if (currentTime - lastInterruptTime > DEBOUNCE_DELAY) {

 

그런 다음 higherPriorityTaskWoken 변수를 생성하고 pdFALSE로 설정합니다. 이 변수는 ISR 작업(이 경우 타이머 시작)이 즉시 실행해야 하는 우선순위가 더 높은 작업을 차단 해제했는지 여부를 알려줍니다. 초기값은 false입니다.

 

BaseType_t higherPriorityTaskWoken = pdFALSE;

 

타이머 시작

 

그런 다음 xTimerStartFromISR 함수를 호출하여 타이머를 시작합니다. 인자로 타이머 핸들, toggleTimer, 그리고 higherPriorityTaskWoken의 주소(&higherPriorityTaskWoken)를 전달합니다. 이를 통해 FreeRTOS는 작업이 더 높은 우선순위 작업(예: 타이머 서비스 태스크)의 차단 해제를 유발할 경우 플래그를 업데이트할 수 있습니다. 이렇게 하면 다음에 해당 플래그를 확인(if (higherPriorityTaskWoken))하고 실시간 응답성을 위해 필요한 경우 즉시 해당 태스크로 전환(portYIELD_FROM_ISR()) 할 수 있습니다.

 

xTimerStartFromISR(toggleTimer, &higherPriorityTaskWoken);

 

타이머를 시작하면 5초(TIMER_DELAY) 후에 ToggleCallback 함수가 실행됩니다. ToggleCallback 함수는 단순히 LED의 현재 상태를 전환합니다.

 

void ToggleCallback(TimerHandle_t xTimer) {
  ledState = !ledState;
  if (ledState) {
    digitalWrite(LED_PIN, HIGH);
    Serial.println("LED is ON");
  } else {
    digitalWrite(LED_PIN, LOW);
    Serial.println("LED is OFF");
  }
}

 

데모

 

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

 

115200의 전송 속도로 직렬 모니터를 엽니다.

 

푸시 버튼을 누르세요. 직렬 모니터에 " buttonISR: LED가 5초 후에 켜집니다 " 라는 메시지가 표시됩니다 .

 

 

원샷 타이머를 사용하여 버튼을 누르면 LED가 켜집니다 - 직렬 모니터 데모

 

그리고 5초 후에 LED가 켜집니다.

 

 

ESP32를 LED와 푸시 버튼에 연결하여 ESP32로 FreeRTOS 원샷 타이머를 테스트합니다.

 

푸시버튼을 다시 누르면 5초 후에 LED가 꺼집니다.

 

 

원샷 타이머를 사용하여 버튼을 누르면 LED가 꺼집니다 - 직렬 모니터 데모

 

마무리하기

 

이 튜토리얼에서는 Arduino IDE로 프로그래밍된 ESP32에 FreeRTOS 소프트웨어 타이머를 설정하고 사용하는 방법을 살펴보았습니다. 주기 타이머와 일회성 타이머에 대해서도 알아보았습니다.

 

소프트웨어 타이머에 대한 자세한 내용은 FreeRTOS 공식 문서를 확인하세요 .

 

ESP32를 사용한 FreeRTOS 프로그래밍에 대해 자세히 알아보려면 다른 튜토리얼을 확인하세요.

 

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

FreeRTOS 대기열을 사용한 ESP32 : 태스크 간 통신(Arduino IDE)

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

 

여기까지 고생하셨습니다. 

 

시간내어 읽어주셔서 감사합니다.

 

 

 

반응형

캐어랩 고객 지원

취업, 창업의 막막함, 외주 관리, 제품 부재!

당신의 고민은 무엇입니까? 현실과 동떨어진 교육, 실패만 반복하는 외주 계약, 아이디어는 있지만 구현할 기술이 없는 막막함.

우리는 알고 있습니다. 문제의 원인은 '명확한 학습, 실전 경험과 신뢰할 수 있는 기술력의 부재'에서 시작됩니다.

이제 고민을 멈추고, 캐어랩을 만나세요!

코딩(펌웨어), 전자부품과 디지털 회로설계, PCB 설계 제작, 고객(시장/수출) 발굴과 마케팅 전략으로 당신을 지원합니다.

제품 설계의 고수는 성공이 만든 게 아니라 실패가 만듭니다. 아이디어를 양산 가능한 제품으로!

귀사의 제품을 만드세요. 교육과 개발 실적으로 신뢰할 수 있는 파트너를 확보하세요.

지난 30년 여정, 캐어랩이 얻은 모든 것을 함께 나누고 싶습니다.

카카오 채널 추가하기

카톡 채팅방에서 무엇이든 물어보세요

귀사가 성공하기까지의 긴 고난의 시간을 캐어랩과 함께 하세요.

캐어랩 온라인 채널 바로가기

캐어랩