프로그램의 메인 루프(loop())를 방해하지 않고 긴급 신호를 감지하는 인터럽트(Interrupt) 제어를 배웁니다.
1. 인터럽트 핵심 용어 사전 (Glossary)
- Interrupt (인터럽트): 마이크로컨트롤러가 메인 프로그램을 실행하다가 내부/외부 이벤트가 발생하면 즉시 실행을 멈추고 지정된 예외 처리 루틴을 수행하는 기능입니다.
- ISR (Interrupt Service Routine): 인터럽트 신호 감지 시 즉시 실행되는 전용 콜백 함수입니다.
- volatile: 컴파일러가 해당 변수를 최적화하여 레지스터에 보관하지 않고, 항상 RAM 메모리 주소에서 직접 읽어오도록 강제하는 키워드입니다 (ISR 내 수정 변수 필수).
- millis(): 아두이노 보드가 켜진 후 경과된 시간(밀리초, ms)을 반환하는 타이머 함수입니다. (약 50일 후 0으로 오버플로우)
2. 외부 하드웨어 인터럽트 (attachInterrupt)
아두이노 우노는 **디지털 2번(Interrupt 0)**과 3번(Interrupt 1) 핀에서 외부 하드웨어 인터럽트를 지원합니다.
const byte ledPin = 13;
const byte interruptPin = 2;
// ISR 내에서 변경되는 변수는 반드시 volatile 지정!
volatile boolean ledState = LOW;
void setup() {
pinMode(ledPin, OUTPUT);
pinMode(interruptPin, INPUT_PULLUP);
// 2번 핀 전압이 HIGH -> LOW로 떨어지는 순간(FALLING) toggleISR() 즉시 실행
attachInterrupt(digitalPinToInterrupt(interruptPin), toggleISR, FALLING);
}
void loop() {
// 메인 루프에서는 딜레이나 무거운 연산을 수행 중이어도 인터럽트는 즉시 반영됨
digitalWrite(ledPin, ledState);
}
// ISR 함수는 매개변수와 반환값이 없어야 함 (void)
void toggleISR() {
ledState = !ledState;
}
3. millis() 비동기 멀티태스킹 템플릿
delay()는 CPU를 멈추게 하므로, 여러 개의 작업을 독립적인 주기로 돌리려면 millis() 타임스탬프 비교 방식을 써야 합니다.
unsigned long prevLED = 0;
unsigned long prevSensor = 0;
void loop() {
unsigned long current = millis();
// Task A: 0.5초 간격 LED 점멸
if (current - prevLED >= 500) {
prevLED = current;
// LED 작업 수행
}
// Task B: 2초 간격 센서 측정
if (current - prevSensor >= 2000) {
prevSensor = current;
// 센서 작업 수행
}
}
4. 자주 묻는 질문 (Q&A)
Q. ISR 함수 안에서 왜 delay()를 사용할 수 없나요?
A. delay() 함수는 아두이노 내부 타이머 인터럽트를 기반으로 시간을 카운팅합니다. 하지만 ISR 함수가 동작하는 동안에는 다른 모든 인터럽트가 차단되므로 delay()의 시간 카운터가 동작하지 않아 무한 대기 상태(Deadlock)에 빠지게 됩니다.