외부 입력 변화를 인지하고 그 처리를 수행하는 프로그램을 작성할 때 폴링(polling)방식 또는 인터럽트(interrupt)방식으로 작성
폴링 vs 인터럽트
폴링 : 상사 눈치보는중. 10초마다 상사가 뭐하나 확인.
인터럽트 : 상사의자에 센서장착. 상사가 일어나면 알 수 있음
※인터럽트가 더 좋아보이지만 센서가 비쌀 경우? 상사가 시도 때도 없이 왔다갔다하는 경우?
지금까지 사용한 digitalRead(), ananlogRead() 등의 함수는 폴링방식이었습니다.
이제 외부의 변화를 감지하면 그때만 실행되는 인터럽트에 대해 알아봅시다.
인터럽트
프로그램 실행 중 하드웨어 등이 장치나 내 외부의 어떤 변화에 의한 예외상황이 발생해,
그에 대한 처리가 필요한 경우 기존 프로그램을 일시 정지 후 변화에 대응하는 다른 프로그램(인터럽트 서비스 루틴,ISR)이 처리하는 것
인터럽트를 발생시키는 사건은 언제 발생할지 모르는 불확정적 상황이기때문에
비동기적으로 발생되므로 인터럽트는 어떤 중요한 작업이 백그라운드에서 수행될 수 있도록 함
인터럽트 동작 순서
ISR (인터럽트 서비스 루틴)
짧고 빠르게 처리해야함.
ISR에서 변경되는 변수는 반드시 volatile(최적화 제외 변수, 항상 메모리에 접근가능)형식으로 선언
인터럽트서비스루틴에서는 delay()를 쓸 수 없다.
하드웨어 인터럽트 (외부 인터럽트)
특정 I/O 핀에 입력상태 변화가 있을 때 이를 감지하고 발생시키는 것
-> 외부 입력에 대한 인터럽트
아두이노 우노 하드웨어 인터럽트
인터럽트 ID번호 | 핀번호 |
0 | 2 |
1 | 3 |
아두이노 우노 보드에서 2번, 3번 핀에서 인터럽트 처리가 가능합니다.
하지만 코드의 pin 부분에는 2,3 이 아닌 0,1로 입력해주어야 합니다.
근데 복잡하게 생각안하고 digitalPinToInterrupt(pin)번호를 쓰면 된다.
인터럽트 함수
attachInterrupt()
attachInterrupt()에 같은핀을 사용해서 다시 작성하면 기존에 있던 attachInterrupt()대신 사용.
attachInterrupt(0,blink,CHANGE) ;
detachInterrupt()
지정된 인터럽트를 해제하는 함수
void detachInterrupt(digitalPinToInterrupt(pin))
실습 : 13핀에 led, 2번핀에 스위치를 달아 예제코드를 따라해보세요. 인터럽트 발생 모드를 바꿔가면서 실행해보세요.
버튼에 연결된 2번핀의 상태가 변함에 따라 interrupt()함수가 실행됩니다.
(날 화나게 했던 문제는....)
int pin = 13;
int interruptPin=2;
volatile int state = LOW;
void setup(){
pinMode(pin, OUTPUT);
attachInterrupt(digitalPinToInterrupt(interruptPin), blink, CHANGE);
//2번핀의 상태가 CHANGE할 때마다 blink함수 실행
}
void loop(){
digitalWrite(pin, state);
}
void blink(){
state = !state;
}
문제 : 하드웨어 인터럽트를 이용해 초음파센서 거리를 측정하시오.
hint : trigPin이 HIGH, LOW를 하고나면 몇초뒤에 echoPin이 HIGH가 됩니다. 이걸 interrupt로 체크하세요.
(echo_pin 을 interrupt로 할려면 interrupt핀이 되는 2,3번에 꽂아야합니다. )
초음파센서 핀
VCC | 5V |
Trigger | 펄스 입력(디지털핀) |
Echo | 펄스 출력(디지털핀) |
GND | GND |
int trig_pin = 7; // trig 핀을 아두이노의 7번핀에 연결
int echo_pin = 2; // echo 핀을 아두이노의 6번핀에 연결 // 인터룹트는
unsigned long time; //현재시간을 저장하는 변수
int goUp=1; // 상승에지
int goDown=0; //하강에지
volatile int interruptMode=goUp; // 맨 처음에는 echo_phin이 LOW->HIGH로 바뀌는거 감지해야됨
volatile boolean timeMeasure=false; // 시간측정이 완료되었는지 여부
volatile unsigned long startT, endT ; //echo_phin 상승시간과 하강시간 저장 변수
void setup() {
Serial.begin(9600); // 시리얼 통신 시작
pinMode(trig_pin, OUTPUT); // trig_pin은 아두이노의 신호를 받으므로 출력모드로 설정
pinMode(echo_pin, INPUT); // echo_pin은 아두이노에 신호를 주므로 입력모드, 인데 사실 interrupt걸면 필요없는 코드
attachInterrupt(digitalPinToInterrupt(echo_pin),soundDetection,RISING);
}
void loop() {
if(millis()-time>2000){ //2초 이상 이면
time=millis() ; //현재 시간을 이전시간에 저장
attachInterrupt(digitalPinToInterrupt(echo_pin),soundDetection,RISING); // 하강에지로 설정되어있을 경우 다시 상승에지로 바꿔주기
digitalWrite(trig_pin,LOW); // 일단 LOW, 좀있다 HIGH로 바꿔줘서 펄스 발생
delayMicroseconds(100);
digitalWrite(trig_pin,HIGH); // 펄스 발생 100us 동안
delayMicroseconds(100);
digitalWrite(trig_pin,LOW); // 펄스 발생시켰으면 쉬어야지
}
if(timeMeasure){ // 시간측정이 완료되었다면
unsigned long duration=endT-startT; //echo_pin이 발생한 HIGH였던 시간 측정 (echo_pin)은 거리가 길수록 HIGH를 오랫동안 유지
float distance = ((float)(340 * duration) / 10000) / 2;
Serial.print("거리 : " );
Serial.print(distance);
Serial.println("cm");
timeMeasure=false; //다음 번 측정을 위해 초기화
}
}
//change가 아닌 상승에지, 하강에지에 대한 정확한 시간측정을 위해 RISING,FALLING 사용
void soundDetection(){
if(interruptMode==goUp){ //상승에지를 감지 했으면
interruptMode=goDown; //이번엔 하강에지를 감지해보자
startT=micros(); //인터룹트함수에서 millis사용 불가, 시작시간 저장
attachInterrupt(digitalPinToInterrupt(echo_pin),soundDetection,FALLING); //다음에는 하강에지를 감지해보자
}else if(interruptMode==goDown){ //하강에지를 감지했으면
interruptMode=goUp; //이번엔 하강에지를 감지해보자
endT =micros(); //인터룹트함수에서 millis사용 불가, // 끝나는 시간 저장
timeMeasure=true; //하강에지로 왔다는건 시간 측정 끝
attachInterrupt(digitalPinToInterrupt(echo_pin),soundDetection,RISING); //다음에는 상승에지를 감지해보자
}
}
타이머 인터럽트
delay()함수는 프로그램이 동작을 멈추지만 타이머는 따로 카운터 로직이 동작하여 프로그램에 영향을 주지않고도
원하는 시간에 원하는 동작을 할 수 있게 해줌.
Mstimer2 검색해서 관련 라이브러리 다운
MsTimer2 라이브러리 함수
MsTimer2::set(unsigned long ms, void (*f)()) 타이머 인터럽트 발생 시간 설정 , 실행 함수 설정
MsTimer::start() 타이머 인터럽트 활성화
MsTimer3::stop() 타이머 인터럽트 비활성화
https://playground.arduino.cc/Main/MsTimer2/ 참고
실습: 타이머 인터럽트를 사용해 led가 깜빡이게 하는 예제.
#include <MsTimer2.h>
const int ledPin=13;
volatile boolean state=LOW;
void setup() {
pinMode(ledPin,OUTPUT);
MsTimer2::set(500,ledBlink); //0.5초 마다 ledBlink함수 실행하겠다
MsTimer2::start();
}
void loop() {
}
void ledBlink(){ //MsTimer2가 프로그램 전체 실행에는 영향을 안 끼치지만
//이 함수가 실행되는동안은 프로그램이 멈춤
digitalWrite(ledPin,state);
state = !state;
}
문제 : 타이머 인터럽트와 하드웨어인터셉트를 사용해서 2초마다 초음파센서 거리를 측정하시오.
#include <MsTimer2.h>
int trig_pin = 7; // trig 핀을 아두이노의 7번핀에 연결
int echo_pin = 2; // echo 핀을 아두이노의 6번핀에 연결 // 인터룹트는
volatile boolean state=LOW;
int goUp=1; // 상승에지
int goDown=0; //하강에지
volatile int interruptMode=goUp; // 맨 처음에는 echo_phin이 LOW->HIGH로 바뀌는거 감지해야됨
volatile boolean timeMeasure=false; // 시간측정이 완료되었는지 여부
volatile unsigned long startT, endT ; //echo_phin 상승시간과 하강시간 저장 변수
boolean timeLapse=false; //2초 지났나 확인하는 변수
void setup() {
Serial.begin(9600); // 시리얼 통신 시작
pinMode(trig_pin, OUTPUT); // trig_pin은 아두이노의 신호를 받으므로 출력모드로 설정
pinMode(echo_pin, INPUT); // echo_pin은 아두이노에 신호를 주므로 입력모드, 인데 사실 interrupt걸면 필요없는 코드
attachInterrupt(digitalPinToInterrupt(echo_pin),soundDetection,RISING);
MsTimer2::set(2000,timeDetection);
MsTimer2::start();
}
void loop() {
if(timeLapse){ //2초 이상 이면
timeLapse=false;
attachInterrupt(digitalPinToInterrupt(echo_pin),soundDetection,RISING); // 하강에지로 설정되어있을 경우 다시 상승에지로 바꿔주기
digitalWrite(trig_pin,LOW); // 일단 LOW, 좀있다 HIGH로 바꿔줘서 펄스 발생
delayMicroseconds(100);
digitalWrite(trig_pin,HIGH); // 펄스 발생 100us 동안
delayMicroseconds(100);
digitalWrite(trig_pin,LOW); // 펄스 발생시켰으면 쉬어야지
}
if(timeMeasure){ // 시간측정이 완료되었다면
unsigned long duration=endT-startT; //echo_pin이 발생한 HIGH였던 시간 측정 (echo_pin)은 거리가 길수록 HIGH를 오랫동안 유지
float distance = ((float)(340 * duration) / 10000) / 2;
Serial.print("거리 : " );
Serial.print(distance);
Serial.println("cm");
timeMeasure=false; //다음 번 측정을 위해 초기화
}
}
//change가 아닌 상승에지, 하강에지에 대한 정확한 시간측정을 위해 RISING,FALLING 사용
void soundDetection(){
if(interruptMode==goUp){ //상승에지를 감지 했으면
interruptMode=goDown; //이번엔 하강에지를 감지해보자
startT=micros(); //인터룹트함수에서 millis사용 불가, 시작시간 저장
attachInterrupt(digitalPinToInterrupt(echo_pin),soundDetection,FALLING); //다음에는 하강에지를 감지해보자
}else if(interruptMode==goDown){ //하강에지를 감지했으면
interruptMode=goUp; //이번엔 하강에지를 감지해보자
endT =micros(); //인터룹트함수에서 millis사용 불가, // 끝나는 시간 저장
timeMeasure=true; //하강에지로 왔다는건 시간 측정 끝
attachInterrupt(digitalPinToInterrupt(echo_pin),soundDetection,RISING); //다음에는 상승에지를 감지해보자
}
}
void timeDetection(){
timeLapse=true;
}
타이머 인터럽트를 사용하면 좀 더 간단히 시간설정을 할 수 있고
하드웨어인터셉트를 이용하면 외부 변화에 대한 코딩을 좀 더 쉽게 할 수 있습니다.