본문 바로가기
임베디드 개발/TMS320F2838x (C28x)

TMS320F28388D ] 타이머, Timer Interrupt 사용하기

by eteo 2022. 12. 5.

 

TMS320F28388D 모델에는 32bit Timer가 CPU당 각 3개씩 총 6개가 있다.

 

 

 

 

 

 

특이한 것은 Timer1,2 인터럽트는 각각 INT13, INT14에 연결되어 있고 Timer0는 PIE Interrupt Group 1의 7번째로 자리잡고 있다.

 

 

 

 

 

 

매뉴얼 153페이지 벡터 테이블

 

CPU0 TIMER가 higher priority를 가지고 그 다음이 CPU1 TIMER 그리고 CPU2 TIMER 이다.

 

 

 

 

 

CPU 타이머 레지스터는 다음과 같다.

 

 

 

 

 

먼저 카운터 레지스터의 설명을 살펴보자.

 

TIM 레지스터는 SYSCLOCK 을 Prescaler값(TDDR+1)으로 분주한 clock cycle 에 따라 감소하고 그러다 0에 도달하면 Period 레지스터(PRD)에 있는 값으로 reload 되며 타이머 인터럽트 시그널을 발생시킨다.

 

 

 

 

 

 

 

 

그리고 가장 중요한 타이머 컨트롤 레지스터이다.

 

 

TIF : Overflow Flag인데 인터럽트랑은 관련 없으며 자동으로 clear 되지 않으니 한번이라도 TIM이 0에 도달했다면 1값을 가지고 있을 것이다.

TIE : 타이머 인터럽트 Enable/disable

TRB : 타이머 카운터(TIM)를 Period 레지스터(PRD)에 있는 값으로 reload 한다.

TSS : 타이머를 stop/start

 

참고로 TI사에서 제공하는 API인 CPUTimer_startTimer()를 사용하면 counter value를 리로드하고 TSS bit을 0으로 set해 타이머를 시작한다.

 

 

 

 

 

사용방법은 driverlib 예제를 참고하면 간단하다.

 

 

 

 

 

먼저 전역공간에 타이머 인터럽트 콜백함수의 원형과  콜백함수가 얼마나 호출되었는지 확인하기 위한 count 변수 그리고 기타 함수 원형을 선언한다.

//
// Globals
//
uint16_t cpuTimer0IntCount;
uint16_t cpuTimer1IntCount;
uint16_t cpuTimer2IntCount;

//
// Function Prototypes
//
__interrupt void cpuTimer0ISR(void);
__interrupt void cpuTimer1ISR(void);
__interrupt void cpuTimer2ISR(void);
void initCPUTimers(void);
void configCPUTimer(uint32_t, float, float);

 

 

 

 

다음 main 함수

//
// Main
//
void main(void)
{
    //
    // Initializes device clock and peripherals
    //
    Device_init();

    //
    // Initializes PIE and clears PIE registers. Disables CPU interrupts.
    //
    Interrupt_initModule();

    //
    // Initializes the PIE vector table with pointers to the shell Interrupt
    // Service Routines (ISR).
    //
    Interrupt_initVectorTable();

    //
    // ISRs for each CPU Timer interrupt
    // 각각 인터럽트 콜백함수를 등록한다.
    Interrupt_register(INT_TIMER0, &cpuTimer0ISR);
    Interrupt_register(INT_TIMER1, &cpuTimer1ISR);
    Interrupt_register(INT_TIMER2, &cpuTimer2ISR);

    //
    // Initializes the Device Peripheral. For this example, only initialize the
    // Cpu Timers.
    // CPU Timer를 initialize 한다.
    initCPUTimers();

    //
    // Configure CPU-Timer 0, 1, and 2 to interrupt every 1, 2, 4 seconds:
    // 1, 2, 4 Period respectively (in uSeconds)
    // CPU Timer 0,1,2 를 각각 1, 2, 4초마다 인터럽트가 걸리게 설정한다. 3번째 인자는 us단위이다.
    configCPUTimer(CPUTIMER0_BASE, DEVICE_SYSCLK_FREQ, 1000000);
    configCPUTimer(CPUTIMER1_BASE, DEVICE_SYSCLK_FREQ, 2000000);
    configCPUTimer(CPUTIMER2_BASE, DEVICE_SYSCLK_FREQ, 4000000);

    //
    // To ensure precise timing, use write-only instructions to write to the
    // entire register. Therefore, if any of the configuration bits are changed
    // in configCPUTimer and initCPUTimers, the below settings must also
    // be updated.
    // CPU Timer Interrupt를 enable한다. 타이머 설정이 바뀌는 경우엔 재호출이 필요하다.
    CPUTimer_enableInterrupt(CPUTIMER0_BASE);
    CPUTimer_enableInterrupt(CPUTIMER1_BASE);
    CPUTimer_enableInterrupt(CPUTIMER2_BASE);

    //
    // Enables CPU int1, int13, and int14 which are connected to CPU-Timer 0,
    // CPU-Timer 1, and CPU-Timer 2 respectively.
    // Enable TINT0 in the PIE: Group 1 interrupt 7
    // 각각 CPU Timer 0,1,2와 연결된 Interrupt를 enable한다.
    Interrupt_enable(INT_TIMER0);
    Interrupt_enable(INT_TIMER1);
    Interrupt_enable(INT_TIMER2);

    //
    // Starts CPU-Timer 0, CPU-Timer 1, and CPU-Timer 2.
    // CPU Timer를 start 한다.
    CPUTimer_startTimer(CPUTIMER0_BASE);
    CPUTimer_startTimer(CPUTIMER1_BASE);
    CPUTimer_startTimer(CPUTIMER2_BASE);

    //
    // Enable Global Interrupt (INTM) and realtime interrupt (DBGM)
    //
    EINT;
    ERTM;

    //
    // IDLE loop. Just sit and loop forever (optional)
    //
    while(1)
    {
    }
}

 

 

 

 

main 함수 안에 있는 initCPUTimers() 와 configCPUTimer() 함수를 차례대로 살펴보자

 

void initCPUTimers(void) 함수

 

//
// initCPUTimers - This function initializes all three CPU timers
// to a known state.
//
void
initCPUTimers(void)
{
    //
    // Initialize timer period to maximum
    // period를 32비트 타이머의 맥시멈값인 0xFFFFFFFF로 설정한다.
    CPUTimer_setPeriod(CPUTIMER0_BASE, 0xFFFFFFFF);
    CPUTimer_setPeriod(CPUTIMER1_BASE, 0xFFFFFFFF);
    CPUTimer_setPeriod(CPUTIMER2_BASE, 0xFFFFFFFF);

    //
    // Initialize pre-scale counter to divide by 1 (SYSCLKOUT)
    // prescaler 값을 0으로 설정한다.
    CPUTimer_setPreScaler(CPUTIMER0_BASE, 0);
    CPUTimer_setPreScaler(CPUTIMER1_BASE, 0);
    CPUTimer_setPreScaler(CPUTIMER2_BASE, 0);

    //
    // Make sure timer is stopped
    // 타이머 stop
    CPUTimer_stopTimer(CPUTIMER0_BASE);
    CPUTimer_stopTimer(CPUTIMER1_BASE);
    CPUTimer_stopTimer(CPUTIMER2_BASE);

    //
    // Reload all counter register with period value
    // 타이머 카운터 값을 period value 로 reload한다.
    CPUTimer_reloadTimerCounter(CPUTIMER0_BASE);
    CPUTimer_reloadTimerCounter(CPUTIMER1_BASE);
    CPUTimer_reloadTimerCounter(CPUTIMER2_BASE);

    //
    // Reset interrupt counter
    // 전역변수 0으로 초기화
    cpuTimer0IntCount = 0;
    cpuTimer1IntCount = 0;
    cpuTimer2IntCount = 0;
}

 

 

 

 

 

void configCPUTimer(uint32_t cpuTimer, float freq, float period) 함수

 

//
// configCPUTimer - This function initializes the selected timer to the
// period specified by the "freq" and "period" parameters. The "freq" is
// entered as Hz and the period in uSeconds. The timer is held in the stopped
// state after configuration.

// 이 함수의 두번째 매개변수로 DEVICE_SYSCLK_FREQ이 들어온다. 이 경우 200MHz이다.
void
configCPUTimer(uint32_t cpuTimer, float freq, float period)
{
    uint32_t temp;

    //
    // Initialize timer period:
    // 타이머의 period를 3번째 매개변수 값으로 설정한다. 단위는 us이다.
    temp = (uint32_t)((freq / 1000000) * period);
    CPUTimer_setPeriod(cpuTimer, temp);

    //
    // Set pre-scale counter to divide by 1 (SYSCLKOUT):
    // prescaler 값을 0으로 설정한다. SYSCLKOUT인 200MHz를 slow down하지 않고 그냥 사용한다.
    CPUTimer_setPreScaler(cpuTimer, 0);

    //
    // Initializes timer control register. The timer is stopped, reloaded,
    // free run disabled, and interrupt enabled.
    // Additionally, the free and soft bits are set
    // 타이머 stop, reload 후 interrupt enable 한다.
    // CPUTimer_setEmulationMode 는 Emulation 모드일 때(디버깅 중)의 동작 방식을 설정하는 것이다.
    // 디버깅 중이 아닐 때는 영향이 없다.
    CPUTimer_stopTimer(cpuTimer);
    CPUTimer_reloadTimerCounter(cpuTimer);
    CPUTimer_setEmulationMode(cpuTimer,
                              CPUTIMER_EMULATIONMODE_STOPAFTERNEXTDECREMENT);
    CPUTimer_enableInterrupt(cpuTimer);

    //
    // Resets interrupt counters for the three cpuTimers
    // 인터럽트 콜백함수에서 count할 변수를 또 초기화해주고 있다.
    if (cpuTimer == CPUTIMER0_BASE)
    {
        cpuTimer0IntCount = 0;
    }
    else if(cpuTimer == CPUTIMER1_BASE)
    {
        cpuTimer1IntCount = 0;
    }
    else if(cpuTimer == CPUTIMER2_BASE)
    {
        cpuTimer2IntCount = 0;
    }
}

 

 

 

콜백함수들. 

눈여겨 볼 것은 타이머0은 PIE Interrupt Group1의 Acknowledge를 하고있는 반면 TIMER1,2는 바로 INT13,INT14에 연결되기에 하지 않고 있다.

 

//
// cpuTimer0ISR - Counter for CpuTimer0
//
__interrupt void
cpuTimer0ISR(void)
{
    cpuTimer0IntCount++;

    //
    // Acknowledge this interrupt to receive more interrupts from group 1
    //
    Interrupt_clearACKGroup(INTERRUPT_ACK_GROUP1);
}

//
// cpuTimer1ISR - Counter for CpuTimer1
//
__interrupt void
cpuTimer1ISR(void)
{
    //
    // The CPU acknowledges the interrupt.
    //
    cpuTimer1IntCount++;
}

//
// cpuTimer2ISR - Counter for CpuTimer2
//
__interrupt void
cpuTimer2ISR(void)
{
    //
    // The CPU acknowledges the interrupt.
    //
    cpuTimer2IntCount++;
}

 

 

 

 

매뉴얼 150페이지의 PIE Channel Mapping

 

 

 

 

 

 

 

 

PIEACK 레지스터는 예를들어 Interrupt Group 1이라고 하면 INT1.1 부터 INT1.16까지 하나라도 인터럽트가 전달되면 그룹에 대한 ACK bit가 1로 set 되고, 해당 인터럽트가 처리되는 동안에는 페리페럴에서 다른 인터럽트가 발생하더라도 CPU로 전달되지 않는다. ACK bit에 1을 기록하면 0으로 클리어되며 이제 다른 인터럽트를 전달할 수 있는 상태가 된다.

때문에 PIE(Peripheral Interrupt Expansin) 인터럽트의 ISR 함수는 빠져나가기 전에 반드시  Interrupt_clearACKGroup(INTERRUPT_ACK_GROUPx) 함수의 호출이 필요하다.

 

 

 

 

 

 

 

인터럽트 구조

 

 

 

 

 

 

참고.

 

현재 타이머 카운터 값 리턴하는 함수. cputimer.h 에 인라인함수로 정의되어 있다. TIM 레지스터를 uint32_t형으로 반환한다.

 

 

 

 

인라인 함수(inline function)

함수의 호출은 꽤 복잡한 과정을 거치므로 약간의 시간이 걸리게 되는데, 이때 함수를 실행하는 시간이 오래 걸린다면 함수를 호출하는데 걸리는 시간은 전혀 문제가 되지 않는다.
하지만 함수의 실행 시간이 매우 짧다면, 함수 호출에 걸리는 시간도 부담이 될 수 있다. 
이러한 경우에 사용할 수 있는 것이 인라인 함수(inline function)이다.
인라인 함수는 호출될 때 일반적인 함수의 호출 과정을 거치지 않고, 함수의 모든 코드를 호출된 자리에 바로 삽입하는 방식의 함수이다. 이 방식은 함수를 호출하는 데 걸리는 시간은 절약되나, 함수 호출 과정으로 생기는 여러 이점을 포기하게 된다.
따라서 코드가 매우 적은 함수만을 인라인 함수로 선언하는 것이 좋다.

출처 : http://www.tcpschool.com/cpp/cpp_cppFunction_inlineFunction