안녕하세요! 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 시리얼 통신을 배워보겠습니다:
- 컴퓨터와 마이크로컨트롤러 간 데이터 교환
- 인터럽트 기반 송수신 버퍼 구현
- 간단한 명령어 프로토콜 설계
- 데이터 로깅과 원격 제어
- 여러 장치 간 통신 네트워크 구축
이제 진짜 실시간 시스템을 구축할 수 있게 되었습니다!
인터럽트를 완전히 마스터하면 마이크로컨트롤러가 단순한 순차 처리기에서 고성능 실시간 시스템으로 변신합니다! 진짜 어려운 부분 끝까지 봐주셔서 감사합니다. 이제 다음장이 마지막입니다.