2009년 6월 2일 화요일

아뒤노에서 타이머 인터럽트 (Timer interrupt in Arduino)



아뒤노가 임베디드 환경을 처음 접하는 사람들에게 매우 쉬운 환경임에 틀림없지만 조금만 복잡한 작업을 하려고만 하면 제약사항이 꽤 많아진다.
특히 가장 아쉬운게 attachInterrupt()를 통해서 외부 인터럽트 2개(Arduino Mega에서는 6개로 늘어났다)만 사용할 수 있다는 점이다.

단순하게 센서값을 읽어서 그 값에 따라 LED나 모터를 구동하는건 별 문제가 없지만 동시에 여러개의 입력을 기다리면서 그와 별도로 작업을 처리하거나 해야 한다면 타이머 인터럽트는 거의 필수적이 되어 버린다.

물론 atmega의 타이머 관련 레지스터(TCCR2, ASSR 등등)를 직접 제어하면 인터럽트를 사용할 수 있지만 이 경우 아뒤노의 장점(?)이 사라져 버린다.
그래서 아뒤노 라이브러리 섹션의 contributed libraries (아뒤노 사용자들이 만든 라이브러리들)에 보면 MsTimer2가 있다. Atmega의 timer 2를 사용해서 ms 단위의 해상도로 타이머 인터럽트를 걸어줄 수 있다.
현재 Atmega 1280, 328, 48/88/168, 128/8을 사용한 arduino 및 arduino clone에서 사용할 수 있다.

이 라이브러리를 사용하려면 MsTimer2를 다운받아 압축을 해제한 다음 만들어 진 폴더를 아뒤노가 설치된 폴더 내의 hardware/libraries 에 복사해 주면 그것으로 설치가 끝난다. 그리고 스케치북에 #include <MsTimer2.h> 를 넣어주면 된다.

set, start, stop 이렇게 단 3개의 메쏘드만 있어 사용법은 매우 쉽다.
먼저 MsTimer2::set(unsigned long ms, void (*f)()) 을 사용해서 타이머를 설정해 준다. 이 함수에는 두개의 파라메터가 필요하다. 첫번째는 타이머 시간(ms 단위, 즉 여기에 1000을 넣어주면 1초마다 한번씩 타이머 인터럽트가 발생한다.), 두번째는 인터럽트 서비스 루틴(인터럽트가 발생했을 때 호출할 함수) 이름이다.
set()을 이용해서 타이머를 설정했으면 MsTimer2::start() 를 호출해주면 타이머가 동작하기 시작해 정해진 시간마다 인터럽트 서비스 루틴이 실행된다.
타이머 동작을 멈추려면 MsTimer2::stop()을 호출해주면 된다.

타이머 인터럽트가 왜 유용한가는 아래의 예제를 보면 잘 알수있다.
0.5초마다 LED를 깜빡이게 하는 프로그램이다.

boolean output = HIGH;
void setup() {
  pinMode(13, OUTPUT);
}

void loop() {
digitalWrite(13, output);
output = !output;
delay(500);
}
---------------------------------------
// Toggle LED on pin 13 each second
#include <MsTimer2.h>

void flash() {
static boolean output = HIGH;

digitalWrite(13, output);
output = !output;
}

void setup() {
pinMode(13, OUTPUT);

MsTimer2::set(500, flash); // 500ms period
MsTimer2::start();
}

void loop() {
}

둘 다 같은 동작을 한다. 그런데 위의 경우와 같이 타이머 인터럽트를 사용하지 않은 경우 0.5초동안 다른 일을 하지 못하고 단지 기다려야 한다. 그에 비해 아래쪽 프로그램의 경우는 LED를 토글하는걸 인터럽트 서비스 루틴(여기서는 flash())에서 처리해주기 때문에 따로 0.5초를 기다릴 필요가 없이 그 동안 다른 일을 할 수 있게 된다. (loop() 함수에서 아무일도 하지 않는걸 확인할 수 있다. 여기에 다른 작업을 넣어주면 된다.)

예를 들어 동작중에 시리얼 포트를 통해 시간을 입력받아 LED가 깜빡이는 간격을 조정하도록 만들려면 상당히 힘들어 지지만 아래쪽 프로그램의 경우 시리얼 포트를 통해 데이터를 입력받는 부분은 loop() 안에서 처리하게 하고 그 입력받은 값에 따라 타이머 설정만 바꿔주면 실제 깜빡이는건 타이머 인터럽트가 담당해 주기 때문에 매우 간단해진다.
 

댓글 27개:

  1. 님 그런데 저는 왜 안될까요? 저대로 했는데
    sketch_jun18a:38: error: 'MsTimer2' has not been declared 라고 나옵니다.

    답글삭제
    답글
    1. MsTimer2를 다운받아 설치해 주지 않아서 그런겁니다.

      삭제
  2. MsTimer2를 다운 받아 설치 했는데도 'MsTimer2' has not been declared 라고 나오는 건 왜 인가요?

    답글삭제
    답글
    1. 어떤 식으로 라이브러리를 설치하셨나요? 저 에러가 계속 나온다면 라이브러리가 제대로 설치되지 않은거 같습니다.

      삭제
    2. 헤더 선언 안하신건 아닌지요?

      삭제
  3. 타이머를 여러개 설정해서 코딩할 수 있나요? 예륻들어 1000ms, 500ms, 100ms 같이 여러개의 간격으로요

    답글삭제
    답글
    1. MsTimer2 라이브러리는 하드웨어 타이머를 하나만 사용합니다. 그러니 소프트웨어적으로 처리하면 됩니다. 즉 위와 같이 1000, 500, 100ms가 필요하다면 100ms마다 타이머 인터럽트가 걸리게 하고 서비스루틴 안에서 처리해야겠죠.

      void time_isr() {
      static cnt = 0;

      cnt++;

      // code for 100ms
      printf("every 100ms\n");

      if ((cnt%5) == 0) { // code for 500ms
      printf("every 500ms\n");
      }

      if ((cnt%10) == 0) { // code for 1000ms
      printf("every 1000ms\n");
      }
      }

      삭제
    2. 이렇게 짜면 1000ms일때 1000ms의 응답만 구현만 되지 않나요?
      1000ms일때 1000ms,500ms,100ms의 3개의 응답을 동시에 원한다면 어떻게 해야되나요?
      단순히 setup에서
      MsTimer2::set(100, 100ms);
      MsTimer2::set(500, 500ms);
      이런식으로 해도 되는걸까요?

      삭제
    3. 위에 코드를 잘 읽어보시면 'every 100ms'는 100ms마다 출력되고, 'every 500ms'는 500ms마다, 'every 1000ms'는 1000ms마다 출력됩니다. 즉 1초가 되면 every 100ms, every 500ms, every 1000ms이 다 출력됩니다.

      삭제
  4. 아두이노로 시계만들려고 하는데요.
    세그먼트에 출력은 됩니다. 00-00-00 으로요
    근데 타이머인터럽트이용하면 동작을 안하네요.
    delay() 함수 사용시에는 되는데..ㅠㅠ

    #include "LedControl.h"
    #include "MsTimer2.h"
    // Arduino Pin 7 to DIN, 6 to Clk, 5 to LOAD, no.of devices is 1
    LedControl lc=LedControl(7,6,5,1);


    int sec1=0;
    int sec10=0;
    int min1=0;
    int min10=0;
    int hour1=0;
    int hour10=0;


    void sec1UP(){
    sec1++;
    if(sec1==10)
    sec10UP();
    }
    void sec10UP(){
    sec1=0;
    sec10++;
    if(sec10==6)
    min1UP();
    }

    void min1UP(){
    sec1=0;
    sec10=0;
    min1++;
    if(min1==10)
    min10UP();
    }
    void min10UP(){
    min1=0;
    min10++;
    if(min10==6)
    hour1UP();
    }

    void hour1UP(){
    min1=0;
    min10=0;
    hour1++;
    if(hour1==10)
    hour10UP();

    if(hour10==2){
    if(hour1=4){
    hour1=0;
    hour10=0;
    }
    }
    }
    void hour10UP(){
    hour1=0;
    hour10++;

    }

    void setup() {
    // Initialize the MAX7219 device
    lc.shutdown(0,false); // Enable display
    lc.setIntensity(0,5); // Set brightness level (0 is min, 15 is max)
    lc.clearDisplay(0); // Clear display register
    MsTimer2::set(1000,sec1UP);
    MsTimer2::start();

    }

    void loop()
    {

    lc.setDigit(0,0,sec1,false);
    lc.setDigit(0,1,sec10,false);
    lc.setChar(0,2,'-', false);
    lc.setDigit(0,3,min1,false);
    lc.setDigit(0,4,min10,false);
    lc.setChar(0,5, '-', false);
    lc.setDigit(0,6,hour1,false);
    lc.setDigit(0,7,hour10,false);

    }

    답글삭제
    답글
    1. volatile int sec1=0;
      volatile int sec10=0;
      volatile int min1=0;
      volatile int min10=0;
      volatile int hour1=0;
      volatile int hour10=0;

      변수 선언 부분을 이렇게 바꿔주면 됩니다.

      삭제
    2. 감사합니다. 음.. 휘발성의 정수형인가요? 처음 접하는 선언이네요. 일반 int와 다른점이 있나요?

      삭제
  5. 모터 드라이브 2채널 ON 시키면서 동시에 릴레이에 HIGH를 주려고 하는데
    이렇게 사용하면 되는건가요??
    setup 함수는 생략했습니다.
    MDriver.driveCh0Ch1(-250, 200); 이건 pwm 만큼 모터 ON 시키는 메소드 실행입니다.

    void Relay_ON(){
    static boolean output = HIGH;

    digitalWrite(Relay_pin,output);
    output = !output;
    }
    void loop{}{
    if(digitalRead(Limit)==LOW) {
    MsTimer2::set(100, Relay_ON);
    MsTimer2::start();
    MDriver.driveCh0Ch1(-250, 200); //모터 드라이브 ch0,ch1 동작
    }
    }

    답글삭제
  6. 모터 드라이브 ch0,ch1 동작하는동안에 릴레이에 HIGH전압을 주고 싶은데 이렇게 사용하면 되나요?? 꼭 위의 예제처럼 setup 함수에 넣어서 사용해야 하나요??
    setup 생략, MDriver.driveCh0Ch1(-250, 200); 모터 ch0,ch1 pwm-250,200 ON

    int Limit_Switch = 30;

    void Relay_ON(){
    static boolean output = HIGH;

    digitalWrite(Relay_pin,output);
    output = !output;
    }

    void loop(){
    if(digitalRead(Limit_Switch)==LOW){
    MsTimer2::set(100,Relay_ON);
    MsTimer2::start();
    MDriver.driveCh0Ch1(200, 200);
    }
    else MDriver.driveCh0Ch1(0, 0);
    }

    답글삭제
  7. 모터 드라이브 ch0,ch1 동작하는동안에 릴레이에 HIGH전압을 주고 싶은데 이렇게 사용하면 되나요?? 꼭 위의 예제처럼 setup 함수에 넣어서 사용해야 하나요??
    setup 생략, MDriver.driveCh0Ch1(-250, 200); 모터 ch0,ch1 pwm-250,200 ON

    int Limit_Switch = 30;

    void Relay_ON(){
    static boolean output = HIGH;

    digitalWrite(Relay_pin,output);
    output = !output;
    }

    void loop(){
    if(digitalRead(Limit_Switch)==LOW){
    MsTimer2::set(100,Relay_ON);
    MsTimer2::start();
    MDriver.driveCh0Ch1(200, 200);
    }
    else MDriver.driveCh0Ch1(0, 0);
    }

    답글삭제
    답글
    1. 질문을 정확하게 이해하기 힘들지만 기본적으로 MsTimer2::set() 메소드는 setup에 넣는다고 생각하셔야 합니다. 물론 필요하다면 loop내에서 어떤 조건에 따라 호출될 함수를 바꾸거나 주기를 바꾸기 위해 MsTimer2::set() 메소드를 호출할 수 있기도 합니다. 그리고 타이머는 일단 MsTimer2::start()로 시작시켜 놓으면 정해진 시간마다 자동으로 start()에서 지정해 놓은 함수를 호출하게 됩니다. 물론 그 동안 loop()에서 어떤 다른 동작을 수행해도 상관 없습니다. (여기서라면 MDrive.driveCh0Ch1() 함수 같은걸 호출하는게 되겠죠)

      삭제
    2. hc06(페어링 되기전:led깜빡거림(high low상태 반복),,, 페어링 된후 연결상태: led high,,계속 켜져있음)
      그런데 이게 페어링 되기전에 led를 깜빡이는것 대신 끄게 할수있을까요?
      일단 led를 mcu에 달았습니다.
      그리고 hc06은 102ms이고(깜빡일때), 위의 라이브러리를 사용해서 1ms의 인터럽트를 150번 이상 high 되게 주는데 계속 깜빡거립니다만... 도저히 왜 인식을 못하는지 모르겠습니다.
      페어링 기능은 정상작동됩니다.혹시 어디가 문제인지 좀 봐주실 수 있나요?
      #include


      #include
      SoftwareSerial BTSerial(2,3);
      int led =13;
      int led2 = 12;
      int _100ms_FG = LOW;
      int _100ms_CNT = 0;
      void TimerISR()
      {
      _100ms_CNT++;
      if(_100ms_CNT>=160)
      {
      _100ms_CNT = 0;
      _100ms_FG = HIGH;
      }
      }
      void setup()
      {
      pinMode(led,INPUT);
      pinMode(led2,OUTPUT);
      MsTimer2::set(1,TimerISR);
      MsTimer2::start();
      Serial.begin(9600);
      BTSerial.begin(9600);
      }

      void loop()
      {
      static boolean output = HIGH;

      if(_100ms_FG == HIGH)
      {
      _100ms_FG == LOW;
      digitalWrite(led2,output);
      output = !output;


      }
      }




      삭제
    3. 회로를 어떻게 연결했는지 모르겠지만 위의 코드만 봐서는 그냥 160ms 간격으로 12번 핀에 연결되어 있는 LED를 토글하는거 이외에 다른 기능은 없군요.

      삭제
    4. 블루투스를 키면 불이 들어와 있고, 끄면 불이 꺼지게 해야 하는데,

      아래 코드를 실행해보니 블루투스를 꺼도 불이 계속 들어오는데 어느 부분이 잘못됐는지 좀 봐주실수 있나요? 부탁드립니다.

      #include

      SoftwareSerial BTSerial(2,3);
      int motor = 8;

      void setup()
      {
      Serial.begin(9600);
      pinMode(motor,OUTPUT);
      BTSerial.begin(9600);
      }
      char a;
      void loop()
      {
      digitalWrite(8,HIGH);
      BTSerial.println("0");
      delay(500);

      if(BTSerial.available())
      {
      a=BTSerial.read();
      if(a!='0')
      {
      digitalWrite(8,LOW);
      delay(1000);
      }}}
      코드를 다시 한번 짜봤습니다만 혹시 봐주실수 있는지요..

      삭제
  8. 블로그 관리자가 댓글을 삭제했습니다.

    답글삭제
  9. 작성자가 댓글을 삭제했습니다.

    답글삭제
  10. 작성자가 댓글을 삭제했습니다.

    답글삭제
  11. Ms타이머를 사용해서 led5개를 1 2 3 4 5 순차적으로불들어오게할려면어떻게해야하나요

    답글삭제
  12. #include

    int h,m,s;
    void flash(){
    s++;
    if(s>=60){
    m++;s=0;
    if (m>=60){
    h++,m=0;
    if(h>24) h=0;
    }
    }
    Serial.print(h);Serial.print(":");
    Serial.print(m);Serial.print(":");
    Serial.println(s);
    }

    void setup() {
    Serial.begin(9600);
    MsTimer2::set(2, flash); // flash함수를 500ms마다 호출한다
    MsTimer2::start();
    }
    void loop(){
    }

    답글삭제
  13. 혹시 타이머 인터럽트를 이용해
    IGBT DRIVER인 IR2110을 구동시킬 방법을 혹시 아시려는지요ㅠㅠ

    답글삭제
  14. 좋은정보 감사드립니다 !!

    답글삭제