2014년 6월 4일 수요일

12. AVR(Atmega)와 Proteus VSM 만남 - Timer0에서 1ms 만들기 (2부)

지난 글에 이어서 오늘은 좀 계산을 해보겠습니다.
난데없이 왠 계산?

미적분은 아니니까 너무 걱정마시고, 그냥 곱셈, 나눗셈만 합니다. 그래도 충분 합니다.
아래를 먼저 보시죠. 와 닿으시나요? 아니면 멍~~ 하시나요?


하나하나 차근하게 설명합니다.

1번은,
클럭주파수입니다. 즉, 수정발진자 주파수라고 생각하면 됩니다.
주파수의 역수가 1주기 이므로, 8Mhz의 주기는 0.000000125초 입니다. 인간의 시간 관념에서 보면 순식간이죠!!

2번은,
Timer0에 설정하는 분주비(Prescale)입니다. 우선 Prescale = 1/1024 로 설정하면 마스터클럭이 1024번 진동할때 Prescale을 통해서는 1번 진동합니다. 그리고, 이것이 TCNT0를 하나씩 증가시키는 것이겠죠. 계산을 하면, Prescale이후의 주파수는 7813Hz이고 주기는 0.000128초 입니다. 주파수의 경우는 1024로 나누면 되고, 주기는 1024만큼 곱해주면 바로 나오겠죠.

3번은,
각 단위시간(ms)을 재기 위해서 TCNT0의 값을 몇 정도 카운트해야 하는가를 계산한 것입니다. 1ms(0.001se)의 도달하려면 0.000128(sec)가 7.813번 있으면 되지요. 그렇다면 반올림해서 8로 잡고, TCNT0레지스터를 0부터 8까지 카운트하고 Overflow발생시키고 나서 TCNT0를 초기화(zero)하고 다시 0부터 8까지 카운트하면 되겠죠?

근데, 문제는 지난 글에 설명했듯이 TCNT0의 Overflow는 레지스터가 꽉 차고있는 상태에서 마지막 물 한방울이 더해질때 발생하잖아요.. 그렇기 때문에 약간의 기교가 필요한 것입니다. 그래서 4번이 필요한 것입니다.

4번은,(기교)
3번에서 계산한 약8번의 카운트를 0부터 시작하는 것이 아니라 TCNT0= 248부터 시작하면 어떨까요? 그러면, 8번 카운트할때 255 --> 0로 변하면서 Overflow가 발생하지 않을까요?
예, 맞습니다. 그렇게 하면됩니다. TCNT0의 값을 248부터 시작하고 오버플로우가 발생하면 TCNT0의 값을 다시 248로 셋팅하면 됩니다. 바로 이것이 4번의 내용입니다.

참고로, 위의 4번은 10ms를 발생하려면 약78번 카운트하면 됩니다.(계산해 보세요) 그러므로 TCNT0 = 256 - 78 로 하면 됩니다.

그럼, 1초는 어떻게 하나요? 10ms를 100번 카운트하면 되지 않을까요? 그럼 됩니다.
아니면 타이머0를 사용하면 굳이 위으 4번과 같이 하지 않더라도 간단히 할 수 있습니다. 왜냐하면 Timer1은 16비트 크기의 TCNT1을 갖고 있습니다. 그러므로, TCNT1 = 65536 - 7813 으로 하면 정확히 1초마다 Overflow가 발생하며, 그때 실행하는 ISR(Timer1_OVF_vect)에서 원하는 일을 하면 됩니다.

아래에 샘플 테스트 코드를 좀 적었습니다.
10ms마다 PD5 포트를 ON/OFF(토글)시킵니다. 참고하세요.

// 2. Timer0 Init : Fast PWM Mode
void init_TM0_NORMAL_mode()
{
// TOP = 255(0xFF)
// WGM02:00 = 000 (Normal mode)
// CS02:00 = 101 (Prescale = 1024 ) --> 122Hz(about 8ms = 8000000/1024 = 122)
// Pin mode = None inverting mode

TCCR0A = 0x00; // Mode : Normal Mode
TCCR0B = _BV(CS02) | _BV(CS00); // Prescale = 1/1024

TCNT0 = 256- 78; // 10ms 발생하기 위해서 78번 카운트(즉, 178 ~ 255)

TIMSK0 = _BV(TOIE0); // 오버플로우 인터럽트 인에이블(TOIE0)
}

// 타이머0 오버플로우 인터럽트 서비스 루틴
ISR(TIMER0_OVF_vect)
{
TCNT0 = 256 - 78;
PORTD ^= _BV(PD5); // PD5 토글
}

아래 프로테우스에서는 분홍색이 10ms단위로 ON/OFF하는 PORTD5 입니다.
노란색은 100ms로 ON/OFF합니다. main에 있는 while(1) 루프 안에 있습니다.
어떤가요??


11. AVR(atmega)와 Proteus VSM의 만남 - 타이머/카운터 1초 만들기(1부)

지난 글에서 AVR을 너무 급한 마음에 소개하려고 PWM을 먼저 설명한 것이 좀 후회되네요.
차근 차근 하나씩 쉽게 했어야 했는데...

그래서, AVR에서 제공하는 Timer/Counter 기능들을 하나씩 소개하려고 합니다.
그 중에서도, Timer0의 Normal Mode를 소개하려고 합니다. 그리고, 그것을 이용하여 1초마다 오버플로우 인터럽트가 발생하면 여기저기 쓸만하겠죠?

참고로, AVR에서 지원하는 타이머/카운터 기능은 아래와 같습니다.


위 표를 보시면, 기본적으로 4가지가 있다는 것을 알 수 있죠.(Mode=0,1,2,3)
그리고 아래 두개의 모드(Mode 5, 7)은  모드1, 3번과 거의 같습니다. 다른 점은 TOP값과 Max값이 조금 다르죠. 하지만 모드 1, 3번의 동작만 안다면  이해하는데는 문제 없습니다.

그럼, 오늘은 Timer0의 Normal Mode를 이용하여 1초를 만들어 보겠습니다.

오버플로우(Overflow)란 말을 들어보셨나요? 최소한 물놀이장(워터파크)에 가보셨다면 파라오의 머리통에서 쏟아져 내리는 물세례를 맞아보아다면 오버플로우가 뭔지 온몸으로 느끼실 겁니다. 아래 그림 한번 보시죠!!

어떤가요? 온몸으로 오버플로우(Overflow = OVF)가 뭔지 느껴지시죠? 맞습니다. 디지털의 세계에서도 원리는 정말로 똑같습니다. 파라오 머리의 물통에 물이 점점 차오르고 거의 마지막에 이르면 물통은 확 뒤집히면서 물을 쏟아내죠. 그리고 다시 처음부터 물을 채우기 시작합니다. 우리는 그 아래에서 물통에 물이 다시 차오르기를 기다리고 물이 내려올때 쯤에는 나름대로 뭔가 할일을 준비하여 진행하죠. 두근거리는 마음을 다독인다던가, 아니면 물 쏟아질때쯤 무서워 딴데로 내빼던가. 아무튼, 파라오 물통의 물이 쏟아지는 그 시점이 바로 오버플로우(Overflow)입니다.

그럼, AVR에서는 어떤가 보겠습니다. 위 8비트의 TCNT0에서 보듯이 물통에 숫자 1이 꽉 차있죠. 그리고, 마지막 물(오른쪽 끝의 1)이 차오르면 TOV(Timer Overflow) Flag가 1로 되면서 오버플로우가 발생하는 겁니다. 물론, 물통속의 8개의 1은 모두 0으로 초기화 되는 것이구요. 나는 단지 TOV Flag만 감시하면 TCNT0의 물통속에 물이 쏟아져 내리고 나서 초기화 되었구나! 라고 생각만 하는 되는거죠. 어떤가요, 쉽지 않나요? 바로 TOV Flag가 Set(1)되는 시점에 Overflow Interrupt가 발생하는 것이고, 그 시점에 내가 이미 코딩해놓은 ISR(Timer0_OVF_vect) { ... } 코드가 실행되는 것입니다.

다음 글에  계속...

끝.

2014년 6월 1일 일요일

10. AVR(atmega)와 Preteus VSM의 만남 - 숨쉬는 LED를 위한 PWM 제어 (4부)

글이 점점 길어집니다. 머릿속에서는 간단했는데 활자로 표현하자니 점점 길어만 지는군요! 그래도 지치지 말고 글을 이어 나갑니다.

오늘은 Timer0의 설정을 좀 하려고 합니다. 지난번 올렸던 소스코드 중에서 Timer0의 초기화 함수를 먼저 보시죠.

// Timer0 Init (Atmega 168기준)
void init_TM0_FPWM_mode()
{
// TOP = 255(0xFF)
// WGM02:00 = 011 (FPWM mode)
// CS02:00 = 100 (Prescale = 256 ) --> 122Hz(about 8ms = 8000000/256/256 = 122)
// Pin mode = None inverting mode

TCCR0A = _BV(WGM01) | _BV(WGM00); // Mode : Fast PWM
TCCR0A |= _BV(COM0A1) | _BV(COM0B1); // PWM Out Pin Mode: None Inveting mode
TCCR0B = _BV(CS02); // Prescale = 1/256

TCNT0 = 0; // 초기값 넣기(안넣어도 되지만, 확실히 하기 위해서)
OCR0A = 1; // 안넣어도 됨. 하지만, None inverting mode에서 0값은 튀는 값이라서 1로 넣어줌.
OCR0B = 1; // 위와 마찬가지.(상동)

        // 인터럽트 마스크 설정 : Output Compare Interrupt Enable 0A, 0B
TIMSK0 = _BV(OCIE0A) | _BV(OCIE0B);
}

우선 TCCR0A, TCCR0B는 타이머0의 컨트롤 레지스터입니다. 여기에 어떤 비트(Bit)를 설정하는냐에 따라서 타이머/카운터0의 동작이 결정되지요.
아래 표를 다시 한번 보시죠. 첫번째 그림은 TCCR0A, TCCR0B입니다. TCCR이란 약자의 의미는 아시죠? Timer Counter Control Register (TCCR). Timer0는 두개의 컨트롤 레지스터가 필요하네요. 어떤 atmega칩에서는 TCCR이 한 개만 있는 것도 있거든요. 각 칩마다 다르니까 그건 그때 그때 데이터시트를 참조하시면 되겠지요.

그럼, 하나씩 설명을 하지요.
TCCR0A = _BV(WGM01) | _BV(WGM00); // Mode : Fast PWM 모드 설정
--> 아래에 있는 TCCR0A 레지스터에서 WGM01, WGM00 비트를 설정함. 물론, TCCR0B의 WGM02 비트는 0(Clear)로 설정하므로, 결국 WGM02:00 = 011로 설정하는 것임. 이것이 Fast PWM 모드로 설정하는 것이죠.  아래 Table15-8 참조하세요. 아주 잘 나와 있습니다.

TCCR0A |= _BV(COM0A1) | _BV(COM0B1);
-> 계속해서, OC0A, OC0B Pin의 출력모드를 설정합니다. Non-inverting mode입니다. 이 모드의 동작방식은 지난 글에 이미 설명했습니다. Table15-3 참조하세요. 즉, TCCR0A 레지스터에서 비트 4,5,6,7 을 해당 모드에 맞게 설정해야죠. 위 명령어는 COM0A1(비트7)과 COM0B1(비트5)를 Set(1) 합니다. COM0A0, COM0B0 비트는 clear(0)합니다. 클리어는 따로 셋팅할 필요가 없겠죠? initial value가 0라고 친절하게 나와 있으니까요.
TCCR0B = _BV(CS02); // Prescale = 1/256
--> 분주비(Prescale)을 설정하는 비트입니다. TCCR0B에서 비트0,1,2에 있습니다. 1/256분주비로 셋팅하기 위해서는 CS02, CS01, CS00 = 1, 0, 0 으로 하면 됩니다. 따라서 CS02 비트만 Set(1)하면 되며, 다른 비트는 clear(0)하면 됩니다. 디폴트(기본값)가 0 이므로  CS02만 set합니다.

  TCNT0 = 0; // 초기값 넣기(안넣어도 되지만, 확실히 하기 위해서)
OCR0A = 1; // 안넣어도 됨. 하지만, None inverting mode에서 0값은 튀는 값이라서 1로 넣어줌.
OCR0B = 1; // 위와 마찬가지.(상동)
--> TCNT0는 기본이 0(zero) 이므로 굳이 넣어줄 필요는 없는데, 명시적으로 보여주기 위해서 넣었구요. OCR0A, OCR0B는 1을 넣었는데, PWM 동작시 0값은 특이값이라서 그냥 1을 넣었습니다.(데이터시트 잘 읽어 보시면, 0을 넣을 경우 이상동작에 대해서 설명되어 있습니다.)
TIMSK0 = _BV(OCIE0A) | _BV(OCIE0B);
 --> TIMSK0는 Timer Interrupt Mask 라는 뜻입니다. 타이머0에는 3가지 인터럽트가 가능하죠. 첫번째는 OVF(Overflow), 두번째와 세번째는 OCIE(Output Compare Interrupt Enable)이며 A와 B 두가지가 있습니다. 아래 그림 참조하세요. TOIE0(Oveflow), OCIE0A(Output Compare A), OCIE0B(Output Compare B


마지막으로 잊지 말아야 할 것은, 모든 인터럽트를 활성화 시켜주세요.
sei(); // 모든 인터럽트 활성화
sei() --> Set Enable Interrupt 
이 함수를 실행시켜야만 인터럽트가 동작하기 시작하는 거죠.^^

오늘은 여기까지 하고, 다음에 또 글을 씁니다.

끝.