외부 인터럽트(External Interrupt)는 앞서 확인했던 인터럽트 벡터 테이블에서 RESET을 제외한 가장 우선순위가 높은 인터럽트로, 하드웨어에 가해지는 전압의 변화에 따라 발생하게 된다. 외부 인터럽트가 있으니 내부 인터럽트도 있지 않을까 하고 생각할 수 있지만 사실 내부 인터럽트라는 개념은 없으며 단지 MCU의 I/O 포트를 통해 외부에서 인터럽트를 수신하기 때문에 외부 인터럽트라고 한다. 


ATmega128에서는 총 8개의 정해진 핀에서만 사용할 수 있으며 간단한 설정을 통해 바로 사용해볼 수 있으므로 프로테우스 툴을 사용하여 내부적으로 어떻게 동작하는지 확인해보자.



1. 레지스터 설정


외부 인터럽트를 사용하기 위해서는 먼저 각 핀에서의 인터럽트 발생을 허용해야 한다. 이러한 역할을 하는 레지스터가 바로 EIMSK (External Interrupt Mask Register) 이며 각 비트별로 단순하게 어떤 핀을 외부 인터럽트 핀으로 사용할 것인가를 결정한다. 사용하고자 하는 인터럽트가 있으면 해당 비트에 1을 세트시키면 된다.



다음으로 EICRA (External Interrupt Control Register A) 는 이름에서 알수 있듯이 실제 인터럽트가 발생하는 시점을 결정하는 레지스터로 INT0 ~ INT3 까지의 외부 인터럽트 발생 시점을 결정한다. 각 비트들을 살펴보면 인터럽트 별로 두 비트씩 할당되어 있음을 알 수 있으며 나머지 인터럽트는 EICRB 레지스터에 같은 규칙으로 적용되어 있다.



비트 설정에 따라 인터럽트가 발생하는 시점은 아래 표와 같으며, 쓰지 않는 Reserved를 제외하면 총 3가지의 신호를 통해 인터럽트를 제어할 수 있다. 예를 들어 ISC01=1, ISC00=1 로 설정하면 포트 0번을 0V에서 5V로 변할때, 즉 상승 엣지(Rising Edge)에서 인터럽트가 발생하게 된다.  



다음은 인터럽트 요청이 발생했을때 해당 비트가 1로 세트되고 인터럽터 종료시 0으로 클리어되는 EIFR (External Interrupt Flag Register) 레지스터이다. 사실 잘 쓰이진 않는 레지스터이며 특이하게도 1이 세트되어 있는 상태에서 다시 1을 써주면 0으로 클리어되는 방식으로 동작한다. 마치 내부적으로 ~(input)  과 같이 동작하는 것 처럼 보인다. 그리고 클리어 되었다고 해도 단순히 해당 플래그가 0으로 바뀔뿐 인터럽트는 이 레지스터와 별개로 계속해서 발생할 수 있다.   



활용 예를 간단하게 보여주고 싶은데 찾아봐도 마땅한 것이 안보인다. 정말 특수한 목적이 있지 않은 이상 위 레지스터를 건드릴 이유는 딱히 없을듯 싶다.


마지막으로 모든 인터럽트를 허용하는 레지스터인 SREG (Status Register) 이다. SREG 레지스터에서 전역적으로 인터럽트 발생을 허용하는 비트는 7번 비트이며 나머지 비트들은 산술 연산이나 논리 연산에 사용되는 비트이므로 이 레지스터에 접근할 때 다른 비트들을 건들지 않도록 주의해야한다. 예를 들어 단순히 대입 연산을 해버리면 다른 비트의 값들을 잃어버릴 수 있으므로 SREG |= 0x80 같은 방식으로 7번 비트만 세트시키거나 컴파일러에서 지원하는 함수인 sei 함수를 사용하면 된다. 



정리하면 각각의 핀에 따라 개별적으로 인터럽트를 허용하고, 인터럽트의 발생 시점을 설정한 후 모든 인터럽트의 발생을 허용하면 그 시점 부터 인터럽트를 발생시킬 수 있게 된다.



2. 예제 코드


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <io.h>
 
volatile int status = 0;
volatile char temp = 0x01;
 
interrupt [EXT_INT0] void ext_int0_isr(void)
{
    status = 1;
}
interrupt [EXT_INT1] void ext_int1_isr(void)
{
    status = 2;
}
 
void main(void)
{
    EIMSK = 0x03;
    EICRA = 0x0A;
    DDRB = 0xFF;
    PORTB = 0x01;
    #asm("sei")
 
while (1)
      { 
        if(status == 1)
        {
            temp = temp << 1;
            if( temp == 0x00)
                temp = 0x01;
            PORTB = temp;
            status = 0;
        }
        else if(status == 2)
        {
            temp = temp >> 1
            if( temp == 0x00)
                temp = 0x80;       
            PORTB = temp;
            status = 0;
        }
      }
}
cs


위 코드는 버튼을 누를 때 마다 불이 켜진 LED 위치를 좌우로 바뀌는 간단한 코드이다. INT0과 INT1을 하강 엣지 신호로 처리하고 있으며, 컴파일러가 자동으로 최적화 시키는 것을 방지하기 위해 2개의 변수를 volatile로 선언하였다.또한 인터럽트 수행시 걸리는 시간을 최소화하기 위해 내부적으로 동작하는 코드를 main에 두고 플래그(flag) 변수만 사용하여 인터럽트의 발생 유무를 확인할 수 있게 했다. 이러한 작업은 단순해 보여도 코드 최적화 부분에서 매우 중요하기 때문에 되도록이면 인터럽트 루틴에 걸리는 시간을 최소화 시키는 것이 좋다.



3. 시뮬레이션



간략히 외부 인터럽트의 개념에 대해 설명해 보았다. 다음 포스팅에서는 프로테우스의 디버깅(Debugging) 기능을 활용하여 외부 인터럽트가 내부적으로 어떻게 동작하는지 확인해보자.


'Electronic > AVR' 카테고리의 다른 글

소스 레벨 디버깅  (0) 2017.06.14
인터럽트 이해하기  (0) 2017.06.06
프로테우스 사용하기  (1) 2017.06.02