개발자/라즈베리파이4

Raspberry Pi 4 GPIO 인터럽트 시작하기

지구빵집 2021. 8. 11. 17:20
반응형

 

 

Raspberry Pi 4 GPIO 인터럽트 시작하기 

 

오늘은 RPi.GPIO 라이브러리를 사용하여 Raspberry Pi에서 GPIO 인터럽트를 사용하는 방법을 알려 드리겠습니다. 당신은 인터럽트가 필요한 이유와 시기, 프로그램에서 인터럽트를 사용하는 방법, 다양한 상황에서 연습할 수 있는 3가지 작업 예제를 배울 수 있습니다. 이 블로그 예제는 Raspbian 운영체제  및 Ubuntu Mate 운영체제와 함께 Raspberry Pi 4(및 이전 버전, 3B, 3B+)에서 작동합니다. 

 

보통 GPIO(범용 목적의 입출력 핀)의 입력 핀 상태를 확인하는 두 가지 주요 방법

 

폴링 및 인터럽트. 폴링은 입력 핀을 계속 감시하는 것입니다. while 루프에서 입력으로 설정한 핀을 계속 감시하는 방식입니다. 실제 사례를 통한 차이점은 다음과 같습니다. 중요한 이메일을 기다리고 있고 도착하는 즉시 열어보고 싶다고 상상해 보십시오. 문제를 해결하는 방법은 다음 중 하나를 수행할 수 있습니다. 

 

  • 이메일을 받을 때까지 5분, 1분 또는 10초마다 이메일을 확인하십시오.
  • 또는 알림 벨을 활성화하여 이메일이 도착하는 즉시 화면에 팝업을 표시할 수 있습니다.

짐작할 수 있듯이 이 경우 첫 번째 폴링 방법보다 두 번째 인터럽트 방법이 훨씬 더 효율적입니다. 인터럽트를 다룰 때 "알림 Notice"을 생각하십시오. 인터럽트(알림)가 모든 문제에 대한 해결책은 아니며 상황에 따라 다릅니다. 

 

RPi.GPIO를 사용한 폴링 예

 

이제 Raspberry Pi의 실제 GPIO로 돌아가 보겠습니다. 이 자습서의 시작 부분에서는 Raspberry Pi 보드에 연결된 간단한 푸시 버튼을 사용합니다. 

 

 

다음은 하드웨어 설정입니다(필요한 경우: Raspberry Pi 핀아웃 가이드 ). 누름 버튼의 4개 다리가 모두 브레드보드에서 분리되어 있는지 확인합니다. 한쪽 다리를 접지(GND)에 연결합니다. 다른 다리를 GPIO 16에 연결합니다(풀업 저항이 필요하지 않으며 코드에서 사용할 내부 저항이 있습니다). 그래서 우리는 화면에 무언가를 인쇄하여 사용자가 버튼을 누를 때를 알고 싶습니다. 먼저 Python과 RPi.GPIO 모듈을 사용하여 폴링을 수행해 보겠습니다. 

 

#!/usr/bin/env python3
import time
import RPi.GPIO as GPIO
BUTTON_GPIO = 16
if __name__ == '__main__':
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(BUTTON_GPIO, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    pressed = False
    while True:
        # button is pressed when pin is LOW
        if not GPIO.input(BUTTON_GPIO):
            if not pressed:
                print("Button pressed!")
                pressed = True
        # button not pressed (or released)
        else:
            pressed = False
        time.sleep(0.1)

 

먼저 버튼의 핀을 입력으로 설정하고 풀업 저항(pull_up_down=GPIO.PUD_UP). GPIO 의 기본 상태 (버튼을 누르지 않았을 때)는 HIGH입니다. 버튼을 누르면 상태가 LOW로 변경됩니다.

 

그런 다음 루프에서 다음을 사용하여 버튼의 상태를 읽습니다. GPIO. 입력 (). "pressed" 플래그 덕분에 버튼이 해제 상태에서 눌림 상태로 전환될 때 메시지를 한 번만 인쇄합니다.

 

이제 이 코드를 실행하고 버튼을 누르면 "버튼을 눌렀습니다!"가 표시됩니다. 화면에 인쇄됩니다. 그러나 여기서 우리가 만나는 몇 가지 문제는 다음과 같습니다.

 

  • 이 예에서 폴링 주파수는 10Hz(0.1초마다)입니다. 버튼을 너무 빨리 누르면 놓칠 수 있습니다. 인간으로서는 0.1초가 상당히 빠르지만, 기계에게는 아주 긴 시간입니다.
  • 버튼이 눌렸다는 것을 알더라도 정확히 언제 눌렀는지 모릅니다. 지금부터 0.1초 전 사이에 언제든지 가능합니다.
  • 이전 두 지점의 시간을 줄이려면 빈도를 높일 수 있습니다. 10Hz에서 100Hz, 또는 1000Hz 또는 3000Hz까지 가능합니다. 여기서 문제는 폴링 빈도를 높일수록 프로그램이 CPU를 더 많이 사용한다는 것입니다. 핀 상태를 확인하기 위해 전체 CPU를 사용하는 것은 약간 과도합니다. 또한 주파수를 증가시킨다고 앞의 두 가지 문제가 해결되지 않습니다. 핀 상태가 너무 빨리 변경되면 프로그램이 여전히 이를 놓칠 수 있으며 여전히 핀을 눌렀을 때 정확히 알 수 없습니다.

여기서 해결책은 이러한 문제를 처리해야 할 때 인터럽트를 사용하는 것입니다. 

 

Raspberry Pi GPIO 인터럽트 작동 방식

 

신호의 상태(LOW/HIGH)가 변경되면 인터럽트가 트리거됩니다. 

 

 

인터럽트에는 2가지 종류가 있습니다.

  • RISING: 상태가 LOW에서 HIGH로 바뀔 때.
  • FALLING: 상태가 HIGH에서 LOW로 바뀔 때.

따라서 기본적으로 프로그램에서 FALLING 인터럽트를 설정하면 신호가 HIGH에서 LOW로 이동하는 즉시 프로그램에 알림이 전송됩니다. 더 이상 신호의 상태를 폴링 할 필요가 없습니다.

 

Arduino의 인터럽트에 이미 익숙하다면 개념이 비슷하다는 점에 유의하십시오. 자세한 내용은 이 Arduino 인터럽트 자습서 를 확인하십시오. 그러나 Arduino 인터럽트는 실제 하드웨어 인터럽트이며 Raspberry Pi에서는 소프트웨어로만 수행됩니다.

 

이 튜토리얼의 나머지 부분에서는 Python과 RPi.GPIO 모듈을 사용하여 Raspberry Pi GPIO 인터럽트로 작업하는 방법을 보여줍니다. 

 

RPi.GPIO wait_for_edge()로 인터럽트 

 

#!/usr/bin/env python3

import RPi.GPIO as GPIO

BUTTON_GPIO = 16

if __name__ == '__main__':
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(BUTTON_GPIO, GPIO.IN, pull_up_down=GPIO.PUD_UP)

    while True:
        GPIO.wait_for_edge(BUTTON_GPIO, GPIO.FALLING)
        print("Button pressed!")

 

사용 wait_for_edge ()GPIO의 상태가 변경될 때까지 대기(차단)하는 기능입니다. 이 함수는 2(+1 선택적) 인수를 취합니다.

 

채널: 여기에 GPIO 번호(BCM 모드)가 있습니다. 인터럽트 유형. (선택 사항) 시간 초과. 여기서는 내부 풀업 저항으로 인해 버튼의 상태가 기본적으로 HIGH이므로 인터럽트 유형에 대해 GPIO.FALLING을 사용했습니다. 버튼을 놓을 때까지 기다리려면 대신 GPIO.RISING을 사용하십시오. 그리고 세 번째 옵션이 있습니다. 버튼을 누르거나 놓을 때까지 기다리려면(상승 및 하강 모두) BOTH를 사용하십시오. 이 경우 신호가 LOW인지 HIGH인지 알기 위해 버튼의 상태를 읽어야 합니다.

 

예를 들어: 

 

while True:
        GPIO.wait_for_edge(BUTTON_GPIO, GPIO.BOTH)
        if not GPIO.input(BUTTON_GPIO):
            print("Button pressed!")
        else:
            print("Button released!")

 

참고: 지금은 디바운스 메커니즘을 사용하지 않았으므로 때때로 이상한 동작이 나타날 수 있습니다. 버튼은 누르거나 놓으면 튕기는 물리적 시스템이므로 버튼을 한 번만 누른 것처럼 여러 번 누른 것처럼 보일 수 있습니다. 

 

add_event_detect() 및 스레드 콜백으로 인터럽트 사용

 

wait_for_edge ()좋지만 메인 스레드를 완전히 차단합니다. 이 함수를 실행 중인 다른 코드와 함께 사용하려면 여러 스레드를 직접 생성해야 합니다. RPi.GPIO 모듈에는 사용하기에 더 실용적인 또 다른 기능이 있습니다. add_event_detected (), 콜백 기능과 함께. 이것은 대부분의 시간에 사용할 솔루션입니다. 이것이 어떻게 작동하는지 봅시다. 

 

RPi.GPIO를 사용한 Python 코드 

 

#!/usr/bin/env python3

import signal
import sys
import RPi.GPIO as GPIO

BUTTON_GPIO = 16

def signal_handler(sig, frame):
    GPIO.cleanup()
    sys.exit(0)

def button_pressed_callback(channel):
    print("Button pressed!")

if __name__ == '__main__':
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(BUTTON_GPIO, GPIO.IN, pull_up_down=GPIO.PUD_UP)

    GPIO.add_event_detect(BUTTON_GPIO, GPIO.FALLING, 
            callback=button_pressed_callback, bouncetime=100)
    
    signal.signal(signal.SIGINT, signal_handler)
    signal.pause()

 

이 코드를 단계별로 분석해 보겠습니다.

 

코드 설명 

 

#!/usr/bin/env python3
import signal
import sys
import RPi.GPIO as GPIO
BUTTON_GPIO = 16

먼저 필요한 가져오기를 추가합니다. 신호 모듈을 사용하여 CTRL+C를 처리하고 프로그램을 무기한 일시 중지할 수 있습니다. 

 

def signal_handler(sig, frame):
    GPIO.cleanup()
    sys.exit(0)

이 콜백은 사용자가 CTRL+C를 누를 때 트리거됩니다(아래에서 기본 기능에서 설정합니다). 실제로 프로그램을 종료하기 전에 정리를 할 수 있는 기회입니다. 

 

def button_pressed_callback(channel):
    print("Button pressed!")

에 대한 콜백입니다. add_event_detect ()함수. 선택한 핀이 있는 인터럽트가 트리거되면 이 콜백이 호출됩니다. 콜백에서 채널 또는 GPIO 번호를 매개변수로 받습니다. 

 

if __name__ == '__main__':
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(BUTTON_GPIO, GPIO.IN, pull_up_down=GPIO.PUD_UP)

여기에 새로운 것은 없습니다. 내부 풀업 저항이 활성화된 상태에서 이전과 같은 방식으로 버튼 GPIO를 초기화합니다. 

 

GPIO.add_event_detect(BUTTON_GPIO, GPIO.FALLING, 
            callback=button_pressed_callback, bouncetime=100)

 

입력할 GPIO 모드를 초기화한 후 여기에서 사용할 수 있습니다. add_event_detect ()이 GPIO에서 인터럽트에 대한 콜백을 등록하는 함수입니다. 이 함수는 최대 4개의 매개변수를 사용합니다. 

 

채널: GPIO 번호(여기서는 BCM 모드이므로). 인터럽트 유형: GPIO.FALLING, GPIO.RISING 또는 GPIO.BOTH. (옵션) callback: 인터럽트가 발생했을 때 호출되는 함수. 다른 함수에 콜백을 등록하도록 선택할 수 있기 때문에 이것은 선택 사항입니다.add_event_callback () (하지만 여기에는 의미가 없습니다) 또는 콜백을 전혀 사용하지 않고 인터럽트가 다음으로 트리거되었는지 확인할 수 있습니다. 이벤트_감지 () (하지만 이 경우 폴링 모드로 돌아갑니다). (선택 사항) 바운스 시간(밀리초). 버튼 바운스로 인해 짧은 시간에 여러 인터럽트가 트리거되는 경우 콜백은 한 번만 호출됩니다. 

 

그래서, 우리가 그것에 준 매개변수를 가지고, add_event_detect () 기능이 트리거됩니다 button_pressed_callback () 버튼의 GPIO 신호가 떨어지자 마자(HIGH에서 LOW로) 디바운스 시간이 100밀리초입니다. 

 

    signal.signal(signal.SIGINT, signal_handler)
    signal.pause()

 

마지막으로 CTRL+C 처리를 위한 콜백을 등록하고 다음을 사용하여 프로그램을 무기한 일시 중지합니다. 신호. 일시 중지 () (왜냐하면 우리가 그렇게 하지 않으면 main 함수가 바로 종료될 것이기 때문입니다). 

 

개선: 상승 및 하강 신호 모두 감지

 

신호의 두 변경 사항을 모두 감지하려는 경우 코드는 다음과 같습니다. 

 

#!/usr/bin/env python3          
                                
import signal                   
import sys
import RPi.GPIO as GPIO

BUTTON_GPIO = 16

def signal_handler(sig, frame):
    GPIO.cleanup()
    sys.exit(0)

def button_callback(channel):
    if not GPIO.input(BUTTON_GPIO):
        print("Button pressed!")
    else:
        print("Button released!")

if __name__ == '__main__':
    GPIO.setmode(GPIO.BCM)
    GPIO.setup(BUTTON_GPIO, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    
    GPIO.add_event_detect(BUTTON_GPIO, GPIO.BOTH, 
            callback=button_callback, bouncetime=50)
    
    signal.signal(signal.SIGINT, signal_handler)
    signal.pause()

 

콜백 함수에서 현재 GPIO의 상태(LOW 또는 HIGH)가 무엇인지 확인해야 합니다. 이 시점에서는 상승 또는 하강 인터럽트에서 온 것인지 알 수 없기 때문입니다. 당신은 그것이 그들 중 하나라는 것을 알고 있습니다.

여기에 또 다른 변경 사항이 있습니다. 바운스 시간을 100밀리초 대신 50밀리초로 설정했습니다. 이렇게 하면 버튼을 빠르게 눌렀다가 놓을 때 일부 트리거가 누락되는 것을 방지할 수 있습니다. 바운스 타임을 설정하는 것은 매우 까다로울 수 있습니다. 트리거를 놓치지 않기 위해 너무 많이 넣는 것은 아니지만 원하지 않는 트리거를 얻지 않도록 너무 적게 넣는 것도 원하지 않습니다. 프로그램에서 몇 가지 테스트를 수행해야 합니다. 또한 하드웨어를 변경하여 바운스가 훨씬 적은 더 나은 품질의 버튼을 얻을 수 있습니다. 

 

Raspberry Pi GPIO 인터럽트가 있는 몇 가지 애플리케이션

 

다음은 Raspberry Pi에서 GPIO 인터럽트를 사용하는 다양한 방법을 보여주는 3가지 코드 예제입니다. 먼저 회로에 LED를 추가해 보겠습니다. 

 

 

짧은 다리를 접지에 연결하고 그 사이에 저항(여기서는 330옴)을 추가합니다. 그런 다음 LED의 더 긴 다리를 GPIO 20에 연결합니다.

 

RPi.GPIO 인터럽트 애플리케이션 예제 #1

 

목표: 버튼을 눌렀을 때 LED의 전원을 켜고 버튼에서 손을 떼면 LED의 전원을 끕니다(버튼을 매우 빠르게 누르면 바운스 시간을 조정해야 할 수 있음). 

 

#!/usr/bin/env python3          
                                
import signal                   
import sys
import RPi.GPIO as GPIO

BUTTON_GPIO = 16
LED_GPIO = 20

def signal_handler(sig, frame):
    GPIO.cleanup()
    sys.exit(0)

def button_callback(channel):
    if GPIO.input(BUTTON_GPIO):
        GPIO.output(LED_GPIO, GPIO.LOW)
    else:
        GPIO.output(LED_GPIO, GPIO.HIGH) 

if __name__ == '__main__':
    GPIO.setmode(GPIO.BCM)
    
    GPIO.setup(BUTTON_GPIO, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.setup(LED_GPIO, GPIO.OUT)   

    GPIO.add_event_detect(BUTTON_GPIO, GPIO.BOTH, 
            callback=button_callback, bouncetime=50)
    
    signal.signal(signal.SIGINT, signal_handler)
    signal.pause()

 

상승 및 하강 인터럽트를 모두 얻기 위해 GPIO.BOTH를 사용합니다. 에서버튼_콜백 () 기능을 사용하여 현재 버튼의 상태(즉, 인터럽트가 트리거된 직후)를 확인하고 그에 따라 LED의 전원을 켜고 끕니다.

 

참고: 폴링 방법으로 이 작업을 수행하는 경우에도 작동하지만 정확하거나 반응적이지 않습니다. RPi.GPIO

 

인터럽트 애플리케이션 예제 #2

 

목표: 버튼을 누를 때마다 LED의 상태를 변경합니다. 

 

#!/usr/bin/env python3          
                                
import signal                   
import sys
import RPi.GPIO as GPIO

BUTTON_GPIO = 16
LED_GPIO = 20

last_LED_state = 0

def signal_handler(sig, frame):
    GPIO.cleanup()
    sys.exit(0)

def button_pressed_callback(channel):
    global last_LED_state
    GPIO.output(LED_GPIO, not last_LED_state)
    last_LED_state = not last_LED_state

if __name__ == '__main__':
    GPIO.setmode(GPIO.BCM)
    
    GPIO.setup(BUTTON_GPIO, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.setup(LED_GPIO, GPIO.OUT)

    GPIO.add_event_detect(BUTTON_GPIO, GPIO.FALLING, 
            callback=button_pressed_callback, bouncetime=200)
    
    signal.signal(signal.SIGINT, signal_handler)
    signal.pause()

 

여기서는 사용자가 버튼을 누를 때만 신경을 쓰므로 인터럽트에 GPIO.FALLING을 사용합니다(풀업 저항이 있기 때문에 기본 버튼의 상태는 HIGH이고 눌렀을 때 LOW가 됨을 기억하십시오).

 

또한 전역 변수가 필요하므로 콜백 함수에서 사용하고 두 인터럽트 트리거 사이의 상태를 유지할 수 있습니다. 콜백 내에서 LED의 상태를 이전 상태와 반대로 변경하고 이 새 상태를 전역 변수에 저장합니다.

 

여기서 바운스 시간은 200밀리초입니다. 이 값을 약간 늘리면 원치 않는 트리거가 발생하지 않을 가능성이 더 커집니다. 버튼을 누르지 않고 누르기만 하면 되므로 괜찮습니다(그러나 "확인"은 실제로 응용 프로그램에서 원하는 항목에 따라 달라집니다. 당신이 결정). 

 

RPi.GPIO 인터럽트 애플리케이션 예제 #3

 

LED가 스스로 깜박입니다. 목표: 버튼에서 손을 떼면 깜박이는 LED 시작/중지. 

 

#!/usr/bin/env python3          
                                
import signal                   
import sys
import time
import RPi.GPIO as GPIO

BUTTON_GPIO = 16
LED_GPIO = 20

should_blink = False

def signal_handler(sig, frame):
    GPIO.cleanup()
    sys.exit(0)

def button_released_callback(channel):
    global should_blink
    should_blink = not should_blink  

if __name__ == '__main__':
    GPIO.setmode(GPIO.BCM)
    
    GPIO.setup(BUTTON_GPIO, GPIO.IN, pull_up_down=GPIO.PUD_UP)
    GPIO.setup(LED_GPIO, GPIO.OUT)   

    GPIO.add_event_detect(BUTTON_GPIO, GPIO.RISING, 
            callback=button_released_callback, bouncetime=200)
    
    signal.signal(signal.SIGINT, signal_handler)
    
    while True:
        if should_blink:
            GPIO.output(LED_GPIO, GPIO.HIGH) 
        time.sleep(0.5)
        if should_blink:
            GPIO.output(LED_GPIO, GPIO.LOW)  
        time.sleep(0.5)

 

여기서는 사용자가 버튼을 놓을 때만 신경을 쓰므로 GPIO.RISING 옵션을 사용합니다. 컴퓨터 마우스 상호 작용과 유사합니다. 마우스로 버튼을 클릭하면 손을 뗀 후에만 동작이 검증됩니다.

 

보시다시피 없습니다. 신호. 일시 중지 ()더 이상. 메인 함수를 사용하여 LED를 깜박이게 할 것입니다. LED를 깜박여야 하는지 여부를 알기 위해 인터럽트 콜백 내에서 수정할 수 있는 전역 변수를 사용합니다.

 

Raspberry Pi에서 GPIO 인터럽트로 더 나아가기

 

지금쯤이면 Raspberry Pi GPIO에서 인터럽트를 사용해야 하는 경우와 RPi.GPIO Python 모듈에서 실제로 인터럽트를 사용하는 방법에 대해 잘 이해하셨을 것입니다. 더 연습하고 싶다면 3가지 코드 예제를 직접 다시 실행해 보세요. 그런 다음 몇 가지 새로운 규칙을 상상하거나 더 많은 하드웨어(여러 버튼, LED 및 기타 구성 요소)를 추가하고 코드로 구현하십시오. "폴링 대 인터럽트"라는 질문은 때때로 매우 분명합니다. 때때로종종 상당히 까다롭습니다. 가다 보면 알게 될 것입니다. 연습을 하면 할수록 쉬워집니다! 

 

 

참고

Raspberry Pi GPIO Interrupts Tutorial  

 

 

 

 

 

 

 

 

반응형