인터럽트 심화와 실시간 시스템) 진짜 멀티태스킹 구현하기

안녕하세요! 6편에서 타이머 인터럽트의 기초를 배웠다면, 이번에는 인터럽트의 모든 종류를 다뤄보겠습니다. 외부 인터럽트, 핀 변화 인터럽트, 인터럽트 우선순위 등을 활용해서 진짜 실시간 시스템을 만들어보겠습니다! (라고 쓰고 지옥에 온걸 환영합니다.?)

이번 편의 목표

  • 외부 인터럽트로 즉각적인 버튼 반응 구현
  • 핀 변화 인터럽트로 다중 입력 모니터링
  • 인터럽트 우선순위와 네스팅 이해
  • 실시간 멀티태스킹 시스템 설계
  • 안전한 인터럽트 프로그래밍 패턴

ATmega328P 인터럽트 전체 맵

인터럽트 벡터 테이블

벡터주소인터럽트 소스설명
10x0000RESET시스템 리셋
20x0002INT0외부 인터럽트 0 (PD2)
30x0004INT1외부 인터럽트 1 (PD3)
40x0006PCINT0핀 변화 인터럽트 0 (PORTB)
50x0008PCINT1핀 변화 인터럽트 1 (PORTC)
60x000APCINT2핀 변화 인터럽트 2 (PORTD)
70x000CWDT워치독 타이머
80x000ETIMER2_COMPATimer2 Compare Match A
90x0010TIMER2_COMPBTimer2 Compare Match B
100x0012TIMER2_OVFTimer2 오버플로우
110x0014TIMER1_CAPTTimer1 캡처
120x0016TIMER1_COMPATimer1 Compare Match A
130x0018TIMER1_COMPBTimer1 Compare Match B
140x001ATIMER1_OVFTimer1 오버플로우
150x001CTIMER0_COMPATimer0 Compare Match A
160x001ETIMER0_COMPBTimer0 Compare Match B
170x0020TIMER0_OVFTimer0 오버플로우
180x0022SPI_STCSPI 전송 완료
190x0024USART_RXUART 수신 완료
200x0026USART_UDREUART 데이터 레지스터 빔
210x0028USART_TXUART 송신 완료
220x002AADCADC 변환 완료
230x002CEE_READYEEPROM 준비 완료
240x002EANALOG_COMP아날로그 비교기
250x0030TWII2C(TWI)
260x0032SPM_READY플래시 메모리 프로그래밍

인터럽트 우선순위 규칙

핵심 원칙:

  1. 낮은 벡터 번호 = 높은 우선순위
  2. 높은 우선순위가 낮은 우선순위를 중단시킬 수 있음
  3. 같은 우선순위끼리는 중단 불가

외부 인터럽트 (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);  // 상승 엣지 트리거

트리거 모드 설정:

ISC1ISC0모드설명
00Low LevelLOW 레벨 동안 계속 트리거
01Any Edge상승/하강 엣지 모두
10Falling Edge하강 엣지 (권장)
11Rising 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 시리얼 통신을 배워보겠습니다:

  • 컴퓨터와 마이크로컨트롤러 간 데이터 교환
  • 인터럽트 기반 송수신 버퍼 구현
  • 간단한 명령어 프로토콜 설계
  • 데이터 로깅과 원격 제어
  • 여러 장치 간 통신 네트워크 구축

이제 진짜 실시간 시스템을 구축할 수 있게 되었습니다!


인터럽트를 완전히 마스터하면 마이크로컨트롤러가 단순한 순차 처리기에서 고성능 실시간 시스템으로 변신합니다! 진짜 어려운 부분 끝까지 봐주셔서 감사합니다. 이제 다음장이 마지막입니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

이 사이트는 Akismet을 사용하여 스팸을 줄입니다. 댓글 데이터가 어떻게 처리되는지 알아보세요.