.
├── 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 로 상관 없다)
'Firmware > RTOS' 카테고리의 다른 글
임베디드 OS 개발 프로젝트 11(Scheduler) (0) | 2020.03.08 |
---|---|
임베디드 OS 개발 프로젝트 10(TASK) (0) | 2020.03.05 |
임베디드 OS 개발 프로젝트 8(Uart Interrupt) (0) | 2020.02.15 |
임베디드 OS 개발 프로젝트 7(Uart Interrupt) (0) | 2020.02.15 |
임베디드 OS 개발 프로젝트 6(UART 송수신) (0) | 2020.01.27 |