ComputerScience & Embedded/NUCLEO & CAN Tranceiver

Timers (Polling mode / Interrupt mode)

leecrossun 2025. 3. 1. 16:00

Introduction to Timers

타이머는 마이크로컨트롤러의 주변장치 중 하나

  • 시간 기반 생성(Timebase Generation) → 필요한 지연(Delay) 생성
  • 신호 주파수 측정 및 파형 주기 측정
  • 다양한 출력 파형 생성
  • 펄스 폭(Pulse Width) 측정
  • PWM (Pulse Width Modulation) 신호 생성
  • ADC/DAC 변환 트리거 등 외부 장치 제어

타이머의 동작 원리

타이머는 기본적으로 0부터 특정한 값까지 세거나(Up Counting), 특정한 값에서 0까지 감소하는(Down Counting) 방식으로 동작

예를 들어, 타이머의 레지스터에 5라는 값을 설정하면:

  1. 0 → 1 → 2 → 3 → 4 → 5까지 증가
  2. 5에 도달하면 다시 0으로 롤백(Reset)
  3. 이때 업데이트 이벤트(Update Event) 발생
  4. 이 이벤트는 인터럽트(Interrupt)로 변환되어 프로세서를 중단할 수도 있음

타이머의 시간 간격 (Time Gap) 조절

  • 클럭 주파수가 높으면 타이머가 빠르게 동작
  • 클럭 주파수가 낮으면 타이머가 천천히 동작

 

Types of STM32 Timers

  1. 기본 타이머 (Basic Timers)
    • 거의 모든 STM32 MCU에 포함됨
    • 최소한 하나 이상의 기본 타이머가 존재
  2. 범용 타이머 (General Purpose Timers)
    • 대부분의 STM32 MCU에 포함됨
    • 하지만 일부 MCU에는 없을 수도 있음 → 레퍼런스 매뉴얼 확인 필요
  3. 고급 타이머 (Advanced Timers)
    • 모든 STM32 MCU에 포함되지 않음
    • 해당 MCU에 포함되어 있는지 레퍼런스 매뉴얼에서 확인 필요

**When we can say 16bit timers, it just means that the counter of that timer peripheral is 16bits (counts from 0 to 0Xffff)

 

타이머 번호와 실제 개수의 관계

  • 예를 들어, STM32F100 MCU에는 TIM17이라는 타이머가 존재하지만,이는 이 MCU가 17개의 타이머를 갖고 있다는 의미는 아님
  • TIM17은 단순히 이 MCU에 포함된 특정 타이머일 뿐이며,기본 타이머, 범용 타이머, 고급 타이머 중 하나일 가능성이 있음

 

타이머 이름과 기능의 일관성

  • 같은 이름의 타이머는 서로 다른 MCU에서도 동일한 기능을 가짐
    • 예) STM32F446RETimer1STM32F4411Timer1
      • 동일한 기능과 동작 모드를 가짐
      • 레지스터 구조 및 설정 방법 동일
      • 즉, 한 MCU에서 작성한 타이머 코드가 다른 MCU에서도 그대로 동작 가능
  • 단, 일부 예외적인 경우도 존재

STM32 Basic Timer Assembly

  • 기본 타이머는 단순한 카운팅 엔진을 가짐
  • 주로 Time Base 생성에 사용됨
  • 입출력 채널이 없음 (I/O와 관련 없음)

  1. 카운터(Counter)
    • 16비트 카운터 레지스터(TIMx_CNT) 를 사용하여 값 저장 및 증가
      • You can’t see what is going on inside “Counter” directly through code, you have to use TIMx_CNT register. When you read TIMx_CNT register it gives the snapshot of counter value.
    • 타이머의 카운팅 속도카운트 클럭(Count Clock) 에 의해 결정됨
  2. 프리스케일러(Pre-scaler)
    • 입력 클럭을 분주하여(나눠서) 카운트 클럭을 생성
    • 프리스케일러 레지스터(TIMx_PSC) 를 통해 속도 조절 가능
  3. 타이머 클럭 공급
    • RCC(Reset and Clock Control) 모듈이 타이머에 클럭을 공급
    • RCC → 메인 타이머 클럭제어 블록(Control Block)프리스케일러 → 카운트 클럭
  4. 자동 리로드 레지스터(Auto-reload Register, TIMx_ARR)
    • 타이머의 최댓값을 설정하는 레지스터
    • 카운터 값이 TIMx_ARR에 도달하면 0으로 초기화(롤백)
    • 이때 업데이트 이벤트(Interrupt) 발생 가능

Timer Exercise - Polling Mode

  • 목표: 100ms마다 인터럽트 발생 후 LED 토글(GPIO 변경)
  • 실습을 통해 배우는 내용:
    1. 기본 타이머 설정 방법
    2. 원하는 시간 간격(Delay) 생성 방법
    3. 타이머 인터럽트 활성화 방법
    4. 타이머 IRQ(인터럽트 번호) 확인 및 핸들러 구현
    5. 논리 분석기(Logic Analyzer)를 활용한 검증

  • 타이머 핸들 변수 생성
    • 모든 주변 장치는 핸들 구조체가 필요하며, TIM6의 경우 TIM_HandleTypeDef 구조체 사용
      • stm32f4xx_hal_tim.h
  • 타이머 설정 요소
    • CounterMode는 Basic Timer 에서는 변경할 수 없으며, 항상 업카운트(up-counting) 방식
    • Prescaler와 Period를 설정해야 함.
      • Prescaler value is used to slow down the TIM_CLK. For example, if TIM_CLK = 50MHz and Prescaler = 1 then TIM_CNT_CLK = TIM_CLK/1+Prescaler = 25MHz
      • 프리스케일러는 타이머 클럭을 분할하는 값 을 지정. 즉, "타이머가 동작하는 클럭 속도를 조정" 하는 역할. 이를 이해하려면 타이머 클럭(Timer Clock)이 어떻게 결정되는지 알아야 함
      • 기본적으로 STM32F4의 시스템 클럭은 84MHz 로 설정되지만 현재 프로젝트에서는 시스템 클럭을 설정하는 코드를 구현하지 않았으므로, 기본적으로 16MHz (HSI - 내부 RC 오실레이터) 를 사용
      • TIM6 타이머는 APB1 버스 에 연결되어 있으며 APB1 버스의 기본 클럭 속도는 16MHz
      • Timers drive clock from APB bus via a multiplier
        • APB 페리페럴 clock은 APB 프리스케일러에서 바로 받지만 Timer는 multiplier를 거침
      • 기본적으로 TIM6 타이머 클럭은 16MHz

  • 타이머 클록 설정
    • STM32의 시스템 클록 및 타이머 클록 구조 이해.
    • 기본적으로 HSI(내부 RC 오실레이터, 16MHz) 사용.
    • TIM6은 APB1 버스에 연결되며, APB1 버스 클록은 기본적으로 16MHz.
    • APB1 타이머 클록은 배율(multiplier)을 통해 설정됨.
  • 프리스케일러 값이 15999 → 타이머 클럭이 (16MHz / 16000 = 1kHz) 로 설정됨
  • 주기(Period) 값이 999 → 1초에 1,000번 카운트 → 1초마다 타이머 인터럽트 발생

프리스케일러의 역할

  • 프리스케일러는 타이머 클럭을 나누어 카운터 클럭(counter clock)을 생성
  • 카운터 클럭 = 타이머 클럭 ÷ (프리스케일러 값 + 1)
  • 예시:
    • 타이머 클럭이 16MHz이고, 프리스케일러 값이 0이면 카운터 클럭도 16MHz
    • 프리스케일러 값을 1로 설정하면 카운터 클럭은 8MHz
  • 즉, 프리스케일러 값이 클수록 카운터 클럭은 느려짐

 

타이머의 주기(Period) 설정

  • Auto-Reload Register(ARR)에 주기 값을 설정
  • 타이머는 이 값에 도달하면 다시 0으로 롤백(초기화)되며 반복 실행
  • 주기 값이 0이면 타이머가 동작하지 않으므로, 최소 1 이상의 값을 설정

 

타이머 레지스터 설정

  • 프리스케일러 값 → Prescaler Register (16비트, 0x0000~0xFFFF 범위)
  • 주기 값 → Auto-Reload Register (16비트, 0x0000~0xFFFF 범위)
    • 만약 ARR값이 0인경우 Timer 동작 X
  • 이 값들은 STM32 Cube의 타이머 드라이버를 통해 레지스터에 저장

 

엑셀 시트 활용한 타이머 주기 계산

  • 사용자가 원하는 시간 단위(ms, μs)를 입력하면, 자동으로 타이머 주기(period)가 계산
  • 예제에서는 타이머 클럭 16MHz로 설정
  • Prescaler 값을 조정하여 Counter Clock을 계산:

  • ARR(Auto-Reload Register) 값이 16비트 범위(최대 65535)를 초과하면, Prescaler를 증가시켜야 함

  • 계산된 ARR 값에서 1을 빼야 정확한 타이밍을 얻을 수 있음 (예: ARR = 5일 때 실제 지연 시간은 6μs → 원하는 5μs를 얻으려면 ARR = 4 설정

 

Timer Initialization

  • Init.Prescaler = 24 설정
  • Init.Period = 64K - 1 설정
  • HAL_TIM_Base_Init() 함수 호출하여 타이머 초기화
// main.c
void Timer6_Init(void)
{
	htimer6.Instance = TIM6;
	htimer6.Init.Prescaler = 24;
	htimer6.Init.Period = 64000-1;
	if(HAL_TIM_Base_Init(&htimer6) != HAL_OK) Error_handler();
}

 

MSP Initialization

  • HAL_TIM_Base_MspInit() 함수에서 TIM6 클럭 활성화 및 인터럽트 설정
  • HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn)을 사용하여 인터럽트 활성화
  • HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 15, 0)로 최저 우선순위 설정
// msp.c
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htimer)
{
	// 1) Enable the clock for the TIM6 peripheral
	__HAL_RCC_TIM6_CLK_ENABLE();

	// 2) Enable the ERQ of TIM6
	HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);

	// 3) Setup the priority for TIM6_DAC_IRQn
	HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 15, 0);
}

 

LED Initialization

  • LED 초기화:
    • LED는 GPIOA 포트의 5번 핀으로 연결되어 있다고 가정하고, 해당 핀을 출력 모드로 설정
    • GPIO 초기화 구조체를 생성하고, 해당 핀의 모드를 "Push-Pull"로 설정
    • HAL_GPIO_Init 함수를 사용해 GPIO 핀을 초기화
  • 타이머 시작:
    • 타이머는 HAL_TIM_Base_Start 함수를 호출하여 시작
    • 타이머가 주기 값을 초과하면 타이머의 상태 레지스터에서 업데이트 이벤트가 발생
    • **while**(!(TIM6->SR & TIM_SR_UIF));
      • 업데이트 이벤트가 발생하면 비트마스킹 값이 1이 되고 전체 조건문은 0이 되므로 루프를 빠져나오게 됨 → 즉, 이벤트 발생 시까지 루프에서 대기하는 코드

  • LED 토글:
    • 타이머의 상태 레지스터에서 UIF(Update Interrupt Flag) 비트를 확인하고, 업데이트 이벤트가 발생하면 LED를 토글
    • HAL_GPIO_TogglePin을 사용하여 GPIO 핀을 토글
  • 주기 설정 및 테스트:
    • 100ms, 10ms, 2.5ms 등 다양한 주기 값으로 테스트를 진행
    • 주기 값이 정확하게 설정되었음을 확인하기 위해 로직 분석기를 사용하여 측정
  • 주의 사항:
    • 타이머는 프로세서와 독립적으로 작동하여 프로세서가 잠자기 상태에 있어도 계속 카운팅됨
    • 타이머 상태 레지스터의 UIF 비트는 하드웨어가 설정하고, 소프트웨어는 이를 클리어해야 함
TIM_HandleTypeDef htimer6;

int main(void)
{
	HAL_Init();
	SystemClockConfig();
	GPIO_Init();
	Timer6_Init();

	// Start Timer
	HAL_TIM_Base_Start(&htimer6);

	while(1)
	{
		// Loop until the update event flag is set
		while(!(TIM6->SR & TIM_SR_UIF));
		// The required time delay has been elapsed, then user code can be executed
		TIM6->SR = 0;
		HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
	}


	while(1);
	return 0;
}
void GPIO_Init(void)
{
	__HAL_RCC_GPIOA_CLK_ENABLE();
	GPIO_InitTypeDef ledgpio;
	ledgpio.Pin = GPIO_PIN_5;
	ledgpio.Mode = GPIO_MODE_OUTPUT_PP;
	ledgpio.Pull = GPIO_NOPULL;

	HAL_GPIO_Init(GPIOA, &ledgpio);
}

void Timer6_Init(void)
{
	htimer6.Instance = TIM6;
	htimer6.Init.Prescaler = 24;
	htimer6.Init.Period = 64000-1;
	if(HAL_TIM_Base_Init(&htimer6) != HAL_OK) Error_handler();
}

Timer Exercise - Interrupt Mode

Poiing mode는 프로세서가 주기적으로 상태 레지스터를 polling 하여 업데이트 이벤트를 확인해야 하므로 프로세서가 sleep 모드로 들어갈 수 없다는 단점 존재

반면, Interrupt mode는 타이머가 이벤트 발생 시에만 인터럽트를 발생시키므로 CPU가 다른 작업을 하거나 sleep 모드로 전환할 수 있음 → 저전력 애플리케이션에서 좋음

// main.c
TIM_HandleTypeDef htimer6;

int main(void)
{
	HAL_Init();
	SystemClockConfig();
	GPIO_Init();
	Timer6_Init();

	// Start Timer
	HAL_TIM_Base_Start_IT(&htimer6);
	
	while(1);
	return 0;
}
void GPIO_Init(void)
{
	__HAL_RCC_GPIOA_CLK_ENABLE();
	GPIO_InitTypeDef ledgpio;
	ledgpio.Pin = GPIO_PIN_5;
	ledgpio.Mode = GPIO_MODE_OUTPUT_PP;
	ledgpio.Pull = GPIO_NOPULL;

	HAL_GPIO_Init(GPIOA, &ledgpio);
}

void Timer6_Init(void)
{
	htimer6.Instance = TIM6;
	htimer6.Init.Prescaler = 24;
	htimer6.Init.Period = 64000-1;
	if(HAL_TIM_Base_Init(&htimer6) != HAL_OK) Error_handler();
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
}

//it.c
extern TIM_HandleTypeDef htimer6;
void TIM6_DAC_IRQHandler(void)
{
	HAL_TIM_IRQHandler(&htimer6);
}

 


Output Compare Mode

  • 인터럽트 핸들러 루틴이 3ms가 걸린다면 GPIO 토클이 인터럽트 처리 후에 실행되게 되므로, 3 마이크로초보다 짧은 시간에 토글하는 것은 불가능
  • 즉, 3 마이크로초보다 짧은 주기의 신호는 정확하게 생성할 수 없음
  • 소프트웨어로 GPIO를 제어하는 방식정확한 타이밍을 구현하기 어려움
  • 이 문제를 해결하려면, 타이머 하드웨어를 직접 사용하는 방식(예: Output Compare Mode) 사용
    • 소프트웨어 코드(~~HAL_GPIO_TogglePin~~) 없이 GPIO를 직접 토글
반응형