본문 바로가기

Develop Knowledge/Atmega, AVR

8강. 내부 인터럽트 - 8bit 타이머 인터럽트

본 글에서는 내부 인터럽트중 일종인, 8bit 타이머 인터럽트를 사용하는 방법을 알아본다.

 

- 오실레이터와 크리스탈?

Atmega에는 다양한 오실레이터와 크리스탈이 있다. 둘 다 특정한 진동수를 만들어낸다는 특징이 있지만, 오실레이터는 자채 내장 회로를 가지고, 크리스탈은 외부 회로(RC 회로)가 필요하다는 차이점이 있다.

Atmega128a 개발 보드에서는 대부분 16MHz 크리스탈이 달려있고, 이 진동수를 이용한다.

왼쪽이 오실레이터, 오른쪽이 크리스탈이다.

 

- Timer?

Atmega의 타이머는 종류마다 개수가 다르다. Atmega128a 기준으로는 8bit timer 2개, 16bit timer 2개가 있다.

이 타이머들은 오실레이터, 크리스탈의 진동수를 이용해서 시간을 계산해낸다. 예를들어, 16MHz 크리스탈은 1/16000000s 마다 1번씩 클럭(진동수를 의미한다.)이 이루어지는데, 이 클럭에 16000000을 곱하면 1초가 되는 것이다.

그러면, 이클럭은 어떻게 사용할 수 있고, 어떻게 계산해내는가? 지금부터 그걸 알아볼 것이다.

 

- 8bit Timer 시간 계산 방법

16MHz 크리스탈을 사용할 떄, 1ms를 만드는 예시로 설명하겠다.

1. 1ms를 만들기 위해서는, 1클락에 수를 곱해주어야 한다.

2. 1클락은 1/16000000 = 62.5ns 이다.

3. Prescaler=8,32,64,128.256,1024 중에 하나를 곱해준다.

4. 자연수 n을 곱해준다. 단, n은 0~255 사이여야 한다. (8bit timer이기 떄문이다.)

따라서 위의 계산 방식으로 1ms를 만든다고 가정한다면, 계산 식은 이렇게 된다.

Prescaler  = 64, n = 250 으로 설정한다면

(1/16000000) * 64 * 250 = 1/1000(s) = 1ms 이다.

이런 방식으로 원하는 주기만큼 타이머 인터럽트를 발생시킬 수 있다.

여기서 1s를 만들고 싶다면, 타이머 인터럽트를 1000번 세면 된다.

설명이 다소 어려워 보일 수 있는데, 밑의 예제 코드를 보면 조금 쉽게 이해할 수 있다.

 

Prescaler에 대해서 좀더 자세히 설명하자면, Prescaler는 1클럭의 시간이 너무 짧아서 임의로 최대 주파수를 prescaler배만큼 적게 해주는 역할을 해주는 역할이다.

 

- 8bit Timer 레지스터

레지스터는 간단히 예기하면 '설정 값'을 의미한다. 보통 다음의 과정을 따른다.

1. TCCR로 타이머를 설정한다.

2. TCNT로 타이머의 초기값을 설정한다.

3. TIMSK로 타이머의 결과를 승인받고,

4. TIFR로 타이머의 결과를 출력한다.

 

> 1. TCCR0 (Timer/Counter Control Register 0) : 타이머 설정

>> WGM00, WGM01 : Timer의 모드를 설정한다.

>> COM01, COM02 : 특정 조건을 만족할 떄, 출력 파형을 내보낼 수 있다.

>> CS00, CS01, CS02 : Prescaler를 설정한다.

 

> WGM00, WGM01

이 글에서는 Normal 모드를 사용할 것이다. 즉, 비트는 00이다.

Normal Mode는 '타이머 값이 오버플로우가 발생 시, 타이머 인터럽트를 발생시킨다'는 의미이다.

 

> COM01, COM02

출력 파형을 만든다. 이 글에서는 안쓴다. 비트를 00으로 설정한다.

 

> CS01, CS02, CS03

비트가 000이면 TImer를 종료하고,

비트가 000이 아니면 Timer를 실행하고, Prescaler를 적용한다.

 

> 2. TCNT0 (Timer/Counter Register 0) : 타이머 클럭 카운트

TCNT0은 0~255를 셀 수 있다.

타이머 클럭이 1씩 증가할 때 마다 TCNT0도 1씩 증가하며,

256이 되면 오버플로우가 되면서 타이머 인터럽트가 활성화 된다.

 

TCNT0 값은 위 공식의 n과 관련이 있다.

만약에 TCNT0=6으로 설정했다면, 클럭이 1씩 올라가면서 TCNT0 값도 올라간다. 7,8,9,10,...

그러다 256이 되면, 오버플로우가 발생하면서 이 값은 0이 되고, 타이머 인터럽트가 발생한다.

즉, TCNT0 값은 256-n 값으로 설정해야 한다.

> 3. TIMSK (Timer/Counter Interrupt Mask Register) : 타이머 인터럽트 승인

타이머 인터럽트를 활성화 시키기 위해, 승인을 받아야 한다.

>> TOIE0 : 1이면, 오버플로우를 통한 타이머 인터럽트를 승인한다.

 

> 4. TIFR (Timer/Counter Interrupt Flag Register) : 타이머 인터럽트 결과

flag는 간단히 말하면 '결과'를 의미한다.

>> TOV0 : TCNT0이 오버플로우 되면, 이 값이 1이 된다.

 

 

- 예제 : 8bit timer0으로 원하는 시간 만들기

// PORTA는 1ms마다, PORTB는 1s마다 신호를 출력한다.

#include <avr/io.h>
#include <avr/interrupt.h>

volatile int count=0;
ISR(TIMER0_OVF_vect) // 타이머 인터럽트가 발생할 때 마다, 이게 실행된다 
{
	// TCNT 값은 스스로 초기화 되지 않는다.
	// 따라서 오버플로우가 발생할 때 마다, 다시 초기값을 변경해주어야 한다.
    
	// -250은 unsigned char 자료형에서 6을 의미한다.
	// 자세한건 https://dojang.io/mod/page/view.php?id=32 에서 확인할 수 있다.
	TCNT0 = -250;
	
	// 작업 수행
	PORTA = ~PORTA;
	
	// 원하는 시간 만들기
	count++;
	if(count>1000-1) //1s 를 만든다.
	{
		PORTB = ~PORTB;
		count=0;
	}
}

void timer0_setting()
{
	// 1<<CS02 이런건, CS02 비트를 활성화 하겠다는 의미이다.
	TCCR0 = ((1<<CS02)|(0<<CS01)|(0<<CS00)); // timer0을 키고, prescaler를 설정한다.
	TCNT0=-250; //1ms로 설정한다. 1/1600000 * 64 * 250 = 1/1000(s) = 1ms
	TIMSK=(1<<TOIE0); // 인터럽트를 허용한다.
}

int main(void)
{
	DDRA = 0xff;
	DDRB = 0xff;
	timer0_setting();
	sei(); // 인터럽트 사용을 허용한다.

	while (1) 
	{
	}
}

실험 결과, 정확히 1ms가 측정된 것을 확인할 수 있다.

(주파수가 500Hz 인데, 이건 1신호를 1ms, 0신호를 1ms씩 만들었기 때문이다.)

 

 

도움받은 블로그 : https://m.blog.naver.com/alsrb968/220867860815

'Develop Knowledge > Atmega, AVR' 카테고리의 다른 글

10강. USART, UART 통신  (0) 2023.01.10
9강. PWM (8bit-timer)  (0) 2022.12.19
7강. 내부 인터럽트와 외부 인터럽트  (0) 2022.12.18
6강. GPIO 실습 - 스위치로 LED 다루기  (0) 2022.12.17
5강. 핀 선택하기  (0) 2022.12.17