이번 포스팅에서는 아두이노 타이머 인터럽트 사용방법을 다뤄보도록 하겠다.
이전 포스팅에서는 아두이노 입출력 인터럽트 사용방법을 다뤘으니, 참고하실 분들은 참고하시기 바란다.
인터럽트는 타이머/카운터 인터럽트와 입출력 인터럽트로 2개가 있다.
입출력 인터럽트는 센서 등의 신호 변화를 감지하여 발생하는 인터럽트라면,
타이머/카운터 인터럽트는 일정 시간마다 발생하는 인터럽트이다.
인터럽트를 모르는 상태로 1초마다 LED를 점등시켜야 한다면,
LOOP() 함수 안에서 delay(1000) 를 사용해서 한다고 하면 제일 초보적인 생각이다.
왜냐하면 delay(1000)을 쓴다고하면 처음에는 거의 정확하더라도, 계속 작동을 하다보면 오차가 누적되어서 전혀 달라지게 된다.
millis() 를 사용해서 오차를 보정해주면 되지 않냐고 하실 수 있다.
그런데 문제는 입출력 인터럽트를 이해했다면, 입출력 인터럽트가 실행되면 millis()는 멈추게 되므로 역시 오차가 누적된다.
따라서 어느 상황에서 1초마다 작동을 하려면 타이머/카운터 인터럽트를 사용해야 한다.
타이머/카운터 인터럽트 원리를 이해해야 왜 이런상황에 타이머 인터럽트를 사용해야 하는지 알 수 있을 것이다.
거의 대부분의 MCU에는 타이머기능을 위해 진동소자가 탑재되어 있다.
이 진동소자는 1초에 무수히 많은 진동을 규칙적으로 한다.
이 진동수를 감지하여 타이머 기능을 하게 되는 것이다.
원리를 이해하려면 복잡한 용어들을 이해해야한다.
이 용어들은 내부 레지스터와 관련이 있다.
최대한 쉽게 이해하도록 설명해보겠다.
타이머 인터럽트는 5가지 사용 방법이 있다.
이중에서 CTC 모드를 알아보도록 하자.
CTC Mode 라는 비교일치 인터럽트를 이용하여 주기적으로 특정 함수를 실행시킬 수 있다.
CTC Mode(Clear Timer on Compare Mode) 는 클럭 주기마다 TCNT(Timer Counter Register)의 값이 0부터 1씩 증가하고,
그 값이 OCR(Output Compare Register) 값 또는 ICR(Inturrupt Control Register)과 일치하면 0으로 초기화 된다.
그리고 다시 증가를 반복한다.
그러니깐 진동수를 감지해서 일정 진동수를 감지하면 TCNT 값을 증가시키고,
그 TCNT 값이 OCR 값과 일치할 때 인터럽트를 발생시키는 것이다.
우리가 제일 많이 다루는 아두이노 우노는 3가지 타이머/카운터를 가지고 있다.
8비트 타이머/카운터 2개, 16비트 타이머/카운터 1개 를 가지고 있는데, 각각 이름이 있다.
8비트 타이머/카운터 : Timer0, Timer2
16비트 타이머/카운터 : Timer1
쉽게 생각할 수 있는건 8비트 타이머/카운터 이므로, Timer0 을 가지고 설명을 더 이어가 보겠다.
Timer0은 앞서 말했듯이 8비트 카운터 타이머 이다.
8비트 값을 가지므로 0~255 값을 저장할 수 있다.
그런데 값이 256이 되면 데이터 크기를 초과하게 되는데,
이런 경우 0으로 저장하게 된다. 이걸 오버플로우(Overflow) 라고 한다.
아두이노 우노에 있는 오실레이터는 16MHz 주파수를 가지고 있다.
이 말의 뜻은 1초에 16,000,000 번 진동을 한다는 뜻이다.
그렇다면 1번 진동에 소요되는 시간은 과연 얼마일까?
계산을 해보면 63ns 시간이 소요된다.
Timer0이 카운트 할 수 있는 개수는 0부터 255까지인데 1진동에 1카운트를 하게되면 약 16us 만에 오버플로우가 발생하게 된다.
이렇게 되면 너무 빠른 순간에 오버플로우가 발생하게 되고, 아두이노 우노에서 처리하기에는 버거운 수치가 된다.
그래서 이 속도를 조절해서 사용해야 한다.
프리스케일러(Perscaler)를 이용하면 된다.
우리나라에서는 이걸 분주비라고 부른다.
시스템 클럭은 16MHz 이지만
분주비가 8 이라면, 2MHz 로 작동하게 된다.
그렇게 되면 오버플로우가 발생하기까지의 시간은 128us 이 소요된다.
[ 1/2,000,000 * 255 = 127.5us ]
분주비가 256 이라면, 62.5kHz 로 작동하게 되고,
그렇게 되면 오버플로우가 발생하기까지의 시간은 약 4ms 가 소요된다.
[ 1/62,500 * 255 = 4.08ms ]
분주비에 대해서는 여기까지 하도록 하고, 이제 OCR을 이해해보자.
앞서 말했듯이 OCR(Output Compare Register)은 비교일치 카운터 레지스터다.
이 값과 타이머 카운터(TCNT)가 같아지면 인터럽트를 발생시킨다.
1초마다 인터럽트를 발생시키는 방법을 살펴보자.
일단 분주비가 256 이라면, 타이머 클럭이 62.5kHz 로 작동하게 된다.
오버플로우가 발생하기까지의 시간은 약 4ms ,
TCNT 값이 255까지 되는데 걸리는 시간이 4ms 라는 뜻이다.
그렇다면 OCR 값이 최대값인 255 라면 TCNT 값이 OCR 값과 같아지는 시간 4ms ,
1초가 되려면 250번 반복을 해야 한다.
unsigned int count=0;
char toggle0=1;
void setup() {
pinMode(13, OUTPUT);
TCCR0A = 0; //TCCR0A initialize
TCCR0B = 0; //TCCR0B initialize
TCNT0 = 0; //TCNT0 initialize
OCR0A= 255;
TCCR0B |= (1<<WGM02);
TCCR0B |= (1<<CS02) | (0<<CS00);
TIMSK0 |= (1<<OCIE0A);
sei();
}
ISR(TIMER0_COMPA_vect){
count++;
if(count>250)
{
if (toggle0){
digitalWrite(13,HIGH);
toggle0 = 0;
}
else{
digitalWrite(13,LOW);
toggle0 = 1;
}
count=0;
TCNT0=0;
}
}
void loop() {
// put your main code here, to run repeatedly:
}
위의 코드를 그대로 아두이노에 업로드 시키면 1초마다 내장 LED가 켜졌다가 꺼지는 것을 확인할 수 있다.
설명을 해보자면
위의 명령으로 레지스터들을 0으로 초기화 한다.
OCR 값을 255로 해서 TCNT 가 255가 되면 타이머 인터럽트를 실행하게끔 한다.
TCCR0B 레지스터에서 WGM02 값에 true 값을 부여해서
CTC 모드의 사용을 선언한다. (아래 표 참고)
TCCR0B 레지스터에 CS02 와 CS00 에 각각 1과 0을 부여해서 분주비를 256로 설정한다. (아래 표 참고)
(사실 CS00 은 안 건드려도 되는 상황이다.)
sei() ; 를 통해서 인터럽트를 사용함을 선언한다.
타이머 인터럽트가 실행되면 count 변수 값을 1씩 증가 시킨다.
(4ms 마다 인터럽트가 실행됨 == 4ms 마다 count가 1씩 증가함)
만약 count가 250보다 커지게 되면 LED를 제어한다.
(4ms * 250 = 1sec 이므로, 1초마다 LED를 제어하게 됨)
LED 제어를 했으면 TCNT0 값과 count 값을 다시 0으로 초기화 시켜준다.
위의 방법을 응용하면 3초마다 작동을 제어할 수도 있다.
count 변수를 사용함으로써 유동적으로 시간 제어를 하는 것이다.
만약 분주비를 1024로 설정한 경우 3초마다 LED를 켜고 끄는 것을 제어하려면 어떻게 해야 할까?
분주비를 우선 1024로 설정하면, 15.625kHz 로 작동하게 된다.
오버플로우가 발생하기까지 걸리는 시간은 16.32ms 이 된다.
[ 1/15625 * 255 = 0.01632 sec ]
OCR 값이 255일 경우 16.32ms 마다 타이머 인터럽트가 발생하게 된다.
3초가 되려면 183.82 번을 카운트하면 되므로 count 변수가 183보다 커지는 순간에 LED 제어를 하면 되는 것이다.
정확하게 3초는 아니더라도 근사하게 맞추는데에 초점을 둬야 한다.
CTC 모드말고도 다른 모드가 많지만, 사실 한가지만 제대로 사용할 줄 안다면 굳이 필요할 내용은 아니다.
그래도 차근차근 다른 타이머 인터럽트 모드들도 다뤄보도록 하겠다.
이번 포스트는 타이머 인터럽트에 대해서 개념을 잡아가는 시간이었다고 보면 좋겠다.
<도움이 될만한 포스트>
m.blog.naver.com/102mania/221550456403
'코딩 > 아두이노' 카테고리의 다른 글
아두이노 nRF24L01 모듈로 무선통신 하는 방법 (1) | 2021.03.13 |
---|---|
모터드라이버 L298N 5V로 모터 제어하기 (13) | 2021.03.11 |
아두이노 버튼 노이즈를 제거해보자, Debounce 이해하기 (0) | 2020.12.23 |
아두이노 입출력 인터럽트에 대해서 알아보자 attachInterrupt() (2) | 2020.12.23 |
아두이노와 ESP8266으로 wifi에 연결해서 웹서버 만드고, 센서값 출력하기 (24) | 2020.10.23 |
댓글