아두이노 인터럽트

2022. 1. 9. 21:16아두이노

외부 입력 변화를 인지하고 그 처리를 수행하는 프로그램을 작성할 때 폴링(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;
}

 

 

 

 

 

 

 

 

 

 

 

 

타이머 인터럽트를 사용하면 좀 더 간단히 시간설정을 할 수 있고

하드웨어인터셉트를 이용하면 외부 변화에 대한 코딩을  좀 더 쉽게 할 수 있습니다.