Interrupt

김준혁·2026년 3월 31일

Event가 발생했을때, 처리하고 복귀하는 매커니즘.
인터럽트 Vector 주소로 이동하여 코드를 실행한다.
ISR(인터럽트 서비스 루틴)

Vector Table

메모리의 맨 앞부분에 주소가 고정되어 있다.
주소가 낮을수록 우선순위가 높다.
Reset>Int0>Int1>...
리셋포함 총 35개

요청-주 프로그램 중단-상태저장-ISR실행-상태복원-복귀

c언어 구현

#include <avr/interrupt.h> 헤더파일 필요
ISR(벡터이름){} 형식임.
전역 인터럽트 활성화 sei()
반대는 cli()

#include <avr/io.h>
#include <avr/interrupt.h>

#define LED_PIN PB5 // 아두이노 우노 내장 LED (D13)
#define BTN_PIN PD2 // 외부 인터럽트 0 (D2)

volatile int state = 0;			// 현재 LED의 상태
void int0_init(void) {
    // 1. PD2 핀을 입력으로 설정 및 내부 풀업 저항 활성화
    DDRD &= ~(1 << BTN_PIN);
    PORTD |= (1 << BTN_PIN);

    // 2. 인터럽트 감지 조건 설정 (EICRA 레지스터)
    // ISC01 = 1, ISC00 = 0 : 하강 에지(Falling Edge) 감지
    // 스위치를 눌러 전압이 GND로 떨어지는 순간을 포착함
    EICRA |= (1 << ISC01); // 1번 비트를 1로
    EICRA &= ~(1 << ISC00); // 0번 비트를 0으로

    // 3. INT0 외부 인터럽트 허용 (EIMSK 레지스터)
    EIMSK |= (1 << INT0);

    // 4. 전역 인터럽트 활성화
    sei(); 
}

// INT0 외부 인터럽트 서비스 루틴(ISR)
ISR(INT0_vect) {
    // LED 상태 반전 (Toggle)
    PORTB ^= (1 << LED_PIN); 
}

int main(void) {
    // LED 핀을 출력으로 설정
    DDRB |= (1 << LED_PIN);

    // 인터럽트 초기화
    int0_init();

    while(1) {
        // 메인 루프는 비워둠. 
        // CPU는 다른 작업을 하다가 버튼이 눌리면 즉각 ISR을 실행함.
    }
    return 0;
}

평소엔 while문(아무것도 동작 안하는 무한루프)를 돌다가, ISR로 이동하면 LED상태를 토글시킨다.

다중 인터럽트 처리

우선순위부터 처리하고, 나머지는 대기(pending상태임)
기존 코드에서, 버튼 디바운싱 부분 추가. Timer0이용(8bit)

#include <avr/io.h>
#include <avr/interrupt.h>

#define LED_PIN PB5 // 아두이노 내장 LED (D13)
#define BTN_PIN PD2 // 외부 인터럽트 0 (D2)
volatile int debounce_count = 0; // 전역 변수 추가
void init_hardware(void) {
    // 1. 입출력 핀 설정 (D2 풀업 저항 활성화)
    DDRB |= (1 << LED_PIN);
    DDRD &= ~(1 << BTN_PIN);
    PORTD |= (1 << BTN_PIN);

    // 2. INT0 외부 인터럽트 설정 (하강 에지)
    EICRA |= (1 << ISC01);
    EICRA &= ~(1 << ISC00);
    EIMSK |= (1 << INT0); 

    // 3. Timer0 초기화 (아직 타이머는 가동하지 않음)
    TCCR0A = 0x00; // 일반(Normal) 모드
    TCCR0B = 0x00; // 타이머 정지 상태
    
    sei(); // 전역 인터럽트 활성화
}

// [핵심 1] INT0 외부 인터럽트 (버튼이 눌리는 순간 1회만 실행됨)
ISR(INT0_vect) {
    // 1. 바운싱으로 인한 중복 실행을 막기 위해 INT0 감지 임시 차단
    EIMSK &= ~(1 << INT0);

    // 2. 실제 원하는 작업 수행
    PORTB ^= (1 << LED_PIN); 

    // 3. 디바운싱 타이머(Timer0) 가동 설정
    TCNT0 = 0;              // 타이머 카운트 0으로 초기화
    TIFR0 |= (1 << TOV0);   // 이전 오버플로우 플래그 강제 지움
    TIMSK0 |= (1 << TOIE0); // Timer0 오버플로우 인터럽트 켜기
    
    // 프리스케일러 1024 적용하여 타이머 시작 
    // (16MHz / 1024 = 15625Hz. 256번 카운트 시 약 16.38ms 경과)
    TCCR0B = (1 << CS02) | (1 << CS00);
}

// [핵심 2] Timer0 오버플로우 인터럽트 (약 16.38ms 후 스위치 안정화 시점)
ISR(TIMER0_OVF_vect) {
    // 1. 타이머 정지 및 인터럽트 끄기 (1회용 지연 역할 완료)
    TCCR0B = 0x00; 
    TIMSK0 &= ~(1 << TOIE0);

    // 2. 대기하는 16ms 동안 발생했던 노이즈(바운싱) 인터럽트 플래그 강제 삭제
    EIFR |= (1 << INTF0);

    // 3. INT0 외부 인터럽트 다시 켜기 (새로운 버튼 클릭 받을 준비 완료)
    EIMSK |= (1 << INT0);
}

int main(void) {
    init_hardware();

    while(1) {
        // 메인 루프는 완전히 비어 있습니다. 
        // 딜레이 없이 100%의 CPU 성능을 다른 기능(UART, 센서 읽기 등)에 쏟을 수 있습니다.
    }
    return 0;
}

delay는 함수는 가급적 지양하는 것이 좋음

profile
임베디드

0개의 댓글