[아두이노] [아두이노 강좌] 22. Interrupt(인터럽트) (2) - 스위치로 LED 켜고 끄기 예제
자, 지난 시간에 인터럽트란 무엇인가에 대해서 알아봤으니, 이번 시간에는 인터럽트는 어디에 사용되는가에 대해 알아보자.
강좌 제목에 나와있다시피 스위치로 LED를 켜고 끄는 예제를 만들어 볼 것이다. 스위치를 한번 누르면 LED가 켜지고, 한번 더 누르면LED가 꺼지는 동작을 만들어 보자.
우선 브레드 보드에 스위치와 LED를 아래 그럼과 같이 연결한다.

스위치의 검정색 선과 LED의 검정색 선은 아두이노 보드의 GND로 연결하고, 스위치의 주황색 선은 2번에, LED의 노란색 선은 13번에 꽂는다. 스위치 핀이야 어디에 꽂아도 상관없지만 LED를 다른 핀에 꽂을 경우 반드시 LED 동작 전압을 확인해서 저항을 사용하도록 한다. 안 그러면 LED가 터져요. 뽁!
그리고 다음 소스를 입력한다.
interruptTest01.ino |
int ledPin = 13; int swPin = 2; boolean bLedOn = false;
void setup() { pinMode(ledPin, OUTPUT); pinMode(swPin, INPUT_PULLUP);
digitalWrite(ledPin, LOW); }
void loop() { if(digitalRead(swPin) == LOW) { if(bLedOn == false) { digitalWrite(ledPin, HIGH); bLedOn = true; } else { digitalWrite(ledPin, LOW); bLedOn = false; } } } |
간단하게 소스 설명.
LED를 켜고 끄기 위한 출력 핀과 스위치 상태를 읽기 위한 입력 핀을 선언한 후 모드를 초기화 한다. 이 때, 스위치는 따로 풀업 저항을 사용하지 않았으므로 내부 풀업을 사용하도록 INPUT_PULLUP 모드로 설정한다는 것에 유의. 그리고 LED 핀의 초기 값을 LOW로 줘서 LED가 꺼진 상태로 동작을 시작하도록 한다.
“bLedOn” 변수는 LED의 현재 상태를 저장하기 위한 변수이며, bLedOn 값에 따라 LED를 켤 것인가, 끌 것인가를 결정한다.
그리고 loop() 함수에서 스위치 핀의 상태를 읽어서 스위치가 눌린 경우, 즉 swPin의 입력 값이 LOW인 경우에 bLedOn 값에 따라 LED를 켜거나 끈다. bLedOn이 false이면 LED를 켜고 bLedOn 값을 true로 변경, 아니라면 LED를 끄고 bLedOn 값을 false로 변경.
어렵지 않아요.
자, 그럼 업로드 하기 전에 손 한번 들어볼까요~? 위 소스가 제대로 동작할 것이라고 생각하는 사람 손??
눈치 빠른 사람은 손 들어보라는 질문 자체에서 이미 동작이 안될 것이라는 것을 예상했을 것이고. 이미 왜 안 되는지 이유를 알고 있는 사람도 있을 것이여. 허허.
동작 안 된다.
스위치를 누르면 LED가 약간 어둡게 켜지거나 빠르게 깜빡이다가 스위치를 떼면 완전히 켜지거나 완전히 꺼진다. 왜요? 왜 때문이죠?
스위치가 눌려졌을 때의 입력 신호는 다음과 같다.

스위치가 눌리면 입력 값은 LOW로 떨어지고, 스위치에서 손을 뗄 때까지 LOW 값을 유지한다. loop()문에서 digitalRead() 함수가 반복되어 호출되므로 스위치가 눌려져 있는 동안은 LED가 계속해서 켜지고 꺼지는 동작을 반복하게 된다. 그리고 스위치에서 손을 떼면 마지막으로 변경된 LED의 상태가 지속되는 것이다.
이거 의외로 초보자들이 자주 하는 실수임.
우리가 원하는 동작은 스위치가 눌렸을 때 한번만 LED의 상태가 바뀌는 건데, 그럼 어떻게 해야 하나요?↗
LED의 이전(previous) 상태를 알기 위해 변수를 하나 사용한 것처럼 스위치의 이전 상태를 알기 위한 변수를 하나 더 추가하면 된다. 다음 소스를 볼까?
interruptTest02.ino |
ledPin = 13; int swPin = 2; boolean bLedOn = false; boolean bSwOn = false;
void setup() { pinMode(ledPin, OUTPUT); pinMode(swPin, INPUT_PULLUP);
digitalWrite(ledPin, LOW); }
void loop() { if(digitalRead(swPin) == LOW) { if(bSwOn == false) { bSwOn = true; if(bLedOn == false) { digitalWrite(ledPin, HIGH); bLedOn = true; } else { digitalWrite(ledPin, LOW); bLedOn = false; } } } else { bSwOn = false; } } |
빨간 색으로 굵게 표시된 부분이 추가된 부분이다.
“bSwOn”이라는 변수를 사용해서 스위치의 이전 상태를 확인하고 있다. 스위치가 눌렸을 경우(swPin이 LOW인 경우) 스위치의 이전 상태가 눌림 상태가 아닐 때(bSwOn 값이 false일 때) LED의 상태를 변경한다. 이 때 스위치의 상태를 눌림 상태(true)로 저장.
스위치의 상태가 눌림 상태로 바뀌었으므로 다시 loop() 함수가 돌아와 digitalRead(swPin) 함수로 읽어온 값이 여전히 LOW여도 LED의 상태를 변경하지 않는다. 즉, 스위치가 눌린 후 처음 조건문에 들어왔을 때에만 LED 상태를 변경하는 것. 그리고 swPin 상태가 HIGH가 됐을 때, 즉 스위치가 떨어졌을 때 다시 bSwOn 변수에 false 값을 저장해서 스위치가 눌림 상태가 아니라는 것을 기억한다.
이제 업로드 한 후 스위치를 눌러보자. 제대로 동작 해야 된대이.
자, 오늘 실습 끝!
은 농담이고, 이거랑 인터럽트랑 무슨 상관??
스위치를 누를 때마다 LED를 켜고 끄는 간단한 동작을 만드는데, 소스가 좀 복잡해 보인다. 게다가 swPin 상태를 끊임없이, 계속, 쭈욱 확인해야 한다.
loop() 함수의 마지막 부분에 “delay(1000);”을 추가한 후 동작해보자.
동작이 잘 안 된다. 반응이 느릴뿐더러 스위치를 짧게 누르면 아예 동작을 안 하기도 한다. 1초간 동작을 멈추는 동안 스위치의 상태를 읽어올 수 없어서 나타나는 현상이다. 만일 delay(1000)이 꼭 필요한 경우라면 어떻게 해야 하나?
이러한 상황을 위해 인터럽트가 있다. 똑같은 동작을 인터럽트를 사용한 소스로 바꿔보자. 아래 소스에서 swPin, 즉 스위치의 핀은 반드시 아두이노 보드의 2번 핀에 연결되어 있어야 한다.
interruptTest03.ino |
int ledPin = 13; int swPin = 2; boolean bLedOn = false;
void setup() { pinMode(ledPin, OUTPUT); pinMode(swPin, INPUT_PULLUP); attachInterrupt(0, swInterrupt, FALLING);
digitalWrite(ledPin, LOW); }
void loop() { delay(1000); }
void swInterrupt() { if(bLedOn == false) { digitalWrite(ledPin, HIGH); bLedOn = true; } else { digitalWrite(ledPin, LOW); bLedOn = false; } } |
오, 새로운 함수가 나왔어.
우선 동작부터 시켜볼까? 아까랑 똑같이 동작하는 걸 볼 수 있다. 어디에도 swPin의 상태를 확인하는 함수가 없는데도, 심지어 loop() 함수에 delay(1000) 구문이 있는데도 불구하고 버벅임이나 시간 지연 없이 자연스럽게 동작하는 것을 확인할 수 있을 것이다.
attachInterrupt() 함수는 swPin의 상태를 확인해서 핀의 상태가 HIGH에서 LOW로 바뀔 때 swInterrupt() 함수를 자동 호출하도록 설정하는 함수이다.
그래서 스위치가 눌려 swPin이 LOW로 변하면 loop() 문에 무슨 내용이 있든, 스위치가 눌린 후 계속 눌려져 있든, 금방 떨어지든 상관 없이, 딱 한 번만 swInterrupt() 함수가 호출되는 것이다.
이제 인터럽트에 대해 조금은 이해가 되는가?
혹시 스위치를 눌렀을 때 swInterrupt() 함수가 여러 번 호출되는 듯한 느낌을 받는다면 아마 그건 채터링(Chattering)이라는 현상 때문일 것이다.
attachInterrupt() 함수에 대한 자세한 내용과 채터링에 관련된 내용은 다음 강좌에서 살펴보도록 하겠다. 오늘은 여기까지. 안녕~!