SW 개발

[AVR 펌웨어] timer/pwm 핸들링 (예제코드)

. . . 2011. 12. 2. 15:12
반응형

출처 : 나 (이전 정리했던 자료...)

기초지식.

ATmega128은 4개의 범용 타이머/카운터가 있다.

  • Timer/Counter0(8비트),
  • Timer/Counter1(16비트),
  • Timer/Counter2(8비트),
  • Timer/Counter3(16비트)

타이머/카운터의 제어에 필요한 레지스터는...

  • 타이머/카운터 제어 레지스터(TCCRn)
  • 타이머/카운터 레지스터(TCNTn)
  • 출력 비교 레지스터(OCRn)

타이머/카운터 인터럽트 관련한 레지스터는...

  • 타이머/카운터 인터럽트 플래그 레지스터(TIFR)
  • 타이머/카운터 인터럽트 마스크 레지스터(TIMSK)

타이머를 사용하기 위해서는 타이머에서 사용하는 클럭에 대해서 설정을 해야 하는데 이는 프리스케일러(Prescaler) 값으로 조절할 수 있다. 프리스케일러 값은 각 타이머의 컨트롤 레지스터(TCCRn)에서 설정할 수가 있다. 타이머 인터럽트는 각 타이머 관련 컨트롤 레지스터에서 적절한 프리스케일러 값을 설정한 후 , 각 타이머 레지스터(TCNTn)에 얼마마다 한번씩 인터럽트를 걸게 할 것인지와 관련된 값을 써주면 된다.

그리고 인터럽트를 사용해야 하므로 타이머 인터럽트 관련 레지스터들을 설정해야 한다. 타이머 인터럽트에서는 TIMSK 레지스터만 설정하면 된다.

실제 간단 예제...

#include <iom128.h>
#include <ina90.h>
#include <sig-avr.h>
#include <interrupt.h>

#define   SYSTEM_CLOCK  16000000   // CLOCK (X-tal frequency)

volatile int Count = 0;

SIGNAL(SIG_OVERFLOW0)
{
        outp(0x83, TCNT0);

        outp(Count, PORTA);
        Count++;
        if (Count > 255)Count= 0;
}

void Port_Init(void)
{
        //Port_A setting up output
        outp(0xFF, DDRA);
}

void Timer_Init(void)
{
        // Timer0 setting, 1ms
        // system_clk/Presclae = divide_clk
        // 1/divide_clk = divide_time
        // 1ms/divide_time = n
        // 256 - n = TCNT0

        outp(0x05, TCCR0);      // Clock Source = System clock/128
        outp(0x83, TCNT0);

        outp(0x01, TIMSK);      // Timer 0 overflow enable
}

int main(void)
{
        // External Memory Disable
        outp(0x00, MCUCR);
        Port_Init();
        Timer_Init();
        sei();

        while(1){ }

        return 0;
}

위의 소스는 인터넷에서 돌아당기던 실제예제소스.이다.

  • 1msec마다 발생하는 타이머 오버플로 인터럽트를 이용하여, 변수값이 1씩 증가하도록 설정하여 포트 A로 카운트된 값을 출력하는 프로그램

타이머 ISR 관리하기

이제 실제 프로젝트에서의 실제 사용 예제를 살펴보자...

void timerDetach(u08 interruptNum)
{
    // make sure the interrupt number is within bounds
    if(interruptNum < TIMER_NUM_INTERRUPTS)
    {
        // set the interrupt function to run nothing
        TimerIntFunc[interruptNum] = 0;
    }
}

역시나 각각의 배열에 함수 포인터로 연결해서 나중에 isr연결할때 쓴다.

각각 타이머 prescaler 관리

void timer0SetPrescaler(u08 prescale)
{
    // set prescaler on timer 0
    outb(TCCR0, (inb(TCCR0) & ~TIMER_PRESCALE_MASK) | prescale);
}
void timer1SetPrescaler(u08 prescale)
{
    // set prescaler on timer 1
    outb(TCCR1B, (inb(TCCR1B) & ~TIMER_PRESCALE_MASK) | prescale);
}

이런식으로 각각 세팅한다.

  • 각 타이머 마다.. 해당레지스터 중에 프리스케일러에 해당하는 비트만 세팅한다. 각각의 프리스케일러 값을...프로그램 메모리 영역에 저장을 한다. 그래서 리턴하는 방식으로 프리스케일러 값을 얻어온다.
u16 timer0GetPrescaler(void) 
{
    // get the current prescaler setting
    return (pgm_read_word(TimerPrescaleFactor+(inb(TCCR0) & TIMER_PRESCALE_MASK)));
}
u16 timer1GetPrescaler(void)
{
    // get the current prescaler setting
    return (pgm_read_word(TimerPrescaleFactor+(inb(TCCR1B) & TIMER_PRESCALE_MASK)));
}

프리스케일러 값을 설정하는것은 reference book을 살펴보길 바란다.

타이머초기화부분...
void timer0Init()
{
    // initialize timer 0
    timer0SetPrescaler( TIMER0PRESCALE );    // set prescaler
    outb(TCNT0, 0);                            // reset TCNT0
    sbi(TIMSK, TOIE0);                        // enable TCNT0 overflow interrupt
    timer0ClearOverflowCount();                // initialize time registers
}
void timer1Init(void)
{
    // initialize timer 1
    timer1SetPrescaler( TIMER1PRESCALE );    // set prescaler
    outb(TCNT1H, 0);                        // reset TCNT1
    outb(TCNT1L, 0);
    sbi(TIMSK, TOIE1);                        // enable TCNT1 overflow
}
void timer2Init(void)
{
    // initialize timer 2
    timer2SetPrescaler( TIMER2PRESCALE );    // set prescaler
    outb(TCNT2, 0);                            // reset TCNT2
    sbi(TIMSK, TOIE2);                        // enable TCNT2 overflow
    timer2ClearOverflowCount();                // initialize time registers
}
void timer3Init(void)
{
    // initialize timer 3
    timer3SetPrescaler( TIMER3PRESCALE );    // set prescaler
    outb(TCNT3H, 0);                        // reset TCNT3
    outb(TCNT3L, 0);
    sbi(ETIMSK, TOIE3);                        // enable TCNT3 overflow
}
  • 각각의 프리스케일러와 오버플로우 레지스터 세팅같은걸 한다.

그럼 인터럽트 처리 루틴을 한번 보자..

TIMER_INTERRUPT_HANDLER(SIG_OVERFLOW0)
{
    Timer0Reg0++;        // increment low-order counter
    if(!Timer0Reg0)        // if low-order counter rollover
        Timer0Reg1++;    // increment high-order counter    

    // if a user function is defined, execute it too
    if(TimerIntFunc[TIMER0OVERFLOW_INT])
        TimerIntFunc[TIMER0OVERFLOW_INT]();
}

//! Interrupt handler for Timer1 overflow interrupt
TIMER_INTERRUPT_HANDLER(SIG_OVERFLOW1)
{
    // if a user function is defined, execute it
    if(TimerIntFunc[TIMER1OVERFLOW_INT])
        TimerIntFunc[TIMER1OVERFLOW_INT]();
}

//! Interrupt handler for Timer2 overflow interrupt
TIMER_INTERRUPT_HANDLER(SIG_OVERFLOW2)
{
    Timer2Reg0++;        // increment low-order counter
    if(!Timer2Reg0)        // if low-order counter rollover
        Timer2Reg1++;    // increment high-order counter    

    // increment pause counter
    TimerPauseReg++;

    // if a user function is defined, execute it
    if(TimerIntFunc[TIMER2OVERFLOW_INT])
        TimerIntFunc[TIMER2OVERFLOW_INT]();
}
  • 아마도 avr_studio 에서는 위와 같은 TIMER_INTERRUPT_HANDLER () 에서 isr을 관리하는듯 하다. 이때 관리시에.. 위에서 등록한 함수포인터들을 연결시켜놓는다.
  • 하나의 타이머 인터럽트로 각 프로그래밍의 상황에따라... 여러 isr을 처리할수도 있을듯 하다.
  • 이렇게 관리를 하면 각 인터럽트 루틴을 따로 안고치더라도 쉽게 isr을 변경할수 있을듯 하다.

실제적인 각 타이머의 동작 관리

void timer3PWMAOn(void)
{
    // turn on channel A (OC3A) PWM output
    // set OC3A as non-inverted PWM
    sbi(TCCR3A,COMA1);
    cbi(TCCR3A,COMA0);
}

void timer3PWMBOn(void)
{
    // turn on channel B (OC3B) PWM output
    // set OC3B as non-inverted PWM
    sbi(TCCR3A,COMB1);
    cbi(TCCR3A,COMB0);
}

void timer3PWMCOn(void)
{
    // turn on channel C (OC3C) PWM output
    // set OC3C as non-inverted PWM
    sbi(TCCR3A,COMC1);
    cbi(TCCR3A,COMC0);
}

void timer3PWMAOff(void)
{
    // turn off channel A (OC3A) PWM output
    // set OC3A (OutputCompare action) to none
    cbi(TCCR3A,COMA1);
    cbi(TCCR3A,COMA0);
}

void timer3PWMBOff(void)
{
    // turn off channel B (OC3B) PWM output
    // set OC3B (OutputCompare action) to none
    cbi(TCCR3A,COMB1);
    cbi(TCCR3A,COMB0);
}

void timer3PWMCOff(void)
{
    // turn off channel C (OC3C) PWM output
    // set OC3C (OutputCompare action) to none
    cbi(TCCR3A,COMC1);
    cbi(TCCR3A,COMC0);
}

void timer3PWMASet(u16 pwmDuty)
{
    // set PWM (output compare) duty for channel A
    // this PWM output is generated on OC3A pin
    // NOTE:    pwmDuty should be in the range 0-255 for 8bit PWM
    //            pwmDuty should be in the range 0-511 for 9bit PWM
    //            pwmDuty should be in the range 0-1023 for 10bit PWM
    outb(OCR3AH, (pwmDuty>>8));        // set the high 8bits of OCR3A
    outb(OCR3AL, (pwmDuty&0x00FF));    // set the low 8bits of OCR3A
}

void timer3PWMBSet(u16 pwmDuty)
{
    // set PWM (output compare) duty for channel B
    // this PWM output is generated on OC3B pin
    // NOTE:    pwmDuty should be in the range 0-255 for 8bit PWM
    //            pwmDuty should be in the range 0-511 for 9bit PWM
    //            pwmDuty should be in the range 0-1023 for 10bit PWM
    outb(OCR3BH, (pwmDuty>>8));        // set the high 8bits of OCR3B
    outb(OCR3BL, (pwmDuty&0x00FF));    // set the low 8bits of OCR3B
}

void timer3PWMCSet(u16 pwmDuty)
{
    // set PWM (output compare) duty for channel B
    // this PWM output is generated on OC3C pin
    // NOTE:    pwmDuty should be in the range 0-255 for 8bit PWM
    //            pwmDuty should be in the range 0-511 for 9bit PWM
    //            pwmDuty should be in the range 0-1023 for 10bit PWM
    outb(OCR3CH, (pwmDuty>>8));        // set the high 8bits of OCR3C
    outb(OCR3CL, (pwmDuty&0x00FF));    // set the low 8bits of OCR3C
}
  • 아마도 timer / pwm의 관리는 해당 레지스터를 세팅하면 해당 isr루틴으로 갈것 같으니 별로 어려울것 없을듯하다.
  • 데이터 시트만 잘 보면 그리 어려울것 같지는 않다.ㅋㅋ
  • 뭐 카운터 관련 레지스터들을 초기화 시켜줘야 할 경우도 있을듯 하다.
반응형