인터럽트와 타이머를 사용한 PIR 모션 센서가 있는 ESP32
이 튜토리얼은 PIR 모션 센서를 사용하여 ESP32로 모션을 감지하는 방법을 보여줍니다. 이 예에서 모션이 감지되면(인터럽트가 트리거됨) ESP32는 타이머를 시작하고 미리 정의된 초 동안 LED를 켭니다. 타이머가 카운트다운을 마치면 LED가 자동으로 꺼집니다.
이 예에서는 인터럽트와 타이머라는 두 가지 중요한 개념도 살펴보겠습니다.
이 튜토리얼을 진행하기 전에 Arduino IDE에 ESP32 애드온이 설치되어 있어야 합니다. 아직 설치하지 않았다면 다음 튜토리얼 중 하나를 따라 Arduino IDE에 ESP32를 설치하세요.
Arduino IDE 2에 ESP32 보드 설치(Windows, Mac OS X, Linux)
비디오 튜토리얼 및 프로젝트 데모 보기
이 튜토리얼은 비디오 형식(아래에서 시청)과 서면 형식(계속 읽기)으로 제공됩니다.
필요한 부품: 이 튜토리얼을 따르려면 다음 부품이 필요합니다.
- ESP32 DOIT DEVKIT V1 보드 - ESP32 개발 보드 리뷰 및 비교 읽기
- 미니 PIR 모션 센서(AM312) 또는 PIR 모션 센서(HC-SR501)
- 5mm LED
- 330옴 저항기
- 점퍼 와이어
- 브레드보드
이전 링크를 사용하거나 직접 이동하여 프로젝트에 필요한 모든 부품을 최상의 가격으로 찾을 수 있습니다!
인터럽트 소개
PIR 모션 센서로 이벤트를 트리거하려면 인터럽트를 사용합니다. 인터럽트는 마이크로컨트롤러 프로그램에서 자동으로 작업을 수행하는 데 유용하며 타이밍 문제를 해결하는 데 도움이 될 수 있습니다.
인터럽트를 사용하면 핀의 현재 값을 지속적으로 확인할 필요가 없습니다. 인터럽트를 사용하면 변경 사항이 감지되면 이벤트가 트리거됩니다(함수가 호출됨).
Arduino IDE에서 인터럽트를 설정하려면 GPIO 핀, 실행할 함수 이름, 모드를 인수로 받는 attachmentInterrupt() 함수를 사용합니다.
attachInterrupt(digitalPinToInterrupt(GPIO), function, mode);
GPIO 인터럽트
첫 번째 인수는 GPIO 번호입니다. 일반적으로 digitalPinToInterrupt(GPIO)를 사용하여 실제 GPIO를 인터럽트 핀으로 설정해야 합니다. 예를 들어 GPIO 27을 인터럽트로 사용하려면 다음을 사용합니다.
digitalPinToInterrupt(27)
ESP32 보드의 경우 다음 그림에서 빨간색 사각형으로 강조 표시된 모든 핀을 인터럽트 핀으로 구성할 수 있습니다. 이 예에서는 GPIO 27을 PIR 모션 센서에 연결된 인터럽트로 사용합니다.
트리거할 함수
attachInterrupt() 함수의 두 번째 인수는 인터럽트가 트리거될 때마다 호출되는 함수의 이름입니다.
모드: 세 번째 인수는 모드입니다. 5가지 모드가 있습니다.
- LOW: 핀이 LOW일 때마다 인터럽트를 트리거합니다.
- HIGH: 핀이 HIGH일 때마다 인터럽트를 트리거합니다.
- CHANGE: 핀 값이 변경될 때마다 인터럽트를 트리거합니다(예: HIGH에서 LOW 또는 LOW에서 HIGH로).
- FALLING: 핀이 HIGH에서 LOW로 전환될 때.
- RISING: 핀이 LOW에서 HIGH로 전환될 때 트리거합니다.
이 예제에서는 RISING 모드를 사용합니다. PIR 모션 센서가 모션을 감지하면 연결된 GPIO가 LOW에서 HIGH로 전환되기 때문입니다.
타이머 소개
이 예제에서는 타이머도 소개합니다. 모션이 감지된 후 미리 정해진 초 동안 LED가 켜져 있기를 원합니다. 코드를 차단하고 정해진 초 동안 다른 작업을 할 수 없게 하는 delay() 함수를 사용하는 대신 타이머를 사용해야 합니다.
delay() 함수
널리 사용되므로 delay() 함수에 대해 잘 알고 있어야 합니다. 이 함수는 사용하기 매우 간단합니다. 인수로 단일 int 숫자를 허용합니다. 이 숫자는 프로그램이 다음 코드 줄로 넘어갈 때까지 기다려야 하는 시간(밀리초)을 나타냅니다.
delay(time in millseconds)
delay(1000)을 호출하면 프로그램이 해당 줄에서 1초 동안 멈춥니다.
delay()는 차단 함수입니다. 차단 함수는 해당 작업이 완료될 때까지 프로그램이 다른 작업을 수행하지 못하도록 합니다. 여러 작업을 동시에 수행해야 하는 경우 delay()를 사용하지 않아야 합니다. 물속에 잠수해서 깨어나지 않습니다.
대부분 프로젝트에서는 지연을 사용하지 말고 대신 타이머를 사용해야 합니다.
millis() 함수
millis()라는 함수를 사용하면 프로그램이 처음 시작된 이후로 경과한 밀리초 수를 반환할 수 있습니다.
millis()
이 함수가 유용한 이유는 무엇일까요? 수학을 사용하면 코드를 차단하지 않고도 얼마나 많은 시간이 지났는지 쉽게 확인할 수 있기 때문입니다.
millis()로 LED 깜박이기
다음 코드 조각은 millis() 함수를 사용하여 깜박이는 LED 프로젝트를 만드는 방법을 보여줍니다. LED를 1000밀리초 동안 켠 다음 끕니다.
/*********
Rui Santos
Complete project details at https://randomnerdtutorials.com
*********/
// constants won't change. Used here to set a pin number :
const int ledPin = 26; // the number of the LED pin
// Variables will change :
int ledState = LOW; // ledState used to set the LED
// Generally, you should use "unsigned long" for variables that hold time
// The value will quickly become too large for an int to store
unsigned long previousMillis = 0; // will store last time LED was updated
// constants won't change :
const long interval = 1000; // interval at which to blink (milliseconds)
void setup() {
// set the digital pin as output:
pinMode(ledPin, OUTPUT);
}
void loop() {
// here is where you'd put code that needs to be running all the time.
// check to see if it's time to blink the LED; that is, if the
// difference between the current time and last time you blinked
// the LED is bigger than the interval at which you want to
// blink the LED.
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
// save the last time you blinked the LED
previousMillis = currentMillis;
// if the LED is off turn it on and vice-versa:
if (ledState == LOW) {
ledState = HIGH;
} else {
ledState = LOW;
}
// set the LED with the ledState of the variable:
digitalWrite(ledPin, ledState);
}
}
코드 작동 방식
delay() 함수 없이 작동하는 이 블링크 스케치를 자세히 살펴보겠습니다(대신 millis() 함수를 사용합니다).
기본적으로 이 코드는 이전에 기록된 시간(previousMillis)을 현재 시간(currentMillis)에서 뺍니다. 나머지가 간격(이 경우 1000밀리초)보다 크면 프로그램은 previousMillis 변수를 현재 시간으로 업데이트하고 LED를 켜거나 끕니다.
if (currentMillis - previousMillis >= interval) {
// save the last time you blinked the LED
previousMillis = currentMillis;
(...)
이 스니펫은 프로그램 진행을 차단하지 않으므로 첫 번째 if 문 밖에 있는 모든 코드는 정상적으로 작동해야 합니다.
이제 loop() 함수에 다른 작업을 추가해도 코드가 1초마다 LED를 깜빡일 수 있다는 것을 이해할 수 있을 것입니다.
이 코드를 ESP32에 업로드하고 다음 회로도를 조립하여 테스트하고 밀리초 수를 수정하여 작동 방식을 확인할 수 있습니다.
참고: ESP32에 코드를 업로드하는 데 문제가 발생한 경우 ESP32 문제 해결 가이드를 참조하세요.
PIR 모션 센서가 있는 ESP32
인터럽트와 타이머라는 개념을 이해한 후 프로젝트를 계속 진행해 보겠습니다.
회로도
만들 회로는 조립하기 쉽고 저항기가 있는 LED를 사용합니다. LED는 GPIO 26에 연결됩니다. 3.3V에서 작동하는 Mini AM312 PIR 모션 센서를 사용합니다. GPIO 27에 연결됩니다. 다음 회로도를 따르기만 하면 됩니다.
중요: 이 프로젝트에서 사용된 Mini AM312 PIR 모션 센서는 3.3V에서 작동합니다. 그러나 HC-SR501과 같은 다른 PIR 모션 센서를 사용하는 경우 5V에서 작동합니다. 3.3V에서 작동하도록 수정하거나 Vin 핀을 사용하여 전원을 공급할 수 있습니다.
다음 그림은 AM312 PIR 모션 센서 핀아웃을 보여줍니다.
AM312 미니 PIR 핀아웃
코드 업로드
회로도에 표시된 대로 회로를 연결한 후 제공된 코드를 Arduino IDE에 복사합니다.
코드를 그대로 업로드하거나 모션을 감지한 후 LED가 켜지는 초 수를 수정할 수 있습니다. timeSeconds 변수를 원하는 초 수로 변경하기만 하면 됩니다.
/*********
Rui Santos
Complete project details at https://randomnerdtutorials.com
*********/
#define timeSeconds 10
// Set GPIOs for LED and PIR Motion Sensor
const int led = 26;
const int motionSensor = 27;
// Timer: Auxiliary variables
unsigned long now = millis();
unsigned long lastTrigger = 0;
boolean startTimer = false;
boolean motion = false;
// Checks if motion was detected, sets LED HIGH and starts a timer
void IRAM_ATTR detectsMovement() {
digitalWrite(led, HIGH);
startTimer = true;
lastTrigger = millis();
}
void setup() {
// Serial port for debugging purposes
Serial.begin(115200);
// PIR Motion Sensor mode INPUT_PULLUP
pinMode(motionSensor, INPUT_PULLUP);
// Set motionSensor pin as interrupt, assign interrupt function and set RISING mode
attachInterrupt(digitalPinToInterrupt(motionSensor), detectsMovement, RISING);
// Set LED to LOW
pinMode(led, OUTPUT);
digitalWrite(led, LOW);
}
void loop() {
// Current time
now = millis();
if((digitalRead(led) == HIGH) && (motion == false)) {
Serial.println("MOTION DETECTED!!!");
motion = true;
}
// Turn off the LED after the number of seconds defined in the timeSeconds variable
if(startTimer && (now - lastTrigger > (timeSeconds*1000))) {
Serial.println("Motion stopped...");
digitalWrite(led, LOW);
startTimer = false;
motion = false;
}
}
참고: ESP32에 코드를 업로드하는 데 문제가 있는 경우 ESP32 문제 해결 가이드를 참조하세요.
코드 작동 방식
코드를 살펴보겠습니다. 먼저 두 개의 GPIO 핀을 led 및 motionSensor 변수에 할당합니다.
// LED 및 PIR 모션 센서에 대한 GPIO 설정
const int led = 26;
const int motionSensor = 27;
그런 다음 모션이 감지된 후 LED를 끄는 타이머를 설정할 수 있는 변수를 만듭니다.
// Timer: Auxiliar variables
long now = millis();
long lastTrigger = 0;
boolean startTimer = false;
now 변수는 현재 시간을 보관합니다. lastTrigger 변수는 PIR 센서가 동작을 감지할 때의 시간을 보관합니다. startTimer는 동작이 감지되면 타이머를 시작하는 부울 변수입니다.
setup()
setup()에서 먼저 직렬 포트를 115200 보드 속도로 초기화합니다.
Serial.begin(115200);
PIR 동작 센서를 INPUT PULLUP으로 설정합니다.
pinMode(motionSensor, INPUT_PULLUP);
PIR 센서 핀을 인터럽트로 설정하려면 앞에서 설명한 대로 attachmentInterrupt() 함수를 사용합니다.
attachmentInterrupt(digitalPinToInterrupt(motionSensor), detectsMovement, RISING);
동작을 감지하는 핀은 GPIO 27이고 RISING 모드에서 detectsMovement() 함수를 호출합니다.
LED는 상태가 LOW에서 시작하는 OUTPUT입니다.
pinMode(led, OUTPUT);
digitalWrite(led, LOW);
loop()
loop() 함수는 끊임없이 반복해서 실행됩니다. 모든 루프에서 now 변수는 현재 시간으로 업데이트됩니다.
now = mills();
loop()에서 다른 작업은 수행되지 않습니다.
하지만 움직임이 감지되면 detectsMovement() 함수가 호출됩니다. setup()에서 이전에 인터럽트를 설정했기 때문입니다.
detectsMovement() 함수는 직렬 모니터에 메시지를 인쇄하고, LED를 켜고, startTimer 부울 변수를 true로 설정하고, lastTrigger 변수를 현재 시간으로 업데이트합니다.
void IRAM_ATTR detectsMovement() {
Serial.println("MOTION DETECTED!!!");
digitalWrite(led, HIGH);
startTimer = true;
lastTrigger = millis();
}
참고: IRAM_ATTR은 RAM에서 인터럽트 코드를 실행하는 데 사용됩니다. 그렇지 않으면 코드가 플래시에 저장되어 속도가 느려집니다.
이 단계 후에 코드는 loop()로 돌아갑니다.
이번에는 startTimer 변수가 참입니다. 따라서 초 단위로 정의된 시간이 지나면(모션이 감지된 이후) 다음 if 문이 참이 됩니다.
if(startTimer && (now - lastTrigger > (timeSeconds*1000))) {
Serial.println("Motion stopped...");
digitalWrite(led, LOW);
startTimer = false;
}
“Motion ceased..." 메시지가 직렬 모니터에 인쇄되고 LED가 꺼지며 startTimer 변수가 false로 설정됩니다.
데모
ESP32 보드에 코드를 업로드합니다. 올바른 보드와 COM 포트를 선택했는지 확인합니다.
시리얼 모니터를 115200의 전송 속도로 엽니다.
PIR 센서 앞으로 손을 움직입니다. LED가 켜지고 시리얼 모니터에 "움직임이 감지되었습니다!!!"라는 메시지가 인쇄됩니다. 10초 후에 LED가 꺼집니다.
마무리
마무리로, 인터럽트는 현재 GPIO 값을 계속 읽을 필요 없이 GPIO 상태의 변화를 감지하는 데 사용됩니다. 인터럽트를 사용하면 변화가 감지되면 함수가 트리거됩니다. 또한 코드를 차단하지 않고도 미리 정의된 초가 지났는지 확인할 수 있는 간단한 타이머를 설정하는 방법도 알아보았습니다.
ESP32와 관련된 다른 튜토리얼도 있으니 마음에 드실 겁니다.
ESP32 웹 서버 - Arduino IDE
ESP32 MicroSD 카드에 온도 데이터 로깅
Arduino IDE에서 ESP32와 함께 I2C LCD를 사용하는 방법
ESP32 대 ESP8266 - 장단점
다음은 저희 과정인 Arduino IDE로 ESP32 배우기에서 발췌한 것입니다. ESP32를 좋아하고 더 자세히 알고 싶으시다면 Arduino IDE로 ESP32 배우기 과정에 등록하는 것이 좋습니다.
읽어주셔서 감사합니다. 배움을 멈추지 마세요.
하지 않은 일에 대한 후회의 고통은 도전하고 실패한 고통보다 훨씬 큽니다.
삶은 고통이 연속이니 즐기세요!
'ESP32' 카테고리의 다른 글
ESP32 BLE 서버 및 클라이언트(Bluetooth Low Energy) (1) | 2025.01.08 |
---|---|
ESP32 Bluetooth Low Energy(BLE) 시작하기 (1) | 2025.01.06 |
ESP32 LoRa 송신 수신기 제작 방법 (0) | 2025.01.06 |
ESP32 웨이크업 소스를 사용한 Deep Sleep (1) | 2025.01.03 |
ESP32 DHT22 센서 및 OLED 디스플레이를 사용한 온도 및 습도 모니터링 (1) | 2024.12.27 |
ESP32 ADC 읽기 Arduino IDE (2) | 2024.12.24 |
ESP32 PWM 아날로그 출력 Arduino IDE (3) | 2024.12.23 |
ESP32 디지털 입력 및 디지털 출력(Arduino IDE) (2) | 2024.12.20 |
더욱 좋은 정보를 제공하겠습니다.~ ^^