지난 글에 이어 푸시버튼으로 카운터를 구현하는 법에 대해 작성해볼 예정이다.
지난 글에서는 B 구간에서 한번만 카운트가 올라가는 방식을 구현했다.
이번에는 A인 순간이나 C인 순간, 즉 엣지에서 카운트가 올라가도록 구현해보려고 한다.
단순히 생각하면 A인 순간에 카운트를 이렇게 올릴 수 있다.
if (digitalRead(BUTTON) == LOW){
if (digitalRead(BUTTON) == HIGH){
count++;
}
}
결론부터 말하면 이런식으로는 구현할 수 없다.
물론 실제로 카운트가 몇번 될 수는 있겠지만 이 방식의 문제점은 우선
전체 loop 함수는 계속 순서대로 진행 중이기 때문이다. 버튼을 누르기 시작할 때 정확히 LOW인지 검사하는 로직을 통과하고, 버튼이 다 눌렸을 때 HIGH인지 검사하는 로직을 통과한다는 보장이 없다.
따라서 만약 다른 로직을 실행 중이더라도 엣지가 발생하면 바로 관련 처리를 해야 하는데, 이럴 때 쓰는 개념이 인터럽트이다.
인터럽트란, CPU가 로직을 수행 중이더라도 해당 명령어를 중단하고 특정 로직을 수행한 후 원래 로직으로 복귀하도록 하는 것이다.
아두이노에서 인터럽트는 기본 라이브러리 함수를 사용해 쉽게 등록할 수 있다.
우선 첫번째 파라미터 값은 핀번호가 아니다. 따라서 핀번호를 인터럽트 번호로 바꿔주는 digitalPinToInterrupt()
함수를 사용해야 한다. 또한, 인터럽트를 걸 수 있는 핀은 보드에 따라 제한되어 있다. 아두이노 우노 보드의 경우 2번 핀과 3번 핀을 사용할 수 있다.
두번째 파라미터(ISR
)는 함수이다. 인터럽트가 걸릴 때 동작할 함수를 작성해주면 된다. 단, 인터럽트는 기본적으로 우선순위가 높은 일을 짧게 처리하고 다시 원래 로직으로 돌아오는 목적으로 사용해야 한다. 따라서 ISR 함수 내부에서는 delay()나 millis() 등이 제대로 작동하지 않으며, 시리얼 입력 수신이 제대로 되지 않을 수 있다. 따라서 관련 플래그를 세팅한 후, 복잡한 로직의 경우 loop에서 작동하도록 짜는 것이 좋다. 또한 내부에서 변경하는 변수는 전부 volatile
로 선언하여 컴파일러의 최적화를 막아두어야 한다.
세번째 파라미터는 인터럽트가 발생할 순간이다.
기본적으로 LOW를 사용할 수 있고, HIGH는 특정 보드에서만 사용이 가능하다.
RISING은 0에서 1로 바뀌는 엣지(C)를 의미하며, FALLING은 1에서 0으로 바뀌는 엣지(A)를 의미한다. CHANGE는 RISING 혹은 FALLING 이벤트를 포함하여 값이 바뀔 때 인터럽트가 발생한다.
volatile int count = 0;
void setup(){
pinMode(BUTTON, INPUT_PULLUP);
Serial.begin(9600);
attachInterrupt(digitalPinToInterrupt(2), count, FALLING); // 버튼을 누르는 순간 인터럽트 발생
}
void count(){
count++;
Serial.println(count);
}
void loop(){
}
이렇게만 작동하면 버튼을 누르는 순간 count()
가 작동되며 카운트를 올린다.
그러나 지금은 시리얼 통신이 인터럽트 함수(ISR, 여기서는 count)에 들어있기 때문에, 이 부분을 최대한 경량화 시켜주려 한다.
#define BUTTON 2
#define LED 13
volatile bool flag = false;
int count = 0;
void setup(){
pinMode(LED, OUTPUT);
pinMode(BUTTON, INPUT_PULLUP);
Serial.begin(9600);
attachInterrupt(digitalPinToInterrupt(BUTTON), triggerCount, FALLING); // 버튼을 누르는 순간 인터럽트 발생
}
void triggerCount(){
flag = true;
}
void loop(){
if (flag){
flag = false;
count++;
Serial.println(count);
digitalWrite(LED, HIGH);
} else {
digitalWrite(LED, LOW);
}
}
이런식으로 인터럽트 함수에서는 플래그만 표시하고, 관련 동작은 루프함수에 넣어서 인터럽트 함수를 가볍게 할 수 있다.