ESP32

ESP32 Arduino: PIR motion sensor and interrupts

기하 2021. 8. 16. 04:30

이 튜토리얼에서는 ESP32에서 실행되는 Arduino 코어를 사용하여 인터럽트 기반 접근 방식을 사용하여 PIR 모션 센서와 상호 작용하는 방법을 확인할 것입니다. 테스트는 ESP32 개발 보드에  통합된  DFRobot의 ESP32 모듈 과  DFRobot의 PIR 센서 모듈을 사용하여 수행되었습니다  .


소개

이 튜토리얼에서는 ESP32에서 실행되는 Arduino 코어를 사용하여 인터럽트 기반 접근 방식을 사용하여 PIR 모션 센서와 상호 작용하는 방법을 확인할 것입니다.

에서 이전의 튜토리얼 , 우리는 PIR 센서와 상호 작용하는 방법에 대한 기본 사항을 포함했다. 그럼에도 불구하고 ESP32에 연결된 센서 데이터 핀의 상태를 주기적으로 확인하는 폴링 방식을 따랐습니다. HIGH 상태이면 현재 일부 움직임이 감지되고 있음을 의미합니다.

그럼에도 불구하고 ESP32에서 인터럽트를 트리거하기 위해 동작이 감지되면 센서의 데이터 핀에서 LOW에서 HIGH로의 전환에 의존하여 이벤트를 알릴 수 있습니다. 그렇게 하면 폴링을 피할 수 있고 이러한 계산 주기를 사용하여 프로그램에서 더 유용한 작업을 수행할 수 있습니다.

이를 달성하려면 몇 가지 FreeRTOS 프리미티브, 보다 정확하게는 세마포어 를 사용해야 합니다.  Arduino 코어와 ESP32를 사용하는 세마포어에 대한 이전 게시물을 여기 에서  읽을 수 있습니다 .

세마포어는 일반적으로 작업 간에 공유되는 리소스에 대한 상호 배타적 액세스를 보장하고 동기화 목적으로도 사용됩니다.

우리의 경우 코드를 실행하는 주요 작업(이 예에서는 Arduino 루프가 됨)을 세마포어 호출에서 블록으로 만들 것입니다. 이 작업이 차단되는 동안 FreeRTOS 스케줄러는 다른 작업을 실행하도록 허용할 수 있습니다.

그런 다음 인터럽트가 발생하면 기본적으로 세마포어를 해제하므로 인터럽트가 완료되면 작업이 차단을 해제하고 모션 감지와 관련된 논리를 실행합니다.

이 튜토리얼에서는 센서를 마이크로컨트롤러에 연결하고 사용을 시작하는 데 필요한 모든 전자 장치가 이미 포함 된  DFRobot의 PIR 센서 모듈을 사용할 것입니다. ESP32에 대한 연결 다이어그램은 여기  확인 하십시오 .

테스트는 ESP32 개발 보드에  통합된  DFRobot의 ESP32 모듈을 사용하여 수행되었습니다  .


코드

센서에 연결될 ESP32의 GPIO 번호를 저장할 전역 변수를 선언하여 코드를 시작합니다. 이렇게 하면 나중에 필요한 경우 쉽게 변경할 수 있습니다.

1 const byte interruptPin = 22;

그런 다음 세마포어를 전역 변수로도 선언해야 인터럽트 서비스 루틴과 메인 코드에서 모두 액세스할 수 있습니다.

1 SemaphoreHandle_t syncSemaphore;

Arduino 설정으로 이동하여 프로그램 실행 결과를 출력하기 위해 직렬 연결을 열어 시작합니다.

1 Serial.begin(115200);

다음으로 세마포어를 생성합니다. 우리는 간단한 동기화를 수행하기 때문에 카운팅 세마포어 를 만들 필요가 없으며 대신 이진 세마포어  만들 수 있습니다 .

바이너리 세마포어는 뮤텍스 로 볼 수 있지만 실제로 여기에서 읽을 수 있는 몇 가지 차이점이 있습니다 . 그럼에도 불구하고 우리가 구현하는 것과 같은 동기화 목적을 위해 권장되는 프리미티브는 바이너리 세마포어[1]입니다.

따라서 이진 세마포어를 생성하려면  인수를 사용하지 않고 SemaphoreHandle_t  을 반환하는 xSemaphoreCreateBinary 함수 를 호출하기  하면 됩니다  . 이것은 관련 함수 호출에서 세마포어를 참조하는 데 사용할 핸들입니다.

인터럽트 서비스 루틴이 초기화되기 전에 세마포어를 사용하기 시작하지 않도록 인터럽트를 연결하기 전에 세마포어를 생성하는 것이 중요합니다.

1 syncSemaphore = xSemaphoreCreateBinary();

입력으로 작동하는 GPIO로 작업할 것이므로 작동 모드를 그대로 선언해야 합니다.

이를 위해 pinMode 함수를 호출하여 첫 번째 입력으로 핀 번호를 전달하고 두 번째로 작동 모드를 전달합니다. 신호가 적용되지 않을 때 핀이 알려진 상태(VCC)에 있도록 보장 하기 위해 INPUT_PULLUP  을 사용할  것입니다. 그렇지 않고 핀을 연결하지 않은 상태로 두면 GND와 VCC(각각 LOW 및 HIGH 디지털 레벨) 사이에서 부동할 수 있어 여러 개의 원치 않는 인터럽트를 트리거합니다.

1 pinMode(interruptPin, INPUT_PULLUP);

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

두 번째 인수로 인터럽트를 처리할 함수를 전달합니다. 우리는 그것을 handleInterrupt 라고 부르고 그것을 구현하는 방법을 나중에 확인할 것입니다.

세 번째이자 마지막 인수로 핀의 디지털 레벨에서 어떤 유형의 변경이 인터럽트를 트리거할지 지정해야 합니다. 우리의 경우 센서 데이터 핀이 LOW에서 HIGH로 갈 때 모션이 감지된다는 것을 알고 있습니다. 이는 신호의 상승 에지를 감지하려는 것을 의미합니다. 따라서 세 번째 인수로 상수 RISING  전달하기만 하면 됩니다 .

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

Arduino 루프 기능으로 이동하여 모션 감지를 처리합니다. 그러나 이전에 언급했듯이 센서를 지속적으로 폴링하거나 인터럽트에 의해 신호를 받는 일부 변수를 확인하는 CPU 주기를 낭비하고 싶지 않습니다.

그래서 우리가 할 일은 이전에 초기화한 세마포어를 얻는 것입니다. 세마포어는 이진법(하나의 단위를 가져오거나 없음)이며 단위 없이 초기화됩니다.

따라서 작업이 세마포어를 얻으려고 할 때 사용 가능한 단위가 없고 FreeRTOS 스케줄러가 다른 작업에 CPU 시간을 할당할 수 있는 경우 차단됩니다.

세마포어를 얻으려면 xSemaphoreTake  함수 를 호출해야  합니다. 이 함수는 첫 번째 입력으로 세마포어를 수신하고 두 번째로  세마포어에 사용할 단위가 없는 경우 대기할 FreeRTOS 틱 수를 수신합니다.

우리의 경우 세마포어를 사용할 수 있을 때까지 작업을 무기한 차단하기를 원하므로 portMAX_DELAY 값을 사용합니다  . 따라서 작업은 세마포어에서 사용할 수 있는 단위가 하나 있을 때까지 시간 초과 없이 차단된 상태로 유지됩니다.

1 xSemaphoreTake(syncSemaphore, portMAX_DELAY);

그 후, 우리는 모션이 감지되었을 때만 메인 루프가 차단 해제된다는 것을 알고 있기 때문에 모션이 감지되었다는 메시지를 출력합니다. 코드를 테스트할 때 작업이 실제로 차단되었는지 확인할 수 있습니다. 차단되지 않은 경우 프로그램이 직렬 포트에 메시지를 계속 인쇄하기 때문입니다.

1 Serial.println("Motion detected");

마치기 위해 이미 언급한 것처럼 센서에 연결된 핀이 LOW에서 HIGH로 이동하면 동작이 감지될 때 발생하는 인터럽트 서비스 루틴을 선언합니다.

그래서 기본적으로 이런 일이 발생했을 때 우리가 하고자 하는 것은 Arduino 메인 루프의 차단을 해제하는 것입니다. 이것은 단순히 세마포어에 단위를 추가하여 수행됩니다. xSemaphoreGiveFromISR  함수 를 호출하여  이를 수행합니다.

함수 이름 끝에 " FromISR " 이 있다는 점에 유의하십시오. 이는 이 호출이 인터럽트 서비스 루틴 내부에서 수행하는 것이 안전함을 나타냅니다. ISR이 아닌 다른 FreeRTOS 작업에서 세마포어에 단위를 제공하려면 대신 xSemaphoreGive 함수를 사용합니다.

xSemaphoreGiveFromISR의 함수는 먼저 입력으로 세마포어를 수신한다.

두 번째 인수로 이 함수는  세마포어를 제공하여 작업이 차단 해제되고 차단 해제된 작업이 현재 실행 중인 작업보다 우선 순위가 높은 경우 pdTRUE 값으로 설정되는 변수를 받을 수 있습니다 [2]. 우리의 경우에는 이것이 필요하지 않으므로 이 두 번째 인수에 NULL  전달 하기만 하면 됩니다.

1
2
void IRAM_ATTR handleInterrupt() {
  xSemaphoreGiveFromISR(syncSemaphore, NULL);
}

참고로 ISR 함수에  IRAM_ATTR 속성을 추가해야 한다는 사실을 잊지 마십시오 . 컴파일러에 의해 IRAM에 배치됩니다. 이에 대한 자세한 내용은 여기에서 확인할 수 있습니다 .

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

1
2

4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const byte interruptPin = 22;


SemaphoreHandle_t syncSemaphore;


void IRAM_ATTR handleInterrupt() {
  xSemaphoreGiveFromISR(syncSemaphore, NULL);
}


void setup() {


  Serial.begin(115200);


  syncSemaphore = xSemaphoreCreateBinary();


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


}


void loop() {


    xSemaphoreTake(syncSemaphore, portMAX_DELAY);
    Serial.println("Motion detected");


}


코드 테스트

이전 코드를 테스트하려면 센서와 마이크로컨트롤러 사이에 필요한 모든 배선을 수행한 후 컴파일하고 ESP32에 업로드하기만 하면 됩니다.

절차가 완료되면 Arduino IDE 직렬 모니터를 엽니다. 센서 앞에서 움직이지 않는 동안 아무 것도 인쇄되지 않습니다. 이동하는 즉시 그림 1과 같이 "모션 감지됨" 메시지가 인쇄되어야 합니다.

그림 1 - ESP32를 사용한 인터럽트 기반 모션 감지.


참고문헌

[1]  https://www.freertos.org/Embedded-RTOS-Binary-Semaphores.html

[2]  https://www.freertos.org/a00124.html