타이머
- 임베디드 시스템에서 타이머는
목표 카운트 레지스터와 측정 카운트 레지스터를 조합해서 만든다
- 목표 카운트 레지스터에 목표하는 값을 지정한다
- 측정 카운트 레지스터의 값은 증가 혹은 감소한다
- 두 레지스터 값이 같아지면 인터럽트를 발생시킨다
- RealViewPB에서는 SP804라는 타이머 하드웨어를 가지고 있다
타이머 하드웨어 초기화
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;
- Timer_t 타입 구조체에는 7개의 레지스터가 정의되어있다
- timerxload는 목표 카운트 레지스터이다
- timerxvalue는 측정 카운트 레지스터이다
- 타이머가 켜지면, timerxvalue에 timerxload 값을 복사하고, timerxvalue가 감소한다
- timerxvalue가 0이 되면 인터럽트가 발생한다
- timerxcontrol은 하드웨어 속성을 설정하는 레지스터이다
- timerxintclr은 인터럽트 처리 완료를 나타낸다
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;
- TimerXControl_t는 타이머 하드웨어 속성을 설정한다
- Oneshot이 1이면, 타이머 인터럽트 한 번 발생 후 타이머가 바로 꺼진다
- TimerSize는 timerxload, timerxvalue의 크기를 설정한다
- 0이면 16bit / 1이면 32bit를 모두 사용하게 된다
- TimerPre는 1클럭/16클럭/256클럭마다 카운터를 줄일지 설정한다
- IntEnable은 타이머의 하드웨어 인터럽트를 켠다
- TimerMode는 free-running/periodic모드 중 선택할 수 있게 한다
- free-running 모드에서는 timerxload를 사용하지 않고, 0xFFFF(32bit인 경우 0xFFFFFFFF)부터 0까지 카운트가 내려가야 인터럽트가 발생한다
- periodic 모드에서는 timerxload에 지정한 값부터 0이 되면 인터럽트가 발생한다
- 우리는 delay 함수를 구현할 것이므로 periodic 모드를 사용한다
- timerEn은 타이머 하드웨어 전체를 켜고 끄는 레지스터이다
#define TIMER_CPU_BASE 0x10011000
#define TIMER_INTERRUPT 36
#define TIMER_1MZ_INTERVAL (1024 * 1024)
- TIMER_CPU_BASE는 타이머 하드웨어 레지스터가 할당된 메모리 주소이다
- TIMER_INTERRUPT는 GIC에 전달되는 타이머 인터럽트 번호이다
- TIMER_1MZ_INTERVAL는 1MHz 클럭에서 1초간 발생하는 클럭 횟수이다
volatile Timer_t* Timer = (Timer_t*)TIMER_CPU_BASE;
- TIMER_CPU_BASE 주소값을 이용해서 Timer 객체를 선언하자
void Hal_timer_init()
{
// 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;
// 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_1ms = TIMER_10HZ_INTERVAL / 1000;
Timer->timerxload = interval_1ms;
Timer->timerxcontrol.bits.TimerEn = 1;
internal_1ms_counter = 0;
// register interrupt
Hal_interrupt_enable(TIMER_INTERRUPT);
Hal_interrupt_register_handler(interrupt_handler, TIMER_INTERRUPT);
}
- Timer init 함수는 위와 같다
- 우선 Timer의 control bits들을 초기화한다
- 이후 Timer를 periodic 모드로 변경한다
- timerxload에 1ms에 해당하는 값을 넣고 Timer를 enable 시킨다
- RealViewPB의 타이머는 클럭으로 1MHz를 사용, 즉 1초에 1024 * 1024번의 clock이 발생한다
- 1ms = 1/1000초이므로,
TIMER_10HZ_INTERVAL / 1000를 interval_1ms로 사용한다
- 1ms가 몇번 지났는지 체크하는
internal_1ms_counter를 0으로 초기화 한다
- 타이머 관련 interrupt를 등록한다
static uint32_t internal_1ms_counter;
static void interrupt_handler(void)
{
internal_1ms_counter++;
Timer->timerxintclr = 1;
}
- interrupt_handler에서는 인터럽트가 발생하였을 때 internal_1ms_counter를 증가시킨다
- 이후 timerxintclr 레지스터를 통해 인터럽트 처리 완료를 타이머에 전달한다
delay 함수
타이머 카운트 overflow
- 1ms마다 타이머 인터럽트가 발생하면
internal_1ms_counter에 1씩 값이 증가한다
internal_1ms_counter은 32bit 변수로, 최대 크기까지 변수가 증가하면 오버플로우가 발생한다
- 0xffffffff / 1000 = 4294967초 = 1193시간 = 약 50일 이후에는 오버플로우가 발생할 것 이다
- 몇 달, 몇 년 이상 전원이 켜져있는 시스템이 있을 수 있으므로 타이머 카운터의 오버플로우에도 정상 동작이 필요하다
delay 함수
- delay 함수는 특정 시간 만큼 아무일도 하지 않고 시간이 지날 때 까지 기다리는 함수이다
- delay 함수와 같은 유틸리티 함수는 stdlib.h에 정의한다
# include/stdlib.h
#ifndef LIB_STDLIB_H_
#define LIB_STDLIB_H_
void delay(uint32_t ms);
#endif /* LIB_STDLIB_H_ */
- delay 함수는 타이머 카운터
internal_1ms_counter 값을 사용해야 한다
internal_1ms_counter는 로컬 전역 변수로 선언되어 있다
- 이 값을 읽기 위한 인터페이스가 필요하다
- HalTimer에 타이머 카운터 값을 읽기 위한 인터페이스를 선언 및 구현하자
uint32_t Hal_timer_get_1ms_counter()
{
return internal_1ms_counter;
}
- 만든 함수를 통해 delay 함수를 아래와 같이 구현할 수 있다
# lib/stdlib.c
void delay(uint32_t ms)
{
uint32_t goal = Hal_timer_get_1ms_counter() + ms;
while(goal != Hal_timer_get_1ms_counter());
}
- 타이머 카운터 값이 goal에 도달했는지 확인하는 방법은
<와 != 두가지 연산자를 활용하는 방법 두가지가 있다
- 보통의 경우에는
<를 사용하는게 더 안전하다
- 그러나
< 방식은 goal 변수에 overflow가 일어나는 경우 잘못 동작할 수 있다
- 따라서, 위 코드처럼
!= 방식을 사용해야 overflow에도 의도대록 동작하게 된다
동작 확인
# Main.c
static void Hw_init(void)
{
...
Hal_timer_init();
}
static void Timer_test(void)
{
while(true)
{
debug_printf("cur count: %u\n", Hal_timer_get_1ms_counter());
delay(100);
}
}
void main(void)
{
Hw_init();
...
Timer_test();
}
- Main 코드에서 delay 함수와
internal_1ms_counter 값이 의도대로 증가하는지 확인해보면 아래와 같은 결과를 확인할 수 있다
