본문 바로가기

Firmware/RTOS

임베디드 OS 개발 프로젝트 9(Timer)

.

├── boot
│   ├── Entry.S
│   ├── Handler.c
│   └── Main.c
├── hal
│   ├── HalInterrupt.h
│   ├── HalTimer.h
│   ├── HalUart.h
│   └── rvpb
│       ├── Interrupt.c
│       ├── Interrupt.h
│       ├── Regs.c
│       ├── Timer.c
│       ├── Timer.h
│       ├── Uart.c
│       └── Uart.h
├── include
│   ├── ARMv7AR.h
│   ├── memio.h
│   ├── MemoryMap.h
│   ├── stdarg.h
│   └── stdint.h
├── lib
│   ├── armcpu.c
│   ├── armcpu.h
│   ├── stdio.c
│   ├── stdio.h
│   ├── stdlib.c
│   └── stdlib.h
├── Makefile
└── navilos.ld

 

Timer 역시 uart 처럼 레지스터 작업을 해야한다. 

다행히 timer register setting은 그렇게 길지 않다.

 

#ifndef HAL_RVPB_TIMER_H_
#define HAL_RVPB_TIMER_H_

typedef union TimerXControl_t
{
    uint32_t all;
    struct {
        uint32_t OneShot:1;     //0
        uint32_t TimerSize:1;   //1
        uint32_t TimerPre:2;    //3:2
        uint32_t Reserved0:1;   //4
        uint32_t IntEnable:1;   //5
        uint32_t TimerMode:1;   //6
        uint32_t TimerEn:1;     //7
        uint32_t Reserved1:24;  //31:8
    } bits;
} TimerXControl_t;

typedef union TimerXRIS_t
{
    uint32_t all;
    struct {
        uint32_t TimerXRIS:1;   //0
        uint32_t Reserved:31;   //31:1
    } bits;
} TimerXRIS_t;

typedef union TimerXMIS_t
{
    uint32_t all;
    struct {
        uint32_t TimerXMIS:1;   //0
        uint32_t Reserved:31;   //31:1
    } bits;
} TimerXMIS_t;

typedef struct Timer_t
{
    uint32_t        timerxload;     // 0x00
    uint32_t        timerxvalue;    // 0x04
    TimerXControl_t timerxcontrol;  // 0x08
    uint32_t        timerxintclr;   // 0x0C
    TimerXRIS_t     timerxris;      // 0x10
    TimerXMIS_t     timerxmis;      // 0x14
    uint32_t        timerxbgload;   // 0x18
} Timer_t;

#define TIMER_CPU_BASE  0x10011000
#define TIMER_INTERRUPT 36

#define TIMER_FREERUNNING   0
#define TIMER_PERIOIC       1

#define TIMER_16BIT_COUNTER 0
#define TIMER_32BIT_COUNTER 1

#define TIMER_10HZ_INTERVAL       (32768 * 4)

#endif /* HAL_RVPB_TIMER_H_ */

Timer_t의 구조체를 보면 레지스터 7개가 정의되어있다.

{

  timerxload     : 카운터의 목표 값을 지정하는 레지스터이다.

  timerxvalue    : 감소하는 레지스터이다.(timerxload의 값을 복사 후 감소하다가 0이되면 interrupt가 발생한다.

  timerxcontrol  : 타이머 하드웨어의 속성을 설정하는 레지스터이다.

  timerxintclr     : 인터럽트 처리가 완료 되었음을 알려주는 레지스터이다.

  나머지 3개 레지스터는 사용하지 않는다.

}

 

TimerXConrtol_t 의 구조체는 타이머 하드웨어의 속성을 설정한다.

{

  OneShot  : 1이면 타이머 인터럽트가 한 번 발생하고 타이머가 바로 꺼진다.(다시 켜려면 수동으로 설정)

  TimerSize : timerxload와 timerxvalue의 크기를 설정한다. 0이면 16비트만 사용하고 1이면 32비트를 사용한다.

  TimerPre  : 클럭마다 카운터를 줄일지,16번마다 줄일지 256번마다 줄일지를 설정한다.

  IntEnable  : 타이머 하드웨어의 인터럽트를 켠다.

  TimerMode : timerxload를 사용할지 사용하지 않을지를 결정한다. 0이면 x

 

->0으로 했을 경우 timerxvalue는 최댓값 (0xFFFF or 0xFFFFFFFF) 부터 0 까지 카운트가 내려가야 인터럽트가 발생한다. 이런 모드를 free running 모드한다.

 

timerxload에 지정한 값 부터 0까지 카운트가 내려가면 피라오딕 모드라고 한다.

 

여기서는 피라오딕 모드로 사용한다.

 

TIMER_CPU_BASE는 타이머 하드웨어 레지스터가 할당되어 있는 메모리 주소이다. 

RealViewPB의 데이터시트에 보면 0x10011000에 타이머 0과 1이 할당되어 있다.

TIMER_INTERRUPT는 타이머 하드웨어가 인터럽트를 발생시킬 때 GIC에 전달하는 Interrupt ID 이다.

 

타이머 구조체를 만들었으니 이제 타이머 초기화 한다.

Hal_timer_init()

void Hal_timer_init(void)
{
    // inerface reset
    Timer->timerxcontrol.bits.TimerEn = 0;
    Timer->timerxcontrol.bits.TimerMode = 0;
    Timer->timerxcontrol.bits.OneShot = 0;
    Timer->timerxcontrol.bits.TimerSize = 0;
    Timer->timerxcontrol.bits.TimerPre = 0;
    Timer->timerxcontrol.bits.IntEnable = 1;
    Timer->timerxload = 0;
    Timer->timerxvalue = 0xFFFFFFFF;

    // set periodic mode
    Timer->timerxcontrol.bits.TimerMode = TIMER_PERIOIC;
    Timer->timerxcontrol.bits.TimerSize = TIMER_32BIT_COUNTER;
    Timer->timerxcontrol.bits.OneShot = 0;
    Timer->timerxcontrol.bits.TimerPre = 0;
    Timer->timerxcontrol.bits.IntEnable = 1;

    uint32_t interval = TIMER_10HZ_INTERVAL / 100;

    Timer->timerxload = interval;
    Timer->timerxcontrol.bits.TimerEn = 1;

    internal_1ms_counter = 0;

    // Register Timer interrupt handler
    Hal_interrupt_enable(TIMER_INTERRUPT);
    Hal_interrupt_register_handler(interrupt_handler, TIMER_INTERRUPT);
}


uint32_t Hal_timer_get_1ms_counter(void)
{
    return internal_1ms_counter;
}

static void interrupt_handler(void)
{
    internal_1ms_counter++;

    Timer->timerxintclr = 1;
}

 책에서는

#define TIMER_1MZ_INTERVAL (1024 *1024)

            TIMER_1MZ_NTERVAL / 1000;

 핵심으로 설명하고 있다.

RealViewPB는 타이머 클럭 소스로 1MHz를 받거나 32.768kHz를 선택하여 사용할 수 있다.

 

QEMU에서 RealViewPB는 1Mhz를 써서 사용한다.

 

기준 클럭이 1MHz라고 설명하고 있지만 책 설명이 잘못된거 같다..

 

책에서는 1MHz 확인하기 위해 printf_test 함수에 다음과 같은 코드를 삽입했다.

Print_Test()

static void printf_test(void)
{
    char *str = "test str";
    int i = 5;
    uint32_t* sysctrl0 = (uint32_t*)0x10001000;

    debug_printf("%s\n", "hello printf");
    debug_printf("output string pointer: %s\n",str);
    debug_printf("%u = 5\n",i);
    debug_printf("dec= %u hex = %x \n", 0xff, 0xff);
    debug_printf("sysctrl value = %x\n", *sysctrl0);
}

 

sysctrl0이 모두 0이므로 TIMCLK =1MHZ라고 하였지만 sysctrl이 0이면 TIMCLK 이 아닌 REFCLK 이다.

 

https://static.docs.arm.com/dui0417/d/DUI0417D_realview_platform_baseboard_for_cortex_a8_ug.pdf

 

REFCLK = 32.768kHz 이고 Timer_init 함수 설정 된 Timer 레지스터 값들은 다음과 같다.

(gdb) p *Timer
$4 = {timerxload = 327, timerxvalue = 327, timerxcontrol = {all = 98, bits = {
      OneShot = 0, TimerSize = 1, TimerPre = 0, Reserved0 = 0, IntEnable = 1, 
      TimerMode = 1, TimerEn = 0, Reserved1 = 0}}, timerxintclr = 0, 
  timerxris = {all = 0, bits = {TimerXRIS = 0, Reserved = 0}}, timerxmis = {
    all = 0, bits = {TimerXMIS = 0, Reserved = 0}}, timerxbgload = 327}

 

timerxload 값은 임의로 설정하여 Test 하고 있어서 위 코드와 다르다

 

timerxvalue 값이 감소 하면서 0이 되면 interrupt가 발생한다.

 

timerxload 공식은 다음과 같다.(1ms 마다 interrupt 발생)

TIMCLK 값은 32768이 된다.

실제 책에서는 TIMCLK이 1M로 설명하고 있지만 코드에서는 (32768 *4)로 되어있다. *4를 

왜 한건지는 모르겠다 예상하는 바로는 qemu 에서 실제 32768 맞추지를 못해 임의로 *4로 해서

1ms 맞춘거 같다. (PRESCALE = 0으로 설정되어있어 DIV by 1 이고 TIMCLKENX =1 로 상관 없다)