ESP32

ESP32 Arduino: External interrupts

기하 2021. 8. 16. 04:12

소개

이 게시물의 목적은 ESP32와 Arduino 코어를 사용하여 외부 인터럽트를 처리하는 방법을 설명하는 것입니다.

테스트는 ESP32 FireBeetle 보드에  통합된 DFRobot의 ESP-WROOM-32 장치에서  수행되었습니다  .


설정 코드

인터럽트가 전역 변수에 연결될 핀을 선언하는 것으로 시작합니다. ESP32 보드에 따라 ESP32 마이크로컨트롤러의 핀 번호와 보드에 표시된 핀 번호가 일치하지 않을 수 있습니다. FireeBeetle 보드에서 아래에 사용된 핀(디지털 핀 25)은 IO25/D2 라고 표시된 핀과 일치합니다 .

1 const byte interruptPin = 25;

또한 인터럽트 루틴이 메인 루프 기능과 통신하고 인터럽트가 발생했음을 알리는 카운터를 선언할 것입니다. 이 변수는 ISR과 기본 코드에서 공유되므로 휘발성으로 선언해야 합니다. 그렇지 않으면 컴파일러 최적화로 인해 제거될 수 있습니다.

1 volatile int interruptCounter = 0;

또한 프로그램 시작 이후 전역적으로 발생한 인터럽트 수를 추적하기 위해 카운터를 선언합니다. 따라서 이 카운터는 인터럽트가 발생할 때마다 증가합니다.

1 int numberOfInterrupts = 0;

마지막으로 메인 코드와 인터럽트 간의 동기화를 처리하는 데 필요한 portMUX_TYPE 유형의 변수를 선언합니다  . 나중에 어떻게 사용하는지 알아보겠습니다.

1 portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;

설정 기능으로 이동하여 프로그램 결과를 출력할 수 있도록 직렬 연결을 열어 시작합니다.

1
2
Serial.begin(115200);
Serial.println("Monitoring interrupts: ");

다음으로 외부 핀 인터럽트로 작업할 것이므로 이전에 선언된 핀 번호를 입력 핀으로 구성해야 합니다. 이를 위해 pinMode 함수를 호출 하여 핀 번호와 작동 모드를 인수로 전달합니다.

핀에 전기 신호가 가해지지 않을 때 입력 상태를 알기 위해 INPUT_PULLUP 모드를 사용 합니다. 따라서 신호가 적용되지 않으면 부동 대신 VCC 의 전압 레벨이 되어 존재하지 않는 외부 인터럽트의 감지를 방지합니다.

1 pinMode(interruptPin, INPUT_PULLUP);

다음으로 attachInterrupt 함수를 호출하여 핀에 인터럽트를 연결합니다 . 첫 번째 인수 로 사용된 핀 번호를 해당 내부 인터럽트 번호로 변환하는 digitalPinToInterrupt 함수 에 대한 호출 결과를 전달합니다 .

다음으로 인터럽트를 처리할 함수를 전달합니다. 즉, 지정된 핀에서 인터럽트가 발생할 때 실행됩니다. 우리는 그것을 handleInterrupt 라고 부르고 나중에 그 코드를 지정할 것입니다.

마지막으로 핀 입력 신호의 변경 유형이 인터럽트를 트리거하는지 기본적으로 지정하는 인터럽트 모드를 전달합니다. 우리는 FALLING 을 사용할 것인데, 이는 VCC에서 GND로의 변화가 핀에서 감지될 때 인터럽트가 발생한다는 것을 의미합니다.

1 attachInterrupt(digitalPinToInterrupt(interruptPin), handleInterrupt, FALLING);

최종 설정 코드는 아래에서 볼 수 있습니다.

1
2

4
5
6
7
8
9
10
11
12
13
14
15
const byte interruptPin = 25;
volatile int interruptCounter = 0;
int numberOfInterrupts = 0;


portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;


void setup() {


  Serial.begin(115200);
  Serial.println("Monitoring interrupts: ");


  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), handleInterrupt, FALLING);


}


메인 루프

이제 메인 루프로 이동합니다. 여기서 인터럽트 카운터가 0보다 큰지 간단히 확인할 것입니다. 그렇다면 처리할 인터럽트가 있음을 의미합니다.

따라서 인터럽트가 발생하면 먼저 이 인터럽트 카운터를 감소시켜 인터럽트가 감지되었으며 처리될 것임을 알립니다.

이 카운터 접근 방식은 플래그를 사용하는 것보다 낫습니다. 메인 코드에서 모든 인터럽트를 처리할 수 없는 상태에서 여러 인터럽트가 발생하면 이벤트가 손실되지 않기 때문입니다. 반면에 플래그를 사용하고 메인 코드에서 처리할 수 없는 여러 인터럽트가 발생하면 플래그 값은 ISR에서 계속 true로 설정되고 메인 루프 핸들러는 하나만 발생한 것처럼 해석합니다. .

명심해야 할 다른 중요한 측면은 인터럽트와 공유되는 변수에 쓸 때 인터럽트를 비활성화해야 한다는 것입니다. 이렇게 하면 기본 코드와 ISR 사이에 동시 액세스가 없도록 합니다.

Arduino 환경에서는 일반적으로 인터럽트  를 비활성화하고 다시 활성화 하는 NoInterrupts  Interrupts 기능이 있습니다. 그럼에도 불구하고 작성 당시 이러한 기능은 ESP32 Arduino 코어에서 아직 구현되지 않았습니다 .

따라서 우리는 portENTER_CRITICAL 및  portEXIT_CRITICAL 매크로를 사용하여 선언하는 임계 섹션 내에서 변수 감소를 수행합니다  . 이러한 호출은 모두 이전에 선언된 전역 portMUX_TYPE 변수 의 주소를 입력으로 받습니다 .

1
2

4
5
6
7
8
if(interruptCounter>0){


      portENTER_CRITICAL(&mux);
      interruptCounter--;
      portEXIT_CRITICAL(&mux);


      //Handle the interrupt
}

카운터 감소를 처리한 후 이제 프로그램 시작 이후 감지된 인터럽트 수를 보유하는 전역 카운터를 증가시킵니다. 이 변수는 인터럽트 서비스 루틴이 액세스하지 않기 때문에 임계 섹션 내에서 증가할 필요가 없습니다.

그런 다음 인터럽트가 감지되었으며 지금까지 발생한 인터럽트 수를 나타내는 메시지를 인쇄합니다. ISR이 가능한 한 빨리 실행되도록 설계되어야 하기 때문에 직렬 포트로 데이터를 보내는 것은 인터럽트 서비스 루틴 내에서 수행되어서는 안 됩니다. 이렇게 하면 런타임 문제가 발생할 가능성이 큽니다.

이런 식으로 우리 아키텍처에서 ISR은 인터럽트가 발생했음을 메인 루프에 신호하는 간단한 작업만 처리하고 메인 루프가 나머지를 처리합니다.

아래에서 전체 메인 루프 코드를 확인할 수 있습니다.

1
2

4
5
6
7
8
9
10
11
12
13
void loop() {


  if(interruptCounter>0){


      portENTER_CRITICAL(&mux);
      interruptCounter--;
      portEXIT_CRITICAL(&mux);


      numberOfInterrupts++;
      Serial.print("An interrupt has occurred. Total: ");
      Serial.println(numberOfInterrupts);
  }
}


인터럽트 처리 기능

코드를 끝내기 위해 인터럽트 처리 함수를 선언합니다. 이전에 언급했듯이 인터럽트가 발생했음을 메인 루프에 알리는 데 사용되는 전역 변수의 증가만 처리합니다.

또한 portENTER_CRITICAL_ISR 및  portExit_CRITICAL_ISR 매크로를 호출하여 선언하는 중요한 섹션에 이 작업을 묶습니다  . 또한 둘 다 전역 portMUX_TYPE 변수 의 주소를 입력으로 받습니다 .

이것은 우리가 사용할 변수도 앞에서 본 것처럼 메인 루프에 의해 변경되고 동시 액세스 문제를 방지해야 하기 때문에 필요합니다.

업데이트: 컴파일러가 코드를 IRAM에 배치하려면 인터럽트 처리 루틴에 IRAM_ATTR 속성 이 있어야 합니다 . 볼 수 있듯이 또한, 인터럽트 처리 루틴은, 또한 IRAM에 배치 함수를 호출해야 여기 IDF의 설명서에. 이 점에 대해 Manuato에게 감사드립니다.

인터럽트 처리 기능의 전체 코드는 다음과 같습니다.

1
2

4
5
void IRAM_ATTR handleInterrupt() {
  portENTER_CRITICAL_ISR(&mux);
  interruptCounter++;
  portEXIT_CRITICAL_ISR(&mux);
}


최종 코드

최종 소스 코드는 아래에서 볼 수 있습니다. 이를 테스트하기 위해 Arduino 환경에 복사하여 붙여넣을 수 있습니다.

1
2

4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const byte interruptPin = 25;
volatile int interruptCounter = 0;
int numberOfInterrupts = 0;


portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;


void IRAM_ATTR handleInterrupt() {
  portENTER_CRITICAL_ISR(&mux);
  interruptCounter++;
  portEXIT_CRITICAL_ISR(&mux);
}


void setup() {


  Serial.begin(115200);
  Serial.println("Monitoring interrupts: ");
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), handleInterrupt, FALLING);


}


void loop() {


  if(interruptCounter>0){


      portENTER_CRITICAL(&mux);
      interruptCounter--;
      portEXIT_CRITICAL(&mux);


      numberOfInterrupts++;
      Serial.print("An interrupt has occurred. Total: ");
      Serial.println(numberOfInterrupts);
  }
}


코드 테스트

코드를 테스트하려면 ESP32에 코드를 업로드하고 Arduino IDE 직렬 모니터를 여십시오. 인터럽트를 트리거하는 가장 쉬운 방법은 와이어를 사용하여 인터럽트가 GND에 연결된 디지털 핀을 연결 및 연결 해제하는 것입니다.

핀이 INPUT_PULLUP 으로 선언 되었으므로 VCC에서 GND로의 전환이 트리거되고 외부 인터럽트가 감지됩니다. 접지 핀을 잘못된 GPIO에 연결하여 보드를 손상시키지 않도록 주의하십시오.

그림 1과 유사한 출력이 나타나야 합니다. 여기에서 인터럽트가 트리거되고 전역 카운터가 인쇄됩니다.

그림 1 - 인터럽트 처리 프로그램의 출력.