본문 바로가기
코딩/아두이노

아두이노 입출력 인터럽트에 대해서 알아보자 attachInterrupt()

by DIYver 2020. 12. 23.

 

 

 

이번 포스트에서는 임베디드 시스템에서 제일 중요한 부분 중 하나인 인터럽트에 대해서 알아보도록 한다.

 

인터럽트란 마이크로컨트롤러프로세서(MCU) 에서 프로그램이 실행 중에 있을때, 예외상황이 발생하면 우선적으로 처리하는 것을 말한다.

 

아두이노를 가지고 설명하자면, 아두이노는 흔히 LOOP() 함수에서 코드가 반복해서 돌아가게 된다.

그런데 이상하게 구동이 되는 경우 도중에 코드를 멈춰야 한다. 

이걸 LOOP() 함수 안에서 구현하려면 코드가 매우 난잡해지고, 즉시 멈추는게 불가능 하다.

멈추더라도 이전 작업이 다 끝나야 멈출 수 있다는 것이다.

 

코드가 난잡해지면 실수가 발생할 수 있고, 오류가 생길 가능성이 커진다.

그리고 즉시 멈출 수 없어서 그 짧은 시간동안 다른 문제를 야기할 수 있다.

따라서 이런 문제들을 깔끔하게 해결할 수 있어야 하는데,

그걸 해결해 주는 녀석이 인터럽트인 것이다.

정확하게 말하면 입출력 인터럽트 입다.

영어로 하면 I/O Interrupt 이다.

 

입출력 인터럽트는 코드가 실행 중이더라도 우선적으로 작동하게 된다.

따라서 이러한 경우, 사용자가 원하는대로 멈추고 싶을때 즉시 멈출 수 있다.

 

인터럽트 개념

인터럽트 실행이 끝나면 다시 원래의 main 코드가 실행되는 구조라는 것을 이해해야 한다.

 

 

 

 

인터럽트는 입출력 인터럽트와 타이머 인터럽트로 두개가 있다.

타이머 인터럽트 역시 중요하므로 다음에 다루도록 한다.

 

 

 

아두이노는 프로미니, 나노, 우노, 메가 이렇게 존재하는데,

모든 아두이노가 인터럽트를 동일하게 지원하지는 않는다.

아두이노 칩셋마다 성능차이가 존재하는데, 인터럽트도 차이가 존재한다.

 

아래 표에서 아두이노마다 지원하는 인터럽트 개수를 알아보자.

<아두이노 공식 홈페이지 참조>

 

우리가 흔히 쓰는 우노, 나노, 미니는 2개만 지원한다.

2번 핀과 3번 핀만 입출력 인터럽트를 지원한다.

 

그 이상의 보드들은 더 많은 인터럽트를 지원하므로 좋은 보드를 사용한다면 인터럽트 개수에 큰 걱정을 하지 않아도 된다.

 

 

 

 

 

아두이노 공식 홈페이지에서 말하는 것을 소개해보록 하겠다.

 

아두이노에서 우리가 흔히 쓰는 delay() 함수는 사실 인터럽트에서 담당하는 부분이라고 한다.

그리고 웃긴건 코드 내에서 시간 타이머를 시작하는 millis() 는 인터럽트와 관련이 있다.

이게 뭔 뜻이냐면, 인터럽트를 사용하게 되면 내부 타이머 millis() 는 그 동안 작동하지 않는다는 것이다.

 

잘 생각해보면, 내부 타이머가 매우 중요하게 쓰인다면 인터럽트를 사용하면 안된다는 것이다.

쓰더라도 인터럽트를 끝내고 다시 내부 타이머를 쓰면 안된다는 것이다.

 

 

 

 

 

인터럽트가 항상 긴급 정지에만 쓰이는 것은 아니다.

정확한 센싱에도 인터럽트를 사용한다.

예를 들면, 모터(로터리) 엔코더에서 사용한다.

신호의 변화 펄스를 감지하여 정확하고 어느 순간에도 놓치지 않고 감지하는 것이다.

다른 구동 때문에 센싱이 잘못되면 안되기에 이런 경우 인터럽트를 이용해야 한다.

 

 

 

 

 

 

const byte ledPin = 13;
const byte interruptPin = 2;
volatile byte state = LOW;

void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), blink, CHANGE);
}

void loop() {
  digitalWrite(ledPin, state);
}

void blink() {
  state = !state;
}

 

예제 코드를 살펴보면, 인터럽트 사용법은 위와 같다.

 

인터럽트를 사용하겠다고 하는 함수는 attachInterrupt( interrupt_number, function name, mode) 이다.

주의 해야 할 것이 있다.

 

1. interrupt_number는 일반적인 pin 번호가 아니다.

 인터럽트 번호를 입력해야 한다.

 예제에서는 digitalPinToInterrupt( ) 함수를 이용해서 쉽게 인터럽트 핀을 사용할 수 있게 해준다.

 예를 들면, 아두이노 우노에서는 위의 표에서 나타낸 것처럼 디지털 2번과 3번핀이 인터럽트 핀이다.

 여기서 디지털 2번핀을 사용하려면 digitalPinToInterrupt(2) 라고 쓰면 된다.

 인터럽트 번호를 입력했다면 attachInterrupt(0 , blink, CHANGE) 라고 입력하면 된다.

 

2. function 은 함수이름을 쓰면 된다. 괄호 없이 말이다.

 사용자 정의 함수로 blink() 를 선언해 주었다.

 인터럽스 사용 함수에서는 이 사용자 정의 함수에서 괄호 없이 blink 만 쓰면 되는 것이다.

 

3. mode 는 펄스 신호를 이해해야 한다. 총 5가지 mode 가 있다.

 - LOW

  • 신호가 LOW가 된 경우 인터럽트가 발생한다.

 - HIGH

  • 신호가 HIGH가 된 경우 인터럽트가 발생한다.

 

 - RISING

  • 신호가 LOW 에서 HIGH 가 되는 경우, 신호의 변화를 감지하여 인터럽트가 발생한다.

 - FALLING

  • 신호가 HIGH 에서 LOW 가 되는 경우, 신호의 변화를 감지하여 인터럽트가 발생한다.

 - CHANGE

  • RISING 또는 FALLING edge 둘 중 하나라도 감지 하면 인터럽트가 발생한다.

 

 

 

이러한 사용 방법을 숙지하고 인터럽트를 사용하여야 잘못된 동작을 예방할 수 있다.

인터럽트는 아두이노 뿐만이 아니라 거의 모든 임베디드 보드에서 중요하게 사용한다.

 

사용하는 방법은 다르더라도 원리는 똑같으므로 이 원리를 이해하는 것이 중요하다.

 

 

 

 

 

 

 

 

다시 코드를 살펴보자

const byte ledPin = 13;
const byte interruptPin = 2;
volatile byte state = LOW;

void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), blink, CHANGE);
}

void loop() {
  digitalWrite(ledPin, state);
}

void blink() {
  state = !state;
}

setup() 에서 LED 출력 핀과, 인터럽트 신호를 입력받을 핀을 설정해주었다.

그리고 해당 핀을 인터럽트에 배정해 주었다.

 

loop() 코드 내용을 해석해보자면

버튼이 누르는 순간 또는 떼는 순간마다 state 신호가 반전된다.

그 state 값에 따라서 led가 켜지고 꺼지는 동작을 보여준다.

 

초기 state 가 LOW 이므로, 버튼을 누르면 LED가 켜지고 버튼에서 손을 떼면 LED가 꺼질 것이다.

 

 

 

작동 영상은 아래 영상에서 확인하면 된다.

youtu.be/6hK5Y4h_is0

 

 

위에서 했던 코드 해석과 살짝 다른 결과를 보여준다.

이 이유는 버튼의 경우 센서 값이 튈 수 있는데,

이 신호를 못 잡았을 경우 값이 살짝 꼬이게 된다.

 

인터럽트 감지를 CHANGE 가 아니라, FALLING 또는 RISING edge 검출로 사용했다면 버튼 신호 노이즈를 잡아줄 수 있었을 것이다.

아니면 Debounce 알고리즘을 추가해야 한다.

 

 

 

 

 

댓글