ESP32

ESP32: Ticker library

기하 2021. 8. 14. 22:26

소개

이 게시물에서는

ESP32와 Arduino 코어를 사용하여 Ticker 라이브러리를 시작하는 방법을 배울 것입니다.

Ticker 라이브러리를 사용하면 타이머 설정의 하위 수준 세부 정보에 대해 걱정할 필요 없이

주기적으로 실행되도록 콜백 함수를 매우 쉽게 설정할 수 있습니다.

( 여기에서 타이머 인터럽트를 설정하는 방법에 대한 예제를 확인할 수 있습니다) 

 

이는 예를 들어 센서에서 주기적으로 측정값을 수집하는 데 유용할 수 있습니다.

아래에 표시된 테스트는 DFRobot 의  ESP32-E FireBeetle 보드  에서 수행되었습니다.

간단한 예

Ticker.h 라이브러리 를 포함하여 코드를 시작합니다

( 여기 에서 헤더와 구현 파일을 모두 확인할 수 있음 ). 

그러면 아래에서 사용할 Ticker 클래스를 불러 포함 시켜줍니다.

#include <Ticker.h>

 

우리의 예에서

주기적으로 실행하도록 함수를 설정하고

시간 간격 후에 한 번만 실행하도록 하는 2가지  함수를 설정하는 방법을 다룰 것입니다. 

따라서 각 사용 사례에 대해 하나씩 두 개의 Ticker 개체 를 만듭니다 .

Ticker periodicTicker;
Ticker onceTicker;

 

Arduino 설정으로 이동하여 먼저 직렬 연결을 열도록합니다. 

Ticker 개체에 의해 트리거되는 함수는 각각 다른 메시지를 인쇄합니다.

Serial.begin(115200);

 

그런 다음 주기적으로 기능을 실행할 Ticker 개체 구성을 처리합니다 . 

주기적으로 실행되도록 콜백 함수를 구성하려면 

attach_ms 메서드 호출할 수 있습니다 .

 

처음 인수로 주기적인 호출 사이의 간격(밀리초)을 나타내는 부호 없는 정수를 수신합니다. 

두 번째 인수로  콜백 함수를 수신합니다. 여기서는 나중에 정의할 periodicPrint 입니다.

 

추가 참고 사항으로,

float처럼 초 단위로 간격을 수신하는 attach 라는 유사한 메서드가 있습니다. 

내부적으로, attach 메소드를 대신 사용하면 

라이브러리는 float 값에 1000을 곱하고 이를 밀리초에서도 작동하는 내부 함수에 전달합니다.

periodicTicker.attach_ms(5000, periodicPrint);

 

미리 정의된 간격(interval) 후에 한 번만 함수를 실행 하도록

다른 Ticker 개체 를 구성하려면 대신 once_ms 메서드 를 호출해야 합니다 . 

첫 번째 인수는 밀리초 단위의 간격이고

두 번째 인수는 해당 간격이 경과한 후 한 번 호출되는 콜백 함수입니다.

 

attach_ms  attach 메소드  유사하게 ,
float로 초 단위의 간격을 수신하는 once  메소드도 있습니다. 

이전과 마찬가지로 후드 아래에서 값이 밀리초로 변환되고

밀리초 단위로 작동하는 내부 함수에 전달됩니다.

onceTicker.once_ms(10000, oncePrint);

 

전체 설정은 아래와 같습니다.

void setup() {
  Serial.begin(115200);
   
  periodicTicker.attach_ms(5000, periodicPrint);
  onceTicker.once_ms(10000, oncePrint);
}

이제 우리는 콜백함수들을 둘러 볼 것입니다. 

그들은 void를 반환하고 매개변수를 받지 않아야 합니다

(아래 섹션에서 이러한 함수에 인수를 전달하는 방법을 다룰 것입니다). 

앞에서 언급한 것처럼 직렬 포트에 메시지를 인쇄하기만 하면 실행 중인 포트를 구별할 수 있습니다.

void periodicPrint() {
  Serial.println("printing in periodic function.");
}
void oncePrint() {
  Serial.println("printing in once function.");
}

전체 코드는 아래에 나와 있습니다. 

기본 구현이 타이머를 기반으로 하기 때문에 메인 루프에 아무것도 추가할 필요가 없습니다.

#include <Ticker.h>

Ticker periodicTicker;
Ticker onceTicker;

void periodicPrint() {
  Serial.println("printing in periodic function.");
}

void oncePrint() {
  Serial.println("printing in once function.");
}

void setup() {
  Serial.begin(115200);   
  periodicTicker.attach_ms(5000, periodicPrint);
  onceTicker.once_ms(10000, oncePrint);
}

void loop() {}

 

평소와 같이 코드를 테스트하려면 간단히 컴파일하고

Arduino IDE를 사용하여 ESP32에 업로드하십시오. 절차가 완료되면 IDE 직렬 모니터를 엽니다.

그림 1과 유사한 결과를 얻을 수 있습니다.

보시다시피, 주기적으로 실행되는 함수에서 인쇄를 가져오고

한 번 실행되도록 예약된 함수에서 하나의 인쇄만 가지고 있습니다.

그림 1 - 정기 및 한 번 실행 기능의 인쇄물을 보여주는 프로그램 출력.

함수 인수 전달 및 분리

이 섹션에서는 콜백 함수에 인수를 전달하는 방법과

콜백의 주기적 실행을 분리하는 방법을 살펴봅니다.

 

이 예에서는 최대 횟수(이 임계값은 콜백의 인수가 됨)를 실행하고

해당 횟수의 실행을 통과한 후 자체적으로 일시 중단되는 콜백 함수를 구성합니다. 

아래에 표시된 코드는 단지 예시일 뿐이며

현재 반복 카운터에서와 마찬가지로 인수로 전달하는 대신

이 임계값을 유지하도록 전역 변수를 정의할 수 있습니다.

 

이전과 마찬가지로 Ticker.h 라이브러리 포함으로 시작합니다 .

#include <Ticker.h>

그런 다음 Ticker 클래스의 개체를 인스턴스화합니다 . 

이번에는 하나의 콜백 함수가 주기적으로 실행되도록 구성하기 위해 객체만 사용합니다.

	Ticker periodicTicker;

콜백 함수가 이미 실행된 횟수를 추적하는 전역 카운터도 정의합니다. 

당연히 0 값으로 초기화하고 나중에 콜백에서 증분합니다.

int executionsCount = 0;

그런 다음 Arduino 설정으로 바로 이동합니다. 

직렬 연결을 열어 콜백이 나중에 현재 반복 값을 인쇄할 수 있도록 합니다.

Serial.begin(115200);

이제 최대 실행 횟수를 저장할 변수를 정의합니다. 

이것은 우리가 콜백에 전달할 변수입니다. 10이라는 값을 사용하겠습니다.

int maxExecutionsCount=10;

마지막으로 Ticker 객체 에서 attach_ms 메서드를 호출하여 

다음 3개의 매개변수를 입력으로 전달합니다.

  • 실행 간격(밀리초)입니다. 5000밀리초(5초)라는 값을 전달할 것입니다.
  • 콜백 함수. 우리는 나중에 함수를 정의할 것이지만, 우리는 그것을 periodPrint 라고 부를 것 입니다.
  • 콜백 함수에 전달할 인수입니다. 우리는 변수를 사용할 것입니다.

중요 : maxExecutionsCount 변수의 이름과 사용 사례로 인해 오해를 받지 마십시오. 

attach_ms 메서드의 세 번째 인수는 이 메서드에 대한 의미가 없으며

단순히 콜백 변수에 전달됩니다. 

작성 당시 라이브러리는 주기 함수의 최대 실행 횟수를 지원하지 않으며

실제로 해당 메커니즘을 직접 구현할 것입니다.

periodicTicker.attach_ms(5000, periodicPrint, maxExecutionsCount);

attach_ms 메소드를 소환함으로써 setup 함수를 완성합니다.

void setup() {
  Serial.begin(115200);
  int maxExecutionsCount=10;   
  periodicTicker.attach_ms(5000, periodicPrint, maxExecutionsCount);
}

이제 콜백 함수의 구현을 살펴보겠습니다. 

다시 한 번 void를 반환해야 하지만 이번에는 정수 매개변수를 받습니다.

void periodicPrint(int maxExecutionsCount) {
   // callback implementation
}

가장 먼저 할 일은 현재 실행 번호를 인쇄하는 것입니다. 

1을 executionCount에 합산하여 사용자에게 1부터 시작하는 카운터를 표시합니다.

Serial.print("printing in periodic function. Exec nr: ");
Serial.println(executionsCount+1);

그런 다음 카운터를 증가시키고 임계값에 도달했는지 확인합니다. 

그렇게 한 경우 Ticker 개체에서 detach 메서드를 호출하여 추가 실행을 중지 합니다. 

이 메서드는 인수를 사용하지 않고 void를 반환합니다.

executionsCount++;
if(executionsCount>=maxExecutionsCount){
    periodicTicker.detach();
}

전체 콜백함수는 아래와 같습니다.

void periodicPrint(int maxExecutionsCount) {
  Serial.print("printing in periodic function. Exec nr: ");
  Serial.println(executionsCount+1);
  executionsCount++;
  if(executionsCount>=maxExecutionsCount){
    periodicTicker.detach();
  }
}

전체 코드는 아래에서 확인할 수 있습니다.

#include <Ticker.h>
Ticker periodicTicker;
int executionsCount = 0;

void periodicPrint(int maxExecutionsCount) {
  Serial.print("printing in periodic function. Exec nr: ");
  Serial.println(executionsCount+1);
  executionsCount++;

  if(executionsCount>=maxExecutionsCount){
    periodicTicker.detach();
  }
}

void setup() {
  Serial.begin(115200);
  int maxExecutionsCount=10;   
  periodicTicker.attach_ms(5000, periodicPrint, maxExecutionsCount);
}
void loop() {}

다시 한 번, 코드를 컴파일 및 업로드하고 절차가 완료되면 직렬 모니터를 엽니다. 

코드에서 정의한 대로 10번의 호출 후에 주기적 콜백 실행이 중지된다는 것을

보여주는 그림 2와 유사한 결과가 표시되어야 합니다.

그림 2 - 콜백 함수의 구성된 10개 실행을 보여주는 코드 출력.

중요 고려 사항

이제 Ticker 라이브러리의 기본 사항을 다루었으므로

이것이 내부에서 어떻게 작동하는지 아는 것이 중요합니다. 

 

Arduino 추상화는 작업을 빠르게 시작하고 실행하는 데 훌륭하지만

더 복잡한 시나리오에 일부를 사용하려는 경우 세부 사항을 알지 못하면

예기치 않은 문제가 발생할 수 있습니다.

 

Ticker 라이브러리는 esp_timer라는 hood 아래에 있는 IDF API 을 사용하여 구현됩니다.

이는 esp_timer.h 라는 화일과 문서에 정의되어 있습니다.

 Ticker.h 파일의시작 부분에 esp_timer.h포함되는 것을 볼 수 있습니다 .

 

내부적으로 esp_timer.h 라이브러리는 마이크로초 정확도를 제공할 수 있는

하드웨어 타이머를 기반으로 합니다[1].

이는 주기적 콜백을 설정하는 데 사용되는 함수에서 쉽게 볼 수 있습니다 . 

 

코딩 섹션에서 보았듯이 Ticker 라이브러리를 사용하는 경우에만

밀리초 정확도로 이동할 수 있습니다. 

따라서 더 세분화된 것이 필요한 경우 기본 기능이 더 유연하다는 것을 아는 것이 유용합니다.

 

또한 하드웨어 타이머가 실행될 때

콜백 함수가 즉시 실행될 것이라고 맹목적으로 신뢰할 수 없습니다. 

 

IDF API의 실제 구현은

타이머 인터럽트 기능[1]에서 알림을 받는 보조 작업을 사용합니다. 

이는 ISR이 작업을 알리고 콜백을 실행하는 데 약간의 시간이 필요함을 의미합니다.

 

하나 이상의 콜백이 호출되어야 하는 경우에 대비하여

하나의 콜백이 다른 콜백을 반환한 후에만 호출된다는 점도 흥미롭습니다. 

 

이것은 주기적으로 실행하도록 등록된 콜백이 많을수록

나중에 호출되는 콜백에 대한 지연이 더 많기 때문에 중요합니다.

 

여기에서 취할 수 있는 다른 흥미로운 가정은

전역 변수에 액세스하고 이를 증가시키는 코드 섹션에서

콜백이 매우 가깝게 실행되더라도 동기화에 대해 걱정할 필요가 없다는 것입니다.

 

순서대로 실행되기 때문에 변수에 대한 동시 액세스가 되어서는 안 됩니다. 

그러나 나는 이것을 검증하지 않았으므로 이것을 가정으로 언급하는 것입니다.

 

또한 다른 타이머 핸들 과 연결된 다른 콜백 

동일한 전역 변수에 액세스하는 경우에 대비하여

이 가정이 합당한지 확인하기 위해 API를 충분히 깊이 파고들지 않았습니다. 

 

다른 콜백이 동시에 실행된다는 보장이 없음을 의미합니다. 

이러한 사용 사례가 있는 경우

응용 프로그램에서 문제를 디버그하기 어려울 수 있으므로 확인하는 것이 좋습니다.

 

esp_timer.h 라이브러리에서 읽을 수 있는 다른 중요한 권장 사항은

콜백이 많은 작업을 수행하지 않아야 하며

오히려 일부 RTOS 알림 메커니즘을 사용하여

작업을 다른 작업으로 오프로드해야 한다는 것입니다. 

 

이것은 인터럽트 핸들러에 적용되어야 하는

일반적인 규칙이며 이러한 콜백이 ISR 함수로 직접 실행되지는 않지만

다른 콜백이 실행될 때 영향을 미치는 것을 이미 보았고

일부 작업을 수행하는 동안 대기 상태로 두지 않습니다.

 

마지막으로 

IDF API를 직접 사용하는 경우에만 액세스할 수 있는 

skip_unhandled_events 구성의 존재를 언급하고 싶습니다 . 

 

이 구성은 API가 이벤트를 느슨하게 하지 않고

지연된 이벤트를 처리할 수 있도록 탄력적으로 설계되었기 때문에 존재합니다. 

이와 같이 타이머가 한 주기 이상(주기적 타이머의 경우) 동안

처리되지 않은 경우 정의된 기간을 기다리지 않고 콜백이 차례로 호출됩니다.

 

그럼에도 불구하고 일부 응용 프로그램과 호환되지 않을 수 있으므로

이 구성이 도입되었습니다. 

설정하면 콜백을 호출할 수 없는 상태로 여러 번 만료된 주기적 타이머는

처리가 가능하면 여전히 하나의 콜백 이벤트가 발생합니다

참고문헌

[1] https://github.com/espressif/esp-idf/blob/8131d6f/components/esp_timer/include/esp_timer.h

[2] https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/esp_timer.html

 

[원문인용번역] https://techtutorialsx.com/2021/08/07/esp32-ticker-library/