인터럽트 개요
개요
이전까지는 이벤트(프로그램이나 시스템 내에서 발생하는 특정한 상황이나 사용자의 액션)를 체크하기 위해 프로그램이 돌아가는 동안 이를 주기적으로 체크하는 방식을 사용했다. 예를 들어, 스위치를 눌러 왕복하고 있던 LED의 방향을 바꾸거나, FND의 출력 상태를 바꾸는 등의 작업이 해당된다. 이처럼 CPU가 주기적으로 이벤트를 확인하고 동작을 수행하는 것을 폴링(Polling)이라고 한다. 과연 이 방식이 효율적일까?
주기적으로 하드웨어 장치의 상태를 검사하며 변화가 있는지 확인하기 때문에, 프로그램의 흐름이 예측이 가능하며, 검사 시간과 간격을 프로그래머가 제어할 수 있다. 즉, 동기성을 갖고 있는 폴링 방식은 변화가 없을 때도 계속해서 상태를 검사하기 때문에 CPU 자원을 낭비하기 십상이다. 또한 폴링 주기 사이에 발생하는 이벤트에 대한 반응이 지연될 수 있으며, 여러 장치를 폴링 하는 경우 시스템의 전반적인 효율성이 저하될 수 있다.
이번 시간에는 이러한 폴링 방식과, 이를 해결해 주는 인터럽트 방식에 대해 알아보도록 하자.
본문
상태 감지
폴링 방식이든, 인터럽트 방식이든, 외부 장치의 상태를 감지하여 특정한 동작을 수행하기 때문에, 이를 감지하는 두 가지 방법을 알아야 한다. 먼저 레벨 감지(Level Detection)는 신호가 높은(HIGH) 상태인지, 낮은(LOW) 상태인지를 판단한다. 예를 들어, 풀업 저항을 사용하는 경우, 스위치가 눌리지 않았을 때 HIGH 신호를 감지하고, 눌렸을 땐 LOW 신호를 감지한다.
반면 에지 감지(Edge Detection)는 신호 레벨이 변화하는 순간을 감지하는 방법이다. 예를 들어, 풀업 저항에서, 스위치가 눌려 신호가 HIGH에서 LOW로 바뀌는 순간 하강 에지가 감지되며, 반대로 떼어질 때 상승 에지가 감지된다.
여기서 중요한 것은 스위치가 어떤 방식으로 연결되어 있냐는 것이다. 위의 예시는 J-KIT-128-1에서 사용하는 풀업 저항을 전제로 설명하였지만, 풀다운 저항을 쓴다면 결과가 반대가 된다.
인터럽트
인터럽트(Interrupt)는 시스템에서 발생하는 특정 이벤트에 대해 현재 실행 중인 프로세스를 중단하고, 해당 이벤트를 우선적으로 처리한 다음 원래 작업으로 복귀하는 메커니즘을 말한다.
그렇다면 인터럽트는 어떻게 실행될까? 프로그램이 실행 중일 때 인터럽트가 발생하면, 시스템은 인터럽트 서비스 루틴(ISR)을 호출한다. ATmega128은 각각의 인터럽트 유형에 대응하는 고유의 인터럽트 벡터(Interrupt Vector)가 있으며, 인터럽트 발생 시 인터럽트 벡터 테이블(Interrupt Vector Table)을 참조하여 적절한 인터럽트 서비스 루틴(ISR)으로 분기한다. 이 테이블은 프로그램 메모리 내에 위치하며, ISR 호출 시 CPU는 벡터 테이블로 이동해 상응하는 ISR의 주소를 찾고, 그 주소에 있는 명령어를 실행하여 인터럽트를 처리한다.
조금 더 자세히 설명하자면, 인터럽트가 발생하면 우선 현재 명령어를 완료한다. 그리고 PC에 있는 복귀 주소를 스택에 저장하고, 인터럽트 벡터를 확인하여 ISR 주소로 분기한다. 그 후 해당 주소에 있는 명령어를 처리하고, 원래 위치로 복귀한다.
또한 인터럽트를 허가하고, 특정 인터럽트 플래그를 설정할 수 있는 레지스터가 존재한다. 만약 프로그램이 실행되는 도중 INT1과 INT3 인터럽트를 활성화하려면, 먼저 SREG의 7번 비트를 세트 하여 전역 인터럽트를 활성화한다. 그런 다음, 인터럽트 마스크 레지스터의 해당 비트를 세트 하여 INT1과 INT3를 활성화해야 한다. 여러 인터럽트가 동시에 발생하면, CPU는 정의된 우선순위에 따라 높은 우선순위의 인터럽트부터 처리한다. 인터럽트 서비스 루틴을 처리한 후, CPU는 인터럽트 플래그 레지스터를 다시 확인하여 대기 중인 다른 인터럽트가 있는지 조사한다.
이때 외부 인터럽트를 제어하는 레지스터는 크게 4개가 있다. 인터럽트 트리거 방식을 설정하는 EICRA(External Interrupt Control Register A), EICRB, 인터럽트를 개별적으로 허가하는 데 사용하는 EIMSK(External Interrupt Mask Register), 인터럽트가 트리거 되었음을 표시하는 데 사용하는 EIFR(External Interrupt Flag Register)인데, 각각의 특징을 설명하면 다음과 같다.
- EICRA: INT3~INT0의 트리거 방식(레벨/에지 트리거) 설정
- EICRB: INT7~INT4의 트리거 방식(레벨/에지 트리거) 설정
- EIMSK: INT7~INT0의 인터럽터를 개별적으로 허가하는 데 사용. 1로 설정 시 허용, 0으로 설정 시 금지.
- EIFR: INT7~INT0의 인터럽트가 트리거 되었음을 표시하는 데 사용. 인터럽트 발생 시 1로 설정, ISR 실행 시 다시 0으로 초기화
트리거 방식 EICR 하나로 쓰지 않고 EICRA와 EIRCB로 나눈 이유는, 4개의 상태를 확인하려면 2bit * 4 = 8bit가 필요한데, 레지스터가 8bit이기 때문이다.
즉, 외부 인터럽트 레지스터의 제어 순서는 다음과 같다. EICR로 사용할 인터럽트 트리거 방식을 설정하고, EIMSK로 사용할 인터럽트의 종류를 설정한다. 그리고 SREG의 7번 비트를 제어하여 전체 인터럽트를 허가하고, 필요시 EIFR 레지스터를 통해 인터럽트 발생을 확인하거나 초기화한다.
이러한 인터럽트는 폴링 방식에 비해 CPU가 다른 작업을 수행하는 동안 외부 이벤트를 기다릴 수 있으므로 시스템 자원을 효율적으로 사용할 수 있다. 또한 이벤트가 발생하는 즉시 처리할 수 있기 때문에, 시스템의 반응 시간이 빠르다. 그리고, 시스템이 여러 작업을 동시에 처리할 수 있는 멀티 태스킹(비동기성)을 구현하기가 쉽고, 실시간 시스템으로 구현하기 용이하다.
인터럽트 제어 실습
위에서 배운 내용들을 토대로 스위치를 누를 시, LED를 순차적으로 제어하는 것을 구현해 보자.
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
ISR (INT4_vect)
{
PORTA <<= 1;
if(PORTA == 0)
PORTA = 0x01;
}
ISR (INT5_vect)
{
PORTA >>= 1;
if(PORTA == 0)
PORTA = 0x80;
_delay_ms(200);
}
먼저 서비스 루틴의 동작을 정의해 주자. INT4에 대한 명령을 스위치 1의 동작과 연결하였고, INT5에 대한 명령을 스위치 2의 동작과 연결하였다. 이때 하드웨어적으로 어느 정도 디바운싱이 되지만, 사용하고 있는 제품에 따라 디바운싱이 불안정할 수 있으므로 직접 동작을 구현해 보고, 디바운싱이 필요하다면 딜레이를 넣어주면 된다. 필자의 제품의 경우 SW2의 디바운싱이 불안정하여 200ms의 딜레이를 추가하였다.
int main(void)
{
DDRA = 0xFF;
DDRE = DDRE & ~(1 << PINE4) & ~(1 << PINE5);
EICRB = EICRB & ~(1 << ISC40) | (1 << ISC41);
EIMSK = EIMSK | (1 << INT4) | (1 << INT5);
sei();
PORTA = 0x01;
while(1)
{
}
return 0;
}
LED를 점등하기 위한 레지스터 DDRA, 스위치를 제어하기 위한 레지스터 DDRE를 설정해 주자. 그리고 LOW LEVEL 인터럽트 트리거 설정을 위해 EICRB 레지스터를 설정해 주고, INT4, INT5 인터럽트를 허가하였다. 마지막으로 전체 인터럽트를 허가하기 위해 sei() 함수를 사용하였다(SREG.I = 1).
총합본
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
ISR (INT4_vect)
{
PORTA <<= 1;
if(PORTA == 0)
PORTA = 0x01;
}
ISR (INT5_vect)
{
PORTA >>= 1;
if(PORTA == 0)
PORTA = 0x80;
_delay_ms(200);
}
int main(void)
{
DDRA = 0xFF;
DDRE = DDRE & ~(1 << PINE4) & ~(1 << PINE5);
EICRB = EICRB & ~(1 << ISC40) | (1 << ISC41);
EIMSK = EIMSK | (1 << INT4) | (1 << INT5);
sei();
PORTA = 0x01;
while(1)
{
}
return 0;
}
요약
폴링 방식
1. 정의: 프로그램이 주기적으로 하드웨어 장치의 상태를 검사하여 이벤트를 감지하는 방식
2. 특징
a. CPU가 정해진 주기마다 상태를 체크
b. 프로그래머가 검사 시간과 간격을 제어할 수 있음
c. 동기성을 갖고 있어 예측 가능한 프로그램 흐름을 가짐
3. 장점
a. 구현이 단순하고 직관적
b. 이벤트 처리에 대한 제어가 용이
4. 단점
a. 변화가 없을 때도 계속해서 상태를 검사하므로 CPU 자원을 낭비
b. 이벤트에 대한 반응이 풀링 주기에 의존하여 지연될 수 있음
c. 여러 장치를 풀링 하는 경우 시스템의 전반적인 효율성 저하
인터럽트 방식
1. 정의: 외부 이벤트가 발생하면 현재 실행 중인 프로세스를 중단하고, 해당 이벤트를 우선적으로 처리한 다음 원래 작업으로 복귀하는 메커니즘
2. 특징
a. 이벤트가 발생하면 ISR을 즉시 호출
b. 인터럽트 발생 시 인터럽트 벡터 테이블을 참조하여 ISR로 분기
c. 우선순위에 따라 여러 인터럽트 중 어떤 것을 먼저 처리할지 결정
d. 비동기성
3. 장점
a. 시스템 자원을 효율적으로 사용하며, 이벤트에 대한 반응 시간이 빠름
b. 멀티태스킹을 구현하기 용이하며, 실시간 시스템 구현에 적합
4. 단점
a. ISR 설계와 관리가 복잡할 수 있음
b. 인터럽트 폭풍과 같은 상황에서 성능 저하가 발생
- 인터럽트 폭풍: 인터럽트 요청이 비정상적으로 많이 발생하여 시스템의 성능이 저하되는 현상
5. 처리 과정
- 인터럽트 발생 -> 현재 명령어 완료 후 PC에 복귀 주소를 스택에 저장 -> 인터럽트 벡터 확인하여 ISR 주소로 분기 -> ISR 내의 명령어 실행 -> 처리 완료 후 복귀 주소로 돌아와 원래 작업 재개
'CS > 마이크로프로세서' 카테고리의 다른 글
[마이크로프로세서] 타이머/카운터 (2) (4) | 2023.11.27 |
---|---|
[마이크로프로세서] 타이머/카운터 (1) (0) | 2023.11.15 |
[마이크로프로세서] ATmega 128 디지털 입출력 제어 (3) (0) | 2023.11.01 |
[마이크로프로세서] ATmega 128 디지털 입출력 제어 (2) (2) | 2023.10.30 |
[마이크로프로세서] ATmega 128 디지털 입출력 제어 (2) | 2023.10.14 |