안녕하세요! 6편에서 타이머 인터럽트의 기초를 배웠다면, 이번에는 인터럽트의 모든 종류를 다뤄보겠습니다. 외부 인터럽트, 핀 변화 인터럽트, 인터럽트 우선순위 등을 활용해서 진짜 실시간 시스템을 만들어보겠습니다! (라고 쓰고 지옥에 온걸 환영합니다.?)
이번 편의 목표
- 외부 인터럽트로 즉각적인 버튼 반응 구현
- 핀 변화 인터럽트로 다중 입력 모니터링
- 인터럽트 우선순위와 네스팅 이해
- 실시간 멀티태스킹 시스템 설계
- 안전한 인터럽트 프로그래밍 패턴
ATmega328P 인터럽트 전체 맵
인터럽트 벡터 테이블
| 벡터 | 주소 | 인터럽트 소스 | 설명 | 
|---|---|---|---|
| 1 | 0x0000 | RESET | 시스템 리셋 | 
| 2 | 0x0002 | INT0 | 외부 인터럽트 0 (PD2) | 
| 3 | 0x0004 | INT1 | 외부 인터럽트 1 (PD3) | 
| 4 | 0x0006 | PCINT0 | 핀 변화 인터럽트 0 (PORTB) | 
| 5 | 0x0008 | PCINT1 | 핀 변화 인터럽트 1 (PORTC) | 
| 6 | 0x000A | PCINT2 | 핀 변화 인터럽트 2 (PORTD) | 
| 7 | 0x000C | WDT | 워치독 타이머 | 
| 8 | 0x000E | TIMER2_COMPA | Timer2 Compare Match A | 
| 9 | 0x0010 | TIMER2_COMPB | Timer2 Compare Match B | 
| 10 | 0x0012 | TIMER2_OVF | Timer2 오버플로우 | 
| 11 | 0x0014 | TIMER1_CAPT | Timer1 캡처 | 
| 12 | 0x0016 | TIMER1_COMPA | Timer1 Compare Match A | 
| 13 | 0x0018 | TIMER1_COMPB | Timer1 Compare Match B | 
| 14 | 0x001A | TIMER1_OVF | Timer1 오버플로우 | 
| 15 | 0x001C | TIMER0_COMPA | Timer0 Compare Match A | 
| 16 | 0x001E | TIMER0_COMPB | Timer0 Compare Match B | 
| 17 | 0x0020 | TIMER0_OVF | Timer0 오버플로우 | 
| 18 | 0x0022 | SPI_STC | SPI 전송 완료 | 
| 19 | 0x0024 | USART_RX | UART 수신 완료 | 
| 20 | 0x0026 | USART_UDRE | UART 데이터 레지스터 빔 | 
| 21 | 0x0028 | USART_TX | UART 송신 완료 | 
| 22 | 0x002A | ADC | ADC 변환 완료 | 
| 23 | 0x002C | EE_READY | EEPROM 준비 완료 | 
| 24 | 0x002E | ANALOG_COMP | 아날로그 비교기 | 
| 25 | 0x0030 | TWI | I2C(TWI) | 
| 26 | 0x0032 | SPM_READY | 플래시 메모리 프로그래밍 | 
인터럽트 우선순위 규칙
핵심 원칙:
- 낮은 벡터 번호 = 높은 우선순위
- 높은 우선순위가 낮은 우선순위를 중단시킬 수 있음
- 같은 우선순위끼리는 중단 불가
외부 인터럽트 (INT0, INT1) 마스터하기
외부 인터럽트의 특징
- PD2 (INT0), PD3 (INT1) 핀 전용
- 엣지 또는 레벨 트리거 선택 가능
- 최고 우선순위 (벡터 2, 3번)
- 즉각적인 반응 (debouncing 불필요)
외부 인터럽트 설정
EICRA (External Interrupt Control Register A)
c
// INT0 설정
EICRA |= (1 << ISC01);  // 하강 엣지 트리거
EICRA &= ~(1 << ISC00);
// INT1 설정  
EICRA |= (1 << ISC11) | (1 << ISC10);  // 상승 엣지 트리거트리거 모드 설정:
| ISC1 | ISC0 | 모드 | 설명 | 
|---|---|---|---|
| 0 | 0 | Low Level | LOW 레벨 동안 계속 트리거 | 
| 0 | 1 | Any Edge | 상승/하강 엣지 모두 | 
| 1 | 0 | Falling Edge | 하강 엣지 (권장) | 
| 1 | 1 | Rising Edge | 상승 엣지 | 
EIMSK (External Interrupt Mask Register)
c
EIMSK |= (1 << INT0);  // INT0 인터럽트 활성화
EIMSK |= (1 << INT1);  // INT1 인터럽트 활성화기본 외부 인터럽트 프로그램
c
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
volatile uint8_t button0_pressed = 0;
volatile uint8_t button1_pressed = 0;
volatile uint32_t last_interrupt_time = 0;
volatile uint32_t system_ms = 0;
// INT0 인터럽트 (PD2)
ISR(INT0_vect) {
    // 간단한 소프트웨어 디바운싱
    if ((system_ms - last_interrupt_time) > 50) {
        button0_pressed = 1;
        last_interrupt_time = system_ms;
    }
}
// INT1 인터럽트 (PD3)  
ISR(INT1_vect) {
    if ((system_ms - last_interrupt_time) > 50) {
        button1_pressed = 1;
        last_interrupt_time = system_ms;
    }
}
// 시스템 타이머 (1ms 틱)
ISR(TIMER0_COMPA_vect) {
    system_ms++;
}
void external_interrupt_init(void) {
    // 핀 설정 (입력, 풀업)
    DDRD &= ~((1 << PD2) | (1 << PD3));
    PORTD |= (1 << PD2) | (1 << PD3);
    
    // 인터럽트 트리거 설정
    EICRA |= (1 << ISC01);  // INT0: 하강 엣지
    EICRA |= (1 << ISC11);  // INT1: 하강 엣지
    
    // 인터럽트 활성화
    EIMSK |= (1 << INT0) | (1 << INT1);
}
void timer_init(void) {
    // Timer0 CTC 모드, 1ms 주기
    TCCR0A = (1 << WGM01);
    TCCR0B = (1 << CS01) | (1 << CS00);  // 64 분주
    OCR0A = 249;  // 16MHz/64/250 = 1kHz (1ms)
    TIMSK0 |= (1 << OCIE0A);
}
int main(void) {
    // LED 출력 설정
    DDRB |= (1 << PB0) | (1 << PB1) | (1 << PB5);
    
    external_interrupt_init();
    timer_init();
    sei();
    
    while(1) {
        // 버튼 이벤트 처리
        if (button0_pressed) {
            button0_pressed = 0;
            PORTB ^= (1 << PB0);  // LED0 토글
            
            // 즉각적인 반응을 위한 다른 작업들...
            handle_emergency_button();
        }
        
        if (button1_pressed) {
            button1_pressed = 0;
            PORTB ^= (1 << PB1);  // LED1 토글
            
            cycle_operating_mode();
        }
        
        // 평상시 작업들
        background_tasks();
        _delay_ms(10);
    }
    
    return 0;
}
void handle_emergency_button(void) {
    // 긴급 정지 기능
    PORTB |= (1 << PB5);   // 경고 LED 켜기
    
    // 모든 모터 정지
    OCR1A = 0;
    OCR1B = 0;
    
    // 안전 상태로 전환
    system_state = STATE_EMERGENCY;
}
void cycle_operating_mode(void) {
    static uint8_t mode = 0;
    
    mode = (mode + 1) % 3;
    
    switch(mode) {
        case 0: set_auto_mode(); break;
        case 1: set_manual_mode(); break;
        case 2: set_setup_mode(); break;
    }
}핀 변화 인터럽트 (PCINT) 활용하기
핀 변화 인터럽트의 특징
- 모든 GPIO 핀 사용 가능
- 상승/하강 엣지 모두 감지
- 포트 단위로 그룹화 (PORTB, PORTC, PORTD)
- 어떤 핀이 변했는지는 소프트웨어에서 판별
PCINT 설정 레지스터
c
// PCINT 그룹 활성화
PCICR |= (1 << PCIE0);  // PORTB (PCINT0~7)
PCICR |= (1 << PCIE1);  // PORTC (PCINT8~14)  
PCICR |= (1 << PCIE2);  // PORTD (PCINT16~23)
// 개별 핀 선택
PCMSK0 |= (1 << PCINT0);  // PB0
PCMSK1 |= (1 << PCINT9);  // PC1
PCMSK2 |= (1 << PCINT18); // PD2다중 버튼 모니터링 시스템
c
#include <avr/io.h>
#include <avr/interrupt.h>
typedef struct {
    uint8_t current_state;
    uint8_t previous_state;
    uint8_t changed_pins;
    uint32_t last_change_time;
} port_monitor_t;
port_monitor_t portb_monitor = {0, 0, 0, 0};
port_monitor_t portc_monitor = {0, 0, 0, 0};
volatile uint32_t system_ms = 0;
// PORTB 핀 변화 인터럽트 (PB0~PB7)
ISR(PCINT0_vect) {
    portb_monitor.current_state = PINB;
    portb_monitor.changed_pins = portb_monitor.current_state ^ portb_monitor.previous_state;
    portb_monitor.last_change_time = system_ms;
    portb_monitor.previous_state = portb_monitor.current_state;
}
// PORTC 핀 변화 인터럽트 (PC0~PC5)
ISR(PCINT1_vect) {
    portc_monitor.current_state = PINC;
    portc_monitor.changed_pins = portc_monitor.current_state ^ portc_monitor.previous_state;
    portc_monitor.last_change_time = system_ms;
    portc_monitor.previous_state = portc_monitor.current_state;
}
// 시스템 타이머
ISR(TIMER0_COMPA_vect) {
    system_ms++;
}
void pcint_init(void) {
    // 모니터링할 핀들을 입력으로 설정
    DDRB &= ~0x0F;  // PB0~PB3 입력
    DDRC &= ~0x3F;  // PC0~PC5 입력
    
    // 풀업 활성화
    PORTB |= 0x0F;
    PORTC |= 0x3F;
    
    // PCINT 활성화
    PCICR |= (1 << PCIE0) | (1 << PCIE1);
    
    // 모니터링할 핀 선택
    PCMSK0 = 0x0F;  // PB0~PB3
    PCMSK1 = 0x3F;  // PC0~PC5
    
    // 초기 상태 저장
    portb_monitor.previous_state = PINB;
    portc_monitor.previous_state = PINC;
}
void timer_init(void) {
    TCCR0A = (1 << WGM01);
    TCCR0B = (1 << CS01) | (1 << CS00);
    OCR0A = 249;
    TIMSK0 |= (1 << OCIE0A);
}
void process_button_changes(void) {
    // PORTB 버튼 처리
    if (portb_monitor.changed_pins && 
        (system_ms - portb_monitor.last_change_time > 5)) {  // 5ms 디바운싱
        
        for (uint8_t i = 0; i < 4; i++) {
            if (portb_monitor.changed_pins & (1 << i)) {
                if (!(portb_monitor.current_state & (1 << i))) {
                    // 버튼 눌림 (하강 엣지)
                    handle_portb_button_press(i);
                } else {
                    // 버튼 떼임 (상승 엣지)  
                    handle_portb_button_release(i);
                }
            }
        }
        
        portb_monitor.changed_pins = 0;  // 처리 완료
    }
    
    // PORTC 버튼 처리 (유사한 로직)
    if (portc_monitor.changed_pins &&
        (system_ms - portc_monitor.last_change_time > 5)) {
        
        for (uint8_t i = 0; i < 6; i++) {
            if (portc_monitor.changed_pins & (1 << i)) {
                if (!(portc_monitor.current_state & (1 << i))) {
                    handle_portc_button_press(i);
                } else {
                    handle_portc_button_release(i);
                }
            }
        }
        
        portc_monitor.changed_pins = 0;
    }
}
void handle_portb_button_press(uint8_t button) {
    switch(button) {
        case 0: // PB0 - 메뉴 버튼
            enter_menu_mode();
            break;
        case 1: // PB1 - 위 버튼
            navigate_up();
            break;
        case 2: // PB2 - 아래 버튼
            navigate_down();  
            break;
        case 3: // PB3 - 선택 버튼
            confirm_selection();
            break;
    }
}
void handle_portc_button_press(uint8_t button) {
    // 각 PC 핀에 연결된 기능들
    printf("PC%d button pressed\n", button);
}
int main(void) {
    pcint_init();
    timer_init();
    sei();
    
    while(1) {
        process_button_changes();
        
        // 기타 작업들
        update_display();
        check_sensors();
        
        _delay_ms(1);
    }
    
    return 0;
}인터럽트 우선순위와 네스팅
인터럽트 네스팅 실험
c
#include <avr/io.h>
#include <avr/interrupt.h>
volatile uint8_t interrupt_depth = 0;
volatile uint8_t max_depth = 0;
// 최고 우선순위: 외부 인터럽트 0
ISR(INT0_vect) {
    interrupt_depth++;
    if (interrupt_depth > max_depth) {
        max_depth = interrupt_depth;
    }
    
    PORTB |= (1 << PB0);  // 인터럽트 시작 표시
    
    // 긴 작업 시뮬레이션
    for (volatile uint16_t i = 0; i < 10000; i++);
    
    PORTB &= ~(1 << PB0);  // 인터럽트 끝 표시
    
    interrupt_depth--;
}
// 중간 우선순위: Timer1 오버플로우  
ISR(TIMER1_OVF_vect) {
    interrupt_depth++;
    if (interrupt_depth > max_depth) {
        max_depth = interrupt_depth;
    }
    
    PORTB |= (1 << PB1);
    
    // 중간 길이 작업
    for (volatile uint16_t i = 0; i < 5000; i++);
    
    PORTB &= ~(1 << PB1);
    
    interrupt_depth--;
}
// 낮은 우선순위: Timer0 오버플로우
ISR(TIMER0_OVF_vect) {
    interrupt_depth++;
    if (interrupt_depth > max_depth) {
        max_depth = interrupt_depth;
    }
    
    PORTB |= (1 << PB2);
    
    // 짧은 작업
    for (volatile uint16_t i = 0; i < 1000; i++);
    
    PORTB &= ~(1 << PB2);
    
    interrupt_depth--;
}
void test_interrupt_nesting(void) {
    // 외부 인터럽트 설정
    DDRD &= ~(1 << PD2);
    PORTD |= (1 << PD2);
    EICRA |= (1 << ISC01);
    EIMSK |= (1 << INT0);
    
    // Timer1 설정 (느린 오버플로우)
    TCCR1B = (1 << CS12) | (1 << CS10);  // 1024 분주
    TIMSK1 = (1 << TOIE1);
    
    // Timer0 설정 (빠른 오버플로우)  
    TCCR0B = (1 << CS02) | (1 << CS00);  // 1024 분주
    TIMSK0 = (1 << TOIE0);
    
    sei();
}
int main(void) {
    DDRB = 0xFF;  // 모든 PB 핀을 출력으로
    
    test_interrupt_nesting();
    
    while(1) {
        // 오실로스코프로 PB0, PB1, PB2 관찰
        // 인터럽트 네스팅 패턴 확인 가능
        
        printf("Max interrupt depth: %d\n", max_depth);
        _delay_ms(1000);
    }
    
    return 0;
}인터럽트 비활성화/활성화 제어
c
#include <util/atomic.h>
void critical_section_example(void) {
    // 방법 1: 전역 인터럽트 제어
    cli();  // 모든 인터럽트 차단
    // 중요한 작업...
    sei();  // 인터럽트 재활성화
    
    // 방법 2: 원자적 블록 (권장)
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
        // 이전 인터럽트 상태를 자동으로 복원
        shared_variable++;
        complex_operation();
    }
    
    // 방법 3: 특정 인터럽트만 제어
    uint8_t old_timsk = TIMSK0;
    TIMSK0 = 0;  // Timer0 인터럽트만 차단
    // 타이머와 무관한 중요 작업...
    TIMSK0 = old_timsk;  // 복원
}실시간 멀티태스킹 시스템
간단한 스케줄러 구현
c
#include <avr/io.h>
#include <avr/interrupt.h>
#define MAX_TASKS 8
typedef enum {
    TASK_READY,
    TASK_RUNNING, 
    TASK_WAITING,
    TASK_SUSPENDED
} task_state_t;
typedef struct {
    void (*function)(void);
    uint16_t period_ms;
    uint16_t next_run_ms;
    task_state_t state;
    uint8_t priority;  // 0 = 최고 우선순위
} task_t;
task_t tasks[MAX_TASKS];
uint8_t task_count = 0;
volatile uint16_t system_time_ms = 0;
// 1ms 시스템 틱
ISR(TIMER0_COMPA_vect) {
    system_time_ms++;
}
void scheduler_init(void) {
    // Timer0 CTC 모드, 1ms 주기
    TCCR0A = (1 << WGM01);
    TCCR0B = (1 << CS01) | (1 << CS00);
    OCR0A = 249;
    TIMSK0 = (1 << OCIE0A);
    
    task_count = 0;
}
uint8_t add_task(void (*func)(void), uint16_t period_ms, uint8_t priority) {
    if (task_count >= MAX_TASKS) return 0;  // 실패
    
    tasks[task_count].function = func;
    tasks[task_count].period_ms = period_ms;
    tasks[task_count].next_run_ms = system_time_ms + period_ms;
    tasks[task_count].state = TASK_READY;
    tasks[task_count].priority = priority;
    
    task_count++;
    return 1;  // 성공
}
void scheduler_run(void) {
    // 실행할 태스크 찾기 (우선순위 순서)
    uint8_t highest_priority = 255;
    int8_t selected_task = -1;
    
    for (uint8_t i = 0; i < task_count; i++) {
        if (tasks[i].state == TASK_READY && 
            system_time_ms >= tasks[i].next_run_ms &&
            tasks[i].priority < highest_priority) {
            
            highest_priority = tasks[i].priority;
            selected_task = i;
        }
    }
    
    // 선택된 태스크 실행
    if (selected_task >= 0) {
        tasks[selected_task].state = TASK_RUNNING;
        tasks[selected_task].function();
        tasks[selected_task].state = TASK_READY;
        tasks[selected_task].next_run_ms = system_time_ms + tasks[selected_task].period_ms;
    }
}
// 샘플 태스크들
void task_blink_led(void) {
    static uint8_t led_state = 0;
    led_state = !led_state;
    
    if (led_state) {
        PORTB |= (1 << PB5);
    } else {
        PORTB &= ~(1 << PB5);
    }
}
void task_read_sensors(void) {
    // ADC 읽기, 센서 값 처리
    uint16_t temp = adc_read(0);
    uint16_t light = adc_read(1);
    
    process_sensor_data(temp, light);
}
void task_update_display(void) {
    // 디스플레이 업데이트 
    static uint8_t counter = 0;
    printf("Display update: %d\n", counter++);
}
void task_check_buttons(void) {
    // 버튼 상태 체크 (PCINT와 별도)
    static uint8_t prev_buttons = 0xFF;
    uint8_t current_buttons = PINC & 0x0F;
    
    if (current_buttons != prev_buttons) {
        handle_button_change(prev_buttons ^ current_buttons);
        prev_buttons = current_buttons;
    }
}
void task_motor_control(void) {
    // 모터 제어 업데이트
    update_motor_speeds();
    check_motor_status();
}
int main(void) {
    // 하드웨어 초기화
    DDRB |= (1 << PB5);  // LED 출력
    
    scheduler_init();
    sei();
    
    // 태스크 등록 (함수, 주기, 우선순위)
    add_task(task_blink_led, 500, 3);      // 500ms, 낮은 우선순위
    add_task(task_read_sensors, 100, 1);   // 100ms, 높은 우선순위  
    add_task(task_update_display, 1000, 2); // 1000ms, 중간 우선순위
    add_task(task_check_buttons, 50, 0);    // 50ms, 최고 우선순위
    add_task(task_motor_control, 20, 1);    // 20ms, 높은 우선순위
    
    while(1) {
        scheduler_run();
        
        // CPU 사용률 측정용
        idle_task();
    }
    
    return 0;
}
void idle_task(void) {
    // CPU가 할 일이 없을 때 실행
    // 전력 절약, 통계 수집 등
    static uint32_t idle_count = 0;
    idle_count++;
    
    // 1초마다 CPU 사용률 출력
    if (idle_count % 100000 == 0) {
        printf("Idle count: %lu\n", idle_count);
    }
}안전한 인터럽트 프로그래밍
데이터 무결성 보장
c
#include <util/atomic.h>
typedef struct {
    int16_t x, y, z;
    uint32_t timestamp;
} sensor_data_t;
volatile sensor_data_t accelerometer_data;
volatile uint8_t data_ready = 0;
// 센서 데이터 업데이트 (인터럽트에서)
ISR(INT0_vect) {
    // 원자적으로 모든 데이터 업데이트
    accelerometer_data.x = read_sensor_x();
    accelerometer_data.y = read_sensor_y(); 
    accelerometer_data.z = read_sensor_z();
    accelerometer_data.timestamp = system_time_ms;
    
    data_ready = 1;
}
// 안전한 데이터 읽기 (메인에서)
sensor_data_t get_sensor_data(void) {
    sensor_data_t local_copy;
    
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
        local_copy = accelerometer_data;
        data_ready = 0;
    }
    
    return local_copy;
}
// 잘못된 예: 비원자적 접근
sensor_data_t unsafe_get_data(void) {
    sensor_data_t result;
    
    // 읽는 도중 인터럽트 발생 시 데이터 일관성 깨짐!
    result.x = accelerometer_data.x;  // 인터럽트 발생 가능
    result.y = accelerometer_data.y;  // 새로운 데이터로 덮어쓰임
    result.z = accelerometer_data.z;  // 섞인 데이터!
    
    return result;
}인터럽트 오버런 감지
c
volatile uint32_t timer_overrun_count = 0;
volatile uint8_t timer_processing = 0;
ISR(TIMER0_COMPA_vect) {
    if (timer_processing) {
        // 이전 인터럽트가 아직 처리 중!
        timer_overrun_count++;
        return;
    }
    
    timer_processing = 1;
    
    // 실제 타이머 작업
    system_time_ms++;
    update_tasks();
    
    timer_processing = 0;
}
void check_system_health(void) {
    if (timer_overrun_count > 0) {
        printf("WARNING: Timer overrun detected! Count: %lu\n", timer_overrun_count);
        timer_overrun_count = 0;
        
        // 시스템 부하 경감 조치
        reduce_task_frequency();
    }
}인터럽트 실행 시간 측정
c
void measure_interrupt_timing(void) {
    // 테스트용 핀 설정
    DDRB |= (1 << PB0) | (1 << PB1) | (1 << PB2);
}
ISR(TIMER0_COMPA_vect) {
    PORTB |= (1 << PB0);   // 인터럽트 시작
    
    // 실제 작업
    system_time_ms++;
    
    PORTB &= ~(1 << PB0);  // 인터럽트 종료
}
ISR(ADC_vect) {
    PORTB |= (1 << PB1);   // ADC 인터럽트 시작
    
    adc_result = ADC;
    adc_ready = 1;
    
    PORTB &= ~(1 << PB1);  // ADC 인터럽트 종료
}
// 오실로스코프로 PB0, PB1 파형 측정
// → 각 인터럽트의 실행 시간과 빈도 확인 가능실전 응용: 스마트 홈 컨트롤러
통합 시스템 예제
c
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/atomic.h>
// 시스템 상태
typedef enum {
    MODE_AUTO,
    MODE_MANUAL,
    MODE_EMERGENCY,
    MODE_SETUP
} system_mode_t;
volatile system_mode_t current_mode = MODE_AUTO;
volatile uint32_t system_time_ms = 0;
// 센서 데이터
volatile uint16_t temperature_raw = 0;
volatile uint16_t light_level_raw = 0;
volatile uint8_t motion_detected = 0;
// 제어 출력
volatile uint8_t fan_speed = 0;        // 0~255
volatile uint8_t light_brightness = 0; // 0~255
volatile uint8_t security_armed = 0;
// 긴급 버튼 (최고 우선순위)
ISR(INT0_vect) {
    current_mode = MODE_EMERGENCY;
    
    // 즉시 안전 상태로
    fan_speed = 0;
    light_brightness = 255;  // 비상등
    security_armed = 0;
    
    // 경고음 활성화
    PORTB |= (1 << PB5);
}
// 모드 전환 버튼
ISR(INT1_vect) {
    static uint32_t last_press = 0;
    
    if ((system_time_ms - last_press) > 200) {  // 디바운싱
        if (current_mode != MODE_EMERGENCY) {
            current_mode = (current_mode + 1) % 3;  // AUTO, MANUAL, SETUP 순환
        }
        last_press = system_time_ms;
    }
}
// 움직임 센서 (PIR)
ISR(PCINT1_vect) {
    static uint8_t prev_motion = 0;
    uint8_t current_motion = PINC & (1 << PC0);
    
    if (current_motion && !prev_motion) {
        motion_detected = 1;
    }
    
    prev_motion = current_motion;
}
// ADC 완료 (센서 읽기)
ISR(ADC_vect) {
    static uint8_t adc_channel = 0;
    
    if (adc_channel == 0) {
        temperature_raw = ADC;
        adc_channel = 1;
        ADMUX = (ADMUX & 0xF0) | 1;  // 채널 1로 전환
    } else {
        light_level_raw = ADC;
        adc_channel = 0;
        ADMUX = (ADMUX & 0xF0) | 0;  // 채널 0으로 전환
    }
    
    // 다음 변환 시작
    ADCSRA |= (1 << ADSC);
}
// 시스템 틱 (1ms)
ISR(TIMER0_COMPA_vect) {
    system_time_ms++;
    
    // 주기적 ADC 시작 (100ms마다)
    static uint8_t adc_timer = 0;
    if (++adc_timer >= 100) {
        adc_timer = 0;
        if (!(ADCSRA & (1 << ADSC))) {  // ADC가 유휴 상태면
            ADCSRA |= (1 << ADSC);
        }
    }
}
// PWM 업데이트 (10ms)
ISR(TIMER2_COMPA_vect) {
    // 팬 속도 제어
    OCR1A = fan_speed;
    
    // 조명 밝기 제어  
    OCR1B = light_brightness;
}
void hardware_init(void) {
    // GPIO 설정
    DDRB |= (1 << PB1) | (1 << PB2) | (1 << PB5);  // PWM + 경고LED
    DDRD &= ~((1 << PD2) | (1 << PD3));             // 외부 인터럽트 입력
    DDRC &= ~(1 << PC0);                            // 움직임 센서
    
    // 풀업 활성화
    PORTD |= (1 << PD2) | (1 << PD3);
    PORTC |= (1 << PC0);
    
    // PWM 초기화 (Timer1)
    TCCR1A = (1 << COM1A1) | (1 << COM1B1) | (1 << WGM10);
    TCCR1B = (1 << WGM12) | (1 << CS11) | (1 << CS10);
    
    // 시스템 타이머 (Timer0 - 1ms)
    TCCR0A = (1 << WGM01);
    TCCR0B = (1 << CS01) | (1 << CS00);
    OCR0A = 249;
    TIMSK0 = (1 << OCIE0A);
    
    // PWM 업데이트 타이머 (Timer2 - 10ms)
    TCCR2A = (1 << WGM21);
    TCCR2B = (1 << CS22) | (1 << CS21) | (1 << CS20);
    OCR2A = 155;  // 약 10ms
    TIMSK2 = (1 << OCIE2A);
    
    // ADC 초기화
    ADMUX = (1 << REFS0);
    ADCSRA = (1 << ADEN) | (1 << ADIE) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
    
    // 외부 인터럽트
    EICRA = (1 << ISC01) | (1 << ISC11);  // 하강 엣지
    EIMSK = (1 << INT0) | (1 << INT1);
    
    // 핀 변화 인터럽트 (움직임 센서)
    PCICR = (1 << PCIE1);
    PCMSK1 = (1 << PCINT8);  // PC0
}
void auto_mode_logic(void) {
    float temperature = (temperature_raw * 5.0 / 1024.0) * 100.0;  // LM35
    float light_percent = (light_level_raw * 100.0) / 1023.0;
    
    // 온도 기반 팬 제어
    if (temperature > 25.0) {
        fan_speed = (uint8_t)((temperature - 25.0) * 10);
        if (fan_speed > 255) fan_speed = 255;
    } else {
        fan_speed = 0;
    }
    
    // 밝기 기반 조명 제어
    if (light_percent < 30.0) {
        light_brightness = 200;  // 어두우면 켜기
    } else if (light_percent > 70.0) {
        light_brightness = 0;    // 밝으면 끄기
    }
    
    // 움직임 감지 시 조명 켜기
    if (motion_detected) {
        motion_detected = 0;
        light_brightness = 150;
        
        // 보안 시스템 활성화
        if (security_armed) {
            trigger_security_alarm();
        }
    }
}
void manual_mode_logic(void) {
    // 수동 제어 로직
    // 버튼이나 다른 입력으로 직접 제어
}
void emergency_mode_logic(void) {
    // 비상 모드: 모든 시스템 안전 상태 유지
    fan_speed = 0;
    light_brightness = 255;
    security_armed = 0;
    
    // 5초마다 경고음
    static uint32_t last_beep = 0;
    if ((system_time_ms - last_beep) > 5000) {
        PORTB ^= (1 << PB5);
        last_beep = system_time_ms;
    }
}
int main(void) {
    hardware_init();
    sei();
    
    // 시스템 시작 신호
    for (uint8_t i = 0; i < 3; i++) {
        PORTB |= (1 << PB5);
        _delay_ms(200);
        PORTB &= ~(1 << PB5);
        _delay_ms(200);
    }
    
    while(1) {
        switch(current_mode) {
            case MODE_AUTO:
                auto_mode_logic();
                break;
            case MODE_MANUAL:
                manual_mode_logic();
                break;
            case MODE_EMERGENCY:
                emergency_mode_logic();
                break;
            case MODE_SETUP:
                setup_mode_logic();
                break;
        }
        
        // 상태 출력 (1초마다)
        static uint32_t last_status = 0;
        if ((system_time_ms - last_status) >= 1000) {
            print_system_status();
            last_status = system_time_ms;
        }
        
        _delay_ms(10);  // 메인 루프 주기
    }
    
    return 0;
}
void print_system_status(void) {
    const char* mode_names[] = {"AUTO", "MANUAL", "EMERGENCY", "SETUP"};
    
    printf("=== Smart Home Controller ===\n");
    printf("Mode: %s\n", mode_names[current_mode]);
    printf("Temperature: %.1f°C\n", (temperature_raw * 5.0 / 1024.0) * 100.0);
    printf("Light: %.0f%%\n", (light_level_raw * 100.0) / 1023.0);
    printf("Fan: %d/255\n", fan_speed);
    printf("Light: %d/255\n", light_brightness);
    printf("Uptime: %lu sec\n", system_time_ms / 1000);
    printf("\n");
}인터럽트 마스터 과제
과제 1: 정밀 주파수 측정기
외부 인터럽트를 이용해서 입력 신호의 주파수를 0.1Hz 정확도로 측정하는 시스템
과제 2: 실시간 데이터 수집기
다중 센서에서 정확히 1ms마다 동기화된 데이터를 수집하는 시스템
과제 3: 인터럽트 기반 통신 프로토콜
외부 장치와 인터럽트만으로 통신하는 커스텀 프로토콜 구현
다음 편 예고
다음 편에서는 UART 시리얼 통신을 배워보겠습니다:
- 컴퓨터와 마이크로컨트롤러 간 데이터 교환
- 인터럽트 기반 송수신 버퍼 구현
- 간단한 명령어 프로토콜 설계
- 데이터 로깅과 원격 제어
- 여러 장치 간 통신 네트워크 구축
이제 진짜 실시간 시스템을 구축할 수 있게 되었습니다!
인터럽트를 완전히 마스터하면 마이크로컨트롤러가 단순한 순차 처리기에서 고성능 실시간 시스템으로 변신합니다! 진짜 어려운 부분 끝까지 봐주셔서 감사합니다. 이제 다음장이 마지막입니다.