[임베디드/C] 8Bit Timer

3 분 소요

타이머/카운터

입력되는 펄스를 세는 장치이다. 주기가 일정한 펄스가 입력된다며 펄스의 개수를 통해 시간을 측정할 수 있으므로 타이머의 역할도 가능하다. 입력 펄스의 경우 시스템 클록이나 외부에서 주어지는 클록 중에서 선택할 수 있다.

8비트 타이머/카운터

0에서 255까지 셀 수 있다. ATmega128의 내부 클록은 16MHz속도로 동작하므로 0에서 시작하여 다시 0으로 돌아오기까지의 시간은 ${256 \over 16M} = 0.016ms$이다. 0.016ms 이상의 시간을 측정하기 위해 분주기를 사용하여 주기가 긴 클록을 생성한다.

TCNTn(n = 0, 2), TCCRn(n = 0, 2) 레지스터

펄스의 수는 TCNTn에 기록된다. 펄스의 개수가 셀 수 있는 최댓값을 넘어서면 0으로 초기화되며 오버플로 인터럽트가 발생한다. 0에서 부터 최댓값까지 도달하는 시간은 클록과 분주비에 의해 결정된다. 클록은 16MHz이고 분주비는 몇 가지 값 중 하나만 쓸 수 있어서 다양한 시간을 측정하기가 어렵다. 그래서 비교 일치 인터럽트를 사용하여 보다 다양한 시간을 측정한다. 비교 일치 인터럽트는 TCNTn 레지스터에 저장된 현재까지의 펄스 개수가 미리 설정된 값과 동일할 경우 발생한다. 미리 설정된 값은 OCRn(n = 0, 2)레지스터에서 읽어온다. 타이머/카운터의 세부 동작을은 TCCRn레지스터를 통해 제어한다.

BIT 7 6 5 4 3 2 1 0
TCNT0[7:0]                
ReadorWrite R/W R/W R/W R/W R/W R/W R/W R/W
InitNum 0 0 0 0 0 0 0 0

타이머/카운터는 디폴트로 비활성 상태이다. TCCR0 레지스터의 분주비를 설정하는 CS00, CS01, CS02비트를 000이 아닌 값으로 바꾸어야 활성화된다.

BIT 7 6 5 4 3 2 1 0
. FOC0 WGM00 COM01 COM00 WGM01 CS02 CS01 CS00
ReadorWrite R/W R/W R/W R/W R/W R/W R/W R/W
InitNum 0 0 0 0 0 0 0 0

2번에서 0번까지의 비트가 분주비를 설정하기 위해 사용한다. 나머지는 비교일치 인터럽트를 제어하는 내용. 가령 111일 때, 분주비는 1,024이다. 그러면 클록은 ${16MHz \over 1,024} ~= 16KHz$ 이고 1초에 16K개의 펄스가 주어지므로 256개 펄스를 세려면 ${256 \over 16K}$초가 필요하다. 오버플로 인터럽트가 64번 발생하면 1초(사실 1.05초, 정확한 계산이 필요하다면 이렇게 해서는 안된다)가 지난다는 의미이다.

오버플로, 비교일치 인터럽트

인터럽트를 허용시키려면 sei();로 전역 인터럽트를 허용해 줄 뿐 아니라 각 레지스터의 인터럽트 비트를 세트해야 한다. 오버플로와 비교일치 인터럽트는 TIMSK 레지스터에 활성화 비트가 있다. 0번 비트인 TOIE0는 오버플로 인터럽트 활성화 비트, 1번 비트인 OCIE0는 비교일치 인터럽트 활성화 비트이다.

오버플로나 비교 일치 각각의 인터럽트에 해당하는 조건을 만족하면 TIFR 레지스터의 해당비트가 먼저 세트된다. TIFR 레지스터의 해당 비트가 세트되었을 때 TIMSK 레지스터의 해당 비트 역시 세트되면 실제로 인터럽트가 발생한다.

비교 일치 인터럽트의 경우, OCR0 레지스터에 비교값을 설정해 주어야 한다. TCNT0레지스터에 기록된 펄스개수와 비교하기 위한 8비트값이 저장된다.

LED점멸 예제 - 오버플로

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

int count = 0;
int state = 0;

ISR(TIMER0_OVF_vect)
{
  count++;
  if(count == 32) { // overflow 32 happened -> 0.5 second
    count = 0;
    state = !state;
    if(state) PORTB = 0x01; // LED on
    else PORTB = 0x00; // LED off
  }
}

int main(void)
{
  DDRB = 0x01;
  PORTB = 0x00;
  
  // pre-scaler set to 1,024
  TCCR0 |= (1 << CS02) | (1 << CS01) | (1 << CS00);
  
  TIMSK |= (1 << TOIE0); // overflow interrupt allowed
  
  sei(); // global interrupt allowed
  
  while(1){}
  return 0;
}

LED점멸 예제 - 비교 일치

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

int count = 0;
int state = 0;

ISR(TIMER0_COMP_vect)
{
  count++;
  TCNT0 = 0;
  if(count == 64) {
    count = 0;
    state = !state;
    if(state) PORTB = 0x01;
    else PORTB = 0x00;
}

int main()
{
  DDRB = 0x01;
  PORTB = 0x00;
  
  TCCR0 |= (1 << CS02) | (1 << CS01) | (1 << CS00);
  
  OCR0 = 128;
  // 8비트 타이머는 256개까지 펄스 개수를 기록하므로 비교 일치 인터럽트가 64번 발생해야 0.5초가 경과한다.
  // 또한 인터럽트가 발생할 때마다 이때까지 센 펄스의 개수를 초기화하지 않으면 128부터 다음 128까지 세므로 주의해야 한다.
  
  TIMSK = (1 << OCIE0);
  
  sei();
  
  while(){}
  return 0;
}

인터럽트 서비스 루틴을 짧게 작성하면,

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

volatile int count = 0;
int state = 0;

ISR(TIMER0_COMP_vect)
{
  count++;
  TCNT0 = 0;
}

int main(void)
{
  DDRB = 0x01;
  PORTB = 0x00;
  
  TCCR0 |= (1 << CS02) | (1 << CS01) | (1 << CS00);
  
  OCR0 = 128;
  
  TIMSK |= (1 << OCIE0);
  
  sei();
  
  while(1) {
    if(count == 64) {
      count = 0;
      state = !state
      if(state) PORTB = 0x01;
      else PORTB = 0X00;
    }
  }
  
  return 0;
}

외부 크리스털 사용시

시스템 클록인 16MHz는 초 단위 시간 간격을 정확하게 세기 어렵다. 그래서 보통 TOSC 핀에 32,768KHz 크리스털을 연결하여 사용하는 것이 일반적이다. 만약 분주비를 128로 설정한다면 ${32768 \over 128} = 256$개의 클록이 1초에 공급되므로 오버플로 인터럽트의 경우 정확히 1초에 한 번 인터럽트가 발생한다. 외부 오실레이터를 사용하려면 ASSR레지스터의 3번 AS0비트를 1로 세트해야 한다.

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

int state = 0;

ISR(TIMER0_OVF_vect)
{
  state = !state;
  if(state) PORTB = 0x01;
  else PORTB = 0x00;
}

int main()
{
  DDRB = 0x01;
  PORTB = 0x00;
  
  ASSR |= (1 << AS0);
  
  TCR0 |= (1 << CS02) | (1 << S00);
  TIMSK |= (1 << TOIE0);
  
  sei();
  
  while(1){ }
  return 0;
}

댓글남기기