라즈베리 파이 피코 보드에서 IRQ와 thread 사용하기

라즈베리 파이 피코 보드에서 IRQ와 thread 사용하기

라즈베리 파이 피코 보드의 헤더 핀으로 입력신호가 들어오는 경우 이를 이용하여 특정한 동작을 시작하도록 하는 방법으로는 IRQ (Interrupt Request) 와 thread를 이용할 수 있다.

이를 실험하기 위해 다음과 같은 회로를 구성하고, IRQ와 thread를 이용하여 어느 핀에 연결된 스위치를 통해 입력신호가 들어오는지를 보여주는 Code를 만들어 보았다.

이 회로는 GP12와 GP13번을 각각의 스위치에 연결하고 GP15에 LED를 연결하여 ㅅ위치가 눌러지면 LED가 잠시 켜지고, 화면에 입력신호가 들어온 핀을 표기하도록 구성하였다.

1. IRQ 이용하기

IRQ는 다음 예로 만든 코드에서 볼 수 있듯이 특정한 핀의 입력신호를 계속 모니터링하다가, 해당 핀으로 입력신호가 들어오면, 미리 정해진 동작을 하게 되는데, 사용법은 다음과 같다.

1) 먼저 입력신호가 들어오는 것을 모니터링할 헤어핀과, 입력값을 저장할 변수명을 선언해 준다.

만일 12번 핀과, 13번 핀으로 들어오는 핀 2개를 모니터링하는 경우, 다음과 같은 방식으로 선언해 준다.

변수명은 자신이 원하는 대로 정하면 되고, 만일 보다 많은 핀을 모니터링하는 경우에는 필요한 만큼 각각의 핀을 선언해 주면 된다.

lbutton = machine.Pin(12, machine.Pin.IN, machine.Pin.PULL_DOWN)

rbutton = machine.Pin(13, machine.Pin.IN, machine.Pin.PULL_DOWN)

2) 해당 핀으로 입력 신호가 들어왔을 때 동작할 handler 부분을 하나의 모듈로 선언해 준다.

만일 위에서 선언한 GP12 또는 GP13으로 입력 신호가 들어왔을 때 전역변수 fastest에 pin 값을 할당하는

동작을 하도록 선언하면 다음과 같다. 참고로, 이 모듈 내에서 사용하는 변숫값을 모듈 밖에서도 사용하려면 global 이란 키워드를 이용하여 모듈 밖과 내부 양쪽에서 모두 전역변수로 선언해 주어야 한다. 만일 모듈 내에서 해당 변수의 값을 변경하지는 않고, 단순 참조만 하는 경우에는 모듈 내에서 전역변수로 선언할 필요까지는 없다.

def button_handler(pin):

lbutton.irq(handler=None)

rbutton.irq(handler=None)

global fastest

fastest = pin

3) 먼저 입력신호가 들어오는 것을 모니터링할 헤어핀과, 입력값을 저장할 변수명을 선언해 준다.

해다 핀으로 입력신호가 들어오는 것을 모니터링하도록 IRQ 명령을 실행해 준다.

lbutton.irq(trigger=machine.Pin.IRQ_RISING, handler=button_handler)

rbutton.irq(trigger=machine.Pin.IRQ_RISING, handler=button_handler)

여기서 trigger=machine.Pin.IRQ_RISING 부분에 포함된 IRQ_RISING은 해당핀으로 들어오는 입력값이 low에서 high로 변경되는 경우(0–>1)에 해당 IRQ가 동작함을 의미하며, 만일 반대로 하려면 IRQ_FALLING을 사용하면 된다.

이렇게 2개의 입력핀을 모니터링하고, 특정 핀으로 입력 신호가 들어온 경우, 이것이 어느 핀을 통해서 입력된 것인지를 화면에 출력하도록 아래와 같은 Code를 만들었다. 특히, IRQ handler에서 fastest 변수를 통해 전달되는 값을 확인하기 위해 이 값도 출력하도록 다음 Code도 추가하였다.

print(fastest)

print(“Left side!” ) 또는 print(“Right side!”)

import machine
import time

led = machine.Pin(15, machine.Pin.OUT)
lbutton = machine.Pin(12, machine.Pin.IN, machine.Pin.PULL_DOWN)
rbutton = machine.Pin(13, machine.Pin.IN, machine.Pin.PULL_DOWN)
global fastest
fastest = None

def button_handler(pin):
     lbutton.irq(handler=None)
     rbutton.irq(handler=None)
     global fastest
     fastest = pin
     
led.value(1)
time.sleep(1)
led.value(0)

lbutton.irq(trigger=machine.Pin.IRQ_RISING, handler=button_handler)
rbutton.irq(trigger=machine.Pin.IRQ_RISING, handler=button_handler)
while True:
if fastest is lbutton:
print(fastest)
print("Left side!" )
         fastest = None
         
    elif fastest is rbutton:
print(fastest)
print("Right side!")
         fastest = None

이렇게 만들어진 Code를 실행하면 각각의 핀으로 입력신호가 들어올 때 다음과 같은 화면 출력이 이루어지는 것을 확인할 수 있었다.

GP12번

이 경우, IRQ는 전체 Code의 일부이므로 독립적이라기보다는 잠시 대기 상태로 전환되는 형태라고 볼 수 있다. 따라서 2개의 핀 중 어느 하나로 입력이 이루어지면 다시 추가로 다른 핀 쪽의 스위치를 눌러도(=입력을 주어도) 결과가 바뀌지는 않는다.

2. thread 이용하기

앞에서 라즈베리 파이 피코 보드는 아두이노가 한 번에 단순히 한 가지 일만 하는 것에 비해, 동시에 두 가지 일(thread)을 처리할 수 있음을 소개한 바 있다.

thread의 사용방법은

1) IRQ와 동일하게 입력신호가 들어오는 것을 모니터링할 헤어핀과, 입력값을 저장할 변수명을 선언해 준다.

만일 12번 핀과, 13번 핀으로 들어오는 핀 2개를 모니터링하는 경우, 다음과 같은 방식으로 선언해 준다.

button1 = machine.Pin(12, machine.Pin.IN, machine.Pin.PULL_DOWN)

button2 = machine.Pin(13, machine.Pin.IN, machine.Pin.PULL_DOWN)

2) 프로그램 앞부분에 thread Library를 사용한다는 것을 다음과 같이 선언해 준다.

import _thread

3) 그리고 thread 명령이 발생하는 경우 수행할 thread의 내용을 하나의 모듈로 정의해 준다.

만일 위에서 선언한 GP12 또는 GP13핀으로 입력이 들어오는 경우, 입력신호가 들어왔다는 것을 button_status라는 변수에, 그리고 어느 핀으로 입력이 들어왔는지는 whichpin 이란 전역 변수에 할당하도록 하려면 다음과 같은 형태가 된다. 이들 두 변수는 이 모듈 내에서 변숫값이 변경되므로 각기 전역변수로 선언해 주어야 한다.

def button_check_thread():

global button_status

global whichpin

whichpin=None

while True:

if button1.value() == 1:

button_status = True

whichpin=button1

elif button2.value() == 1:

button_status = True

whichpin=button2

4) 위의 thread의 업무 내용이 선언된 이후의 행에서, 이 thread를 실행하도록 다음과 같이 실행 명령을 넣어준다.

_thread.start_new_thread(button_check_thread, ())

이렇게 thread 명령을 넣으면 그때부터 피코 보드의 RP2040 칩은 2개의 thread 처리능력 중 한 개를 이 thread에 할당해서 독립적으로 처리하고, 나머지 thread는 이 실행명령 이후의 행들을 차례대로 계속해서 수행해 간다.

결과적으로 위에서 만든 회로에, 12번 핀과 13번 핀 중 어느 특정 핀 이 눌러지는 경우 LED가 들어오고, 어느 핀으로 입력이 들어오는지를 화면에 표시하도록 변경해 보았다. 또한 두 개의 스위치를 계속해서 누르면 누를 때마다 눌러진 스위치가 연결된 핀을 표시하도록 변경한 코드는 다음과 같다.

import machine
import time
import _thread

myled = machine.Pin(15, machine.Pin.OUT)
button1 = machine.Pin(12, machine.Pin.IN, machine.Pin.PULL_DOWN)
button2 = machine.Pin(13, machine.Pin.IN, machine.Pin.PULL_DOWN)

global button_status
button_status = False
global whichpin
whichpin=None

def button_check_thread():
     global button_status
     global whichpin
     whichpin=None
     while True:
if button1.value() == 1:
             button_status = True
             whichpin=button1
             
         elif button2.value() == 1:
             button_status = True
             whichpin=button2
             
_thread.start_new_thread(button_check_thread, ())
while True:
if button_status == True:
     myled.value(1)
     time.sleep(1)
print (whichpin)
     button_status = False
     myled.value(0) 

이 Code를 실행시키면 각 핀에 연결된 스위치를 반복해서 누를 때마다 아래처럼 어느 핀으로 입력이 들어왔는지를 계속해서 표시하게 된다. 이는 thread 처리는 IRQ와는 달리 독립적이기 때문에, 메인 프로그램에서 순환물을 돌면서 입력을 모니터링하는 작업이 반복되기 때문이다.

이 경우, IRQ는 전체 Code의 일부이므로 독립적이라기보다는 잠시 대기 상태로 전환되는 형태라고 볼 수 있다. 따라서 2개의 핀 중 어느 하나로 입력이 이루어지면 다시 추가로 다른 핀 쪽의 스위치를 눌러도(=입력을 주어도) 결과가 바뀌지는 않는다.