< 일정한 시간에 인터럽트를 거는 2가지 방법 >
* TCNTx 레지스터
- 클럭을 카운터한 값을 가지고 있는 레지스터
- 16Mhz의 시스템 클럭을 쓰는 시스템에서는 TCNT0의 값은 1/16000000hz = 62.5ns마다 1씩 증가한다.(분주비 1일 경우)
* 노멀모드
- 8bit 타이머의 경우 0부터 255까지 계속 증가하고 255를 넘으면 다시 0부터 증가를 반복한다.
- 타이머 오버플로우 인터럽트를 허용해 놓으면 255에서 0으로 갈 때 인터럽트가 걸리게 된다.
- 원하는 시간간격으로 인터럽트를 주기 위해 인터럽트 서비스 루틴 첫부분에 TCNT값을 쓰면 된다.
- 예를 들어, main 클럭이 16Mhz이고 분주비가 256이면 62500hz가 되고 주기는 16us인데 x256(8bit)하면 4.09ms마다 인터럽트가 걸리게 된다. 그런데 2ms마다 인터럽트를 걸고 싶으면 인터럽트가 걸리자마자 TCNT의 값을 130으로 적어준다. 그럼 TCNT의 값은 130 ~ 255까지의 계수동작을 반복하고 2ms마다 인터럽트가 걸리게 된다.
- 수학 공식으로 보기
- 1 / 16000000 = 0.0000000625 = 62.5ns
- 256분주일 때, 1 / 16000000 * 256 = 0.000016 = 16us
- 8bit 타이머 오버플로우 인터럽트 허용시(TCNT0은 0부터 255까지(256개) 반복)
- 16us * 256 = 4.096ms
- TCNT0을 130부터 255까지 반복하게끔 했을 때
- 16us * ( 255 - 130 ) = 2ms
- TCNT0을 사용해서 원하는 시간 만드는 공식
- TCNT0 = (타이머 TOP값(8bit 타이머이므로 2^8 = 256) - 1) - ( (원하는 시간) * (클럭 / 분주비) )
- 2ms를 만들 때, TCNT0 = (256 - 1) - ( 0.002 * (16000000 / 256) ) = 130
< Normal mode 사용법 >
* 일반모드로 1초 만들기
#include <avr/io.h>
#include <avr/interrupt.h>
unsigned int count = 0;
/* Timer/Count 0 Overflow 인터럽트 2ms마다 인터럽트가 걸리므로 500번 인터럽트가 거리면 1초임 */
SIGNAL(SIG_OVERFLOW0) // 0.002 * 500 = 1s
{
TCNT0 = 0x82; // 130
if( ++count == 500 ){ // 1초마다 LED 점멸, 1초 켜짐, 1초 꺼짐
PORTB = ~PINB;
count = 0;
}
}
int main(void)
{
DDRB = 0xFF;
PRTB = 0xFF;
// TCNT0 = (256 - 1) - ( 0.002s * (16000000 / 256) ) = 130
TCCR0 = 0x06; // 16Mhz / 256 = 62.5Khz ==> 16us
TCNT0 = 0x82; // (256 - 125) = 130 ==> 0x82
TIMSK = 0x01; // Timer/Count Interrupt Mask Register => 0x01 : overflow enable
sei();
while(1) ;
}
#include <avr/interrupt.h>
unsigned int count = 0;
/* Timer/Count 0 Overflow 인터럽트 2ms마다 인터럽트가 걸리므로 500번 인터럽트가 거리면 1초임 */
SIGNAL(SIG_OVERFLOW0) // 0.002 * 500 = 1s
{
TCNT0 = 0x82; // 130
if( ++count == 500 ){ // 1초마다 LED 점멸, 1초 켜짐, 1초 꺼짐
PORTB = ~PINB;
count = 0;
}
}
int main(void)
{
DDRB = 0xFF;
PRTB = 0xFF;
// TCNT0 = (256 - 1) - ( 0.002s * (16000000 / 256) ) = 130
TCCR0 = 0x06; // 16Mhz / 256 = 62.5Khz ==> 16us
TCNT0 = 0x82; // (256 - 125) = 130 ==> 0x82
TIMSK = 0x01; // Timer/Count Interrupt Mask Register => 0x01 : overflow enable
sei();
while(1) ;
}
* CTC 모드
- TCNT0의 값이 0부터 증가하다가 OCR0에 써준 값과 일치하면 다음 클럭에서 인터럽트가 걸리면서 TCNT의 값이 0으로 초기화된다.
- 2ms마다 인터럽트가 걸리도록 하기
- 1 / 16000000 = 0.0000000625 = 62.5ns
- 256분주일 때, 1 / 16000000 * 256 = 0.000016 = 16us
- 8bit 타이머 오버플로우 인터럽트 허용시 (TCNT0은 0부터 255까지 반복)
- 16us * 256 = 4.096ms
- TCNT0을 OCR0 = 124와 비교해서 TCNT0이 124가 되면 다음 클럭에서 인터럽트를 발생
- 16us * (124 + 1) = 2ms
- 공식 정리
- OCR0 = ( (원하는 시간) * (클럭 / 분주비) ) - 1
- OCR0 = ( 0.002 * (16000000 / 256) - 1 = 124
< CTC 모드 사용법 >
* 소스 구성
인터럽트 헤더파일
인터럽트 서비스 루틴
{
인터럽트 걸렸을 때 동작
}
main()
{
인터럽트 설정
while(1){
메인 동작
}
}
인터럽트 서비스 루틴
{
인터럽트 걸렸을 때 동작
}
main()
{
인터럽트 설정
while(1){
메인 동작
}
}
* 타이머를 CTC 모드 0.5초 인터럽트를 만들기
#include <avr/io.h>
#include <avr/interrupt.h>
ISR(SIG_OUTPUT_COMPARE1A)
{
PORTA ^= 0xFF;
}
int main(void)
{
DDRA = 0xFF; // PORTB 출력 모드
TCCR1A = 0x00;
TCCR1B = 0x0C; // CTC 모드, 256 분주
TCCR1C = 0x00;
OCR1AH = (31249 >> 8); // 16Mhz / 256 / (1 + 31249) = 2hz = 0.5초
OCR1AL = 31249 & 0xFF;
TIMSK = 0x10; // 타이머1 출력 비교 인터럽트 A 허용
ETIMSK = 0x00;
TIFR = 0x00; // 플래그 클리어
ETIRF = 0x00;
sei(); // 글로벌 인터럽트 허용
while(1) ;
}
#include <avr/interrupt.h>
ISR(SIG_OUTPUT_COMPARE1A)
{
PORTA ^= 0xFF;
}
int main(void)
{
DDRA = 0xFF; // PORTB 출력 모드
TCCR1A = 0x00;
TCCR1B = 0x0C; // CTC 모드, 256 분주
TCCR1C = 0x00;
OCR1AH = (31249 >> 8); // 16Mhz / 256 / (1 + 31249) = 2hz = 0.5초
OCR1AL = 31249 & 0xFF;
TIMSK = 0x10; // 타이머1 출력 비교 인터럽트 A 허용
ETIMSK = 0x00;
TIFR = 0x00; // 플래그 클리어
ETIRF = 0x00;
sei(); // 글로벌 인터럽트 허용
while(1) ;
}
* 주기 = F (F : 시스템 클럭) 16000000
--------------- ---------------- = 2hz
N x ( 1 + OCR ) (N : 분주비) 256 x (1 + 31249)
* 시간 = 1 / 주기 = 1 / 2 = 0.5초
* CTC 모드에서는 TCNT와 값이 일치하면 다음 클럭에서 인터럽트가 발생하기 때문에 31250이 아니라 31249가 된다.
출처 : 당근이 AVR
댓글 없음:
댓글 쓰기