본문 바로가기
임베디드/STM32

19. [STM32] Timer의 기초

by fuhehe 2024. 11. 19.

타이머는 단독 또는 주변장치와 연결되어 사용할 수 있는 MCU제어에서 아주 중요한 장치 중 하나이다.

이 타이머는 일정주기의 펄스나 인터럽트 발생, PWM생성, 엔코더 또는 홀센서등의 카운터 등 다양한 용도로 사용된다.

이번 시간부터는 이 타이머에 대해 알아보자.

 

 

  • 본 블로그는 STM32를 소프트웨어 엔지니어 관점에서 바라본 블로그입니다. 따라서 회로등의 전자공학 관련 내용은 사실과 다를 수 있습니다.
  • 본 블로그에서 사용된 MCU 및 개발보드는 다음과 같습니다.
    1. NUCLEO-F429ZI (STM32F429ZIT LQFP144)
    2. NUCLEO-F439ZI (STM32F439ZIT LQFP144)

 

 

 

 

 

 

 

1. 타이머의 기초

먼저 타이머를 알아보기 전에 타이머의 작동방식과 설정시 사용되는 용어에 대해 살펴보자.

타이머는 기본적으로 내부에 카운트를 위한 16/32bit크기의 각 레지스터 (TIMx_CNT) 가 존재하며 이 카운터 레지스터는 타이머에 인가되는 외부 클럭이 1증가할 때 마다 자신도 1증가한다.

그리고 카운트값이 증가하다 오버플로우가 발생하면 인터럽트를 발생시킨다(감소하면서 언더플로우가 발생하도록 할 수도 있다.). 이 오버플로우 인터럽트로 시간경과를 체크할 수 있지만 각 카운터 레지스터는 길이가 고정되어 있으므로 사용자가 오버플로우를 발생시키는 시점을 변경할 수 있도록 해주어야 다양한 시간설정이 가능해 진다. 따라서 STM32는 이를 위해 타이머 레지스터와 동일한 크기의 ARR레지스터(Auto-reload register - TIMx_ARR)를 둬서 카운트가 ARR값을 초과하는 경우 펄스나 인터럽트를 발생시키도록 하고 있다.

 

기본적인 내용을 살펴보기 위해 CubeMX의 타이머 설정 화면을 기준으로 살펴보자.

[CubeMX의 TIM6 설정]

 

위의 그림은 오직 내부 타이머 용도로만 사용가능한 Basic Timer인 TIM6의 설정화면이다.(다른 General-purpose Timer, Advanced-control Timer도 위 그림의 Counter Settings부분은 동일하다.)

설명을 쉽게 하기 위해 순서를 조정해서 하나씩 살펴보자.

 

A. Counter

위의 설정에는 나와있지 않지만 처음 설명대로 각 타이머는 입력되는 클럭당 1회 증가/감소하는 TIMx_CNT레지스터를 가지고 있다. 여기서 입력되는 외부클럭의 속도가 해당 타이머의 기본적인 속도값이고 이를 다시 ARR, Prescaler를 통해 원하는 속도를 설정해서 사용한다.

이때 입력되는 외부클럭의 속도는 STM32의 각 데이터시트와 CubeMX의 Clock Configuration의 내용을 참조해봐야 한다.

STM32F4 시리즈는 총 14개의 타이머를 가지고 있는데 각 타이머에 입력되는 클럭속도가 다르기 때문이다.

먼저 해당 타이머가 어느 버스에 연결되어 있는지를 알아야 하는데 이는 데이터시트의 20p에 있는 Block diagram을 보면 나와있다.

[STM32F의 Block Diagram일부]

위 Diagram일부에서 보듯 TIM1, TIM8, TIM9, TIM10, TIM11은 APB2에, 나머지는 APB1에 연결되어 있다. 타이머 설정전에 이 내용을 알고 있어야 하며 해당 버스의 타이머 클럭속도는 CubeMX의 Clock Configuration에서 찾으면 된다.

[Clock Configuration]

 

예를들어 위의 그림에서 TIM6의 경우 APB1에 연결되어 있고 APB1 Timer Clock은 84MHz이다.

따라서 TIM6의 Counter레지스터의 경우 84MHz의 속도로 1씩 증가한다.

 

B. Counter Mode

카운터 레지스터의 값을 증가시켜서 레지스터 크기를 초과할 경우 오버플로우를 발생시킬지, 아니면 값을 감소시켜서 0미만이 되는 경우 언더플로우를 발생시킬지 결정하는 값이다.

이 값은 Basic Timer로 분류된 TIM6, TIM7에서는 Up만 설정가능하고 그 외 타이머에서는 다양한 값으로 설정할 수 있다.

  • Up Counting : 0부터 시작해서 ARR값으로 증가후 ARR값을 초과하면 오버플로우 발생 후 0으로 리셋되어 다시 카운팅.
  • Down Counting : ARR부터 시작해서 0까지 감소한 뒤 언더플로우 발생 후 다시 ARR값으로 리셋되어 카운팅.
  • Up/Down Counting(Center Aligned) : 0부터 시작해서 ARR값으로 증가한 뒤 다시 0으로 감소를 반복. ARR값 도달, 0이 될때 각각 이벤트나 인터럽트를 발생시킬 수 있다.

이 설정은 Auto-reload preload설정에 영향을 미친다.

 

C. Count Period - ARR(Auto-reload Register)

각 타이머의 카운터 레지스터의 크기는 고정되어 있으므로 모든 타이머는 해당 카운터 레지스터의 크기만큼 증가한 뒤 한번의 오버플로우가 발생하므로 사용자가 시간을 임의로 조절할 수 없다.

이를 해결하기 위해 각 타이머는 ARR이라는 카운터 레지스터와 동일한 크기의 레지스터를 가지고 있는데 이 값을 사용자가 설정해서 카운터 레지스터가 ARR값을 초과하는 경우 오버플로우 인터럽트를 발생시켜 시간을 조절할 수 있도록 한다. 그리고 카운터 레지스터는 다시 0으로 설정된다.(Counter Mode가 Down인 경우는 ARR값으로 초기화).

이 ARR값은 타이머 동작중에도 변경이 가능하다.

 

D. Prescaler

분주기라고 하는 이 값은 입력된 클럭을 나눠주는 역할을 한다. 하나의 입력 클럭을 이 값으로 쪼갠다는 의미가 아니라 입력 주파수 값을 이 값으로 나눠서 더 느리게 만든다는 의미다.

예를들어 80MHz의 입력 클럭이 있고 Prescaler의 값을 4로 설정하면 80MHz / 4 = 20MHz가 된다.

따라서 우리는 ARR값과 이 Prescaler값을 조정해서 실제 입력 클럭수를 조정해서 사용할 수 있다.

 

E. Auto-reload preload

Preload를 설명하기 위해 한가지 예를 들어 설명하겠다.

TIM6 Basic Timer를 설정하고 있고 ARR을 1000이라고 설정했다고 하자. APB1의 속도는 위의 그림과 같이 84MHz이다.

TIM6의 카운터 레지스터는 16bit이므로 값이 65535가 되면 그 다음 클럭에서 오버플로우가 발생한다.

여기서 ARR을 1000이라고 설정했으므로 오버플로우가 발생하기전 타이머는 1000이 되면 인터럽트를 발생시킨 뒤 카운터 레지스터를 초기화한다.

그런데 실행중 ARR의 값을 1000에서 500으로 변경한다고 치자.(ARR값은 타이머 동작중에도 변경가능하다고 하였다)

이때 ARR을 변경하는 시점에서 카운터의 값이 500 미만이면 크게 문제가 없다. ARR이 변경되고 500이 되는 시점에서 인터럽트가 발생하기 때문이다.

하지만 변경시점에서 현재 카운터 값이 500을 초과한 경우에는 해당 카운터가 카운터 레지스터의 최대값에 도달할 때 까지 인터럽트를 발생시키지 않는다.

즉, 변경시점에서 한번의 인터럽트가 발생하는 주기가 길어질 수도 있다.

 

이런 현상을 방지하기 위해 Preload설정값을 Enable시키면 MCU는 ARR변경 명령을 받았을 때 값을 바로 바꾸지 않고 Preload 레지스터라는 곳에 잠시 저장을 해놓는다.

그리고 이후 오버플로우가 발생한 시점에서 이 값을 ARR에 설정한다.

Preload설정값이 Disable이라면 ARR을 바로 바꿔버린다.

 

만일 Counter Mode를 Up이 아닌 Down으로 설정했다면 위의 문제는 어느 정도 해결된다.

Down으로 설정시 카운트가 1000에서 0으로 감소하므로 최초 ARR을 1000으로 설정하고 중간에 500으로 변경한 경우 현재 카운터 값이 500미만이라 하더라도 어차피 0으로 감소하므로 위의 경우보다 주기가 많이 증가할 일은 없기 때문이다.

 

 

 

이 다섯가지 항목이 타이머를 설정하는 가장 기본적인 구성이다.

 

다음 그림은 레퍼런스의 Basic Timer에 기술되어 있는 Prescaler변경에 따른 카운터 타이밍에 대한 그림이다.

[카운터 타이밍]

이 그림에서 CK_PSC는 Prescaler를 통과하기전의 펄스, 즉 타이머 컨트롤러를 거친 인가된 내부클럭(CK_INT)이다.

그리고 CNT_EN에 의해 타이머가 Enable되는 순간부터 타이머가 동작하기 시작한다.

CK_CNT는 Prescaler를 통과한 클럭이므로 위의 그림에서 최초에 1이었다가 6번째 클럭부터 2가 되는 과정을 표현했고, 아래 그림은 최초 1이었다가 6번째 클럭부터 4가 되는 과정을 표현하고 있다.

그림 제일 아래의 Prescaler counter값이 증가하다가 지정된 Prescaler값에 도달하면 그때 CK_CNT에 의해 Counter 레지스터값을 변경한다.

그리고 ARR이 0xFC로 설정되어 이 부분에서 UEV가 발생한다.

 

 

2. 설정값 산출하기

이번에는 자신이 원하는 정확한 시간의 타이머를 설정하는 방법을 알아보자.

위의 설명대로 타이머의 시간을 정하는 요인은 해당 타이머에 인가되는 클럭속도를 기반으로 ARR과 Prescaler두가지값을 조정해서 타이머 시간을 설정할 수 있다.

ARR, Prescaler 두개의 변수가 있지만 설명을 쉽게하기 위해 일단 Prescaler는 배제하고 ARR한가지 변수만 고려해보자.

 

설정의 목적은 타이머가 우리가 설정한 시간이 경과하면 인터럽트를 발생시키도록 하는 것이다.

따라서 입력 클럭의 한개 클럭 속도에 ARR값을 곱해서 원하는 시간이 나오도록 ARR값을 구하면 된다.

여기서 입력클럭은 Hz단위이므로 이를 초단위로 변환을 해야 하는데 그렇게 되면 아래와 같은 공식이 나온다.

[t=타이머 시간(초), CLK=입력 클럭속도(Hz)]

$$t = \frac{1}{CLK} * ARR$$

 

예를들어 TIM6 타이머를 1ms로 설정하고 싶다면

$$\frac{1}{1000}=\frac{ARR}{84000000}$$

이렇게 되므로 ARR=84000000/1000=84000으로 설정하면 된다.

하지만 TIM6의 카운터는 16비트이므로 ARR역시 16비트이다. 따라서 ARR에 입력가능한 값은 0~65535까지여서 84000 이 값을 그대로 사용할 수는 없다.

이때 Prescaler로 이 값을 조정해서 사용해야 한다. 

따라서 84000이란 값은 그대로 사용할 수 없고 Prescaler값을 2로 설정해서 42000으로 설정한다면 이 값은 ARR에 입력이 가능하다.

그리고 ARR, Prescaler를 적용하기전에 한가지 주의해야 할 사항이 하나 있다.

카운터는 0부터 시작한다. 다시 말해 0으로 설정되는 과정도 하나의 클럭을 소모한다.

따라서 우리가 설정하고자 하는 값은 계산상으로 나온 값에서 1을 빼야 한다. 즉, 42000이 아니라 41999로 설정해야 0~41999 클럭, 총 42000클럭이 설정되는 것이다.

그리고 Prescaler값도 0부터 시작하므로 실제 값에서 1을 빼야 한다.

따라서 이 예에서는 ARR=42000-1, Prescaler=2-1 로 설정을 하면 1㎳속도의 타이머를 설정할 수 있다.

 

ARR과 Prescaler 두가지 변수를 모두 고려해 수식을 다시 만들어 보면 아래와 같다.

$$t(초)=\frac{ARR\times Prescaler}{CLK}$$

 

만일 Hz단위의 결과가 필요하다면 우항을 뒤집으면 된다.

$$ f(Hz)=\frac{CLK}{ ARR\times Prescaler }$$

 

두가지 변수가 있으므로 한꺼번에 두가지 값을 구하기는 어렵다.

위와 같이 Prescaler값을 배제하고 ARR값을 구한 뒤 Prescaler값을 정하거나, Prescaler 입력값은 ARR에 비해 상대적으로 작은 숫자를 가지므로 Prescaler값을 정해놓고 ARR값을 구하는 등 한가지를 먼저 정하고 나머지 변수를 설정해야 한다.(ARR값을 최대한 크게 하는게 분해능을 증가시키는 방법이므로 ARR값은 최대한 크게, Prescaler값은 최소한으로 잡는게 정확도면에서 유리하다)

 

다시한번 말하지만 구한 값에서 -1 해서 CubeMX에 입력하는것도 잊지말자. 

참고로 CubeMX에서는 상수값 뿐 아니라 수식도 그대로 입력이 가능하다. 따라서 41999대신 42000-1 이렇게 써도 된다.

 

아래에 간단하게 이 수식이 적용된 폼을 하나 만들어봤다.

사용하려는 타이머의 CLK를 기본으로 입력하고 각각의 값을 구하는 폼이다.

ARR을 구하고 싶으면 ARR을 제외한 나머지 항목(CLK, Prescaler, 시간)을 입력하고 ARR옆의 계산 버튼을 누르면 되고, Prescaler를 구하고 싶으면 Prescaler를 제외한 CLK, ARR, 시간값을 입력하고 시간옆의 계산 버튼을 누르면 된다.

CLK(APB1/2) MHz  
ARR  
Prescaler  
시간 ms

 

3. STM32F4 시리즈의 타이머 종류

STM32F4의 타이머는 총 14개가 제공된다. 이 중 몇개를 제외하고는 동일 타이밍 조건으로 각 2~4개씩의 별도 채널을 구분해서 사용할 수도 있으므로 하나의 타이머가 여러 용도로 사용할 수 있게 구성이 되어 있다.

14개의 모든 타이머가 동일한 기능을 제공하면 좋겠지만 그렇지는 않고 아래와 같이 기능별로 세개의 방식으로 구분된다.

그리고 모든 타이머는 동작중에 Prescaler값을 수정할 수 있다.

  • Basic Timer : 단순히 지정된 카운트에 의해 인터럽트를 발생시키는 타이머. GPIO 핀연결이 없어 외부 장치의 신호 입출력 용도로는 사용할 수 없고 PWM용도로 사용할 수 없으므로 채널 구분도 없다. TIM6, TIM7 두개의 타이머가 이 방식으로 동작한다.
    • 16bit크기의 ARR(Counter Mode는 Up만 가능)
    • 16bit크기의 Prescaler
    • 별도의 입출력 핀이 없으므로 외부 신호용으로는 사용불가.
    • DAC를 위한 Time구간 설정
  • General-purpose Timer : 이 종류의 타이머는 Basic Timer의 기능을 모두 포함하며 GPIO를 통한 입출력이 가능하다. 이 타이머에 속하는 TIM2~TIM5는 16bit크기의 ARR을 가지며 TIM9~TIM14의 경우 32bit의 ARR크기를 가진다.
    • 16/32bit의 ARR과 Prescaler
    • 최대 4개까지의 독립적인 채널 구성.(각각 Input Capture, Output Compare, PWM, On-Pulse 구성 가능.)
    • 외부 신호 또는 다른 내부 타이머와 동기화 가능
    • 모터등의 위치제어를 위한 엔코더 또는 Hall-Sensor등의 신호분석 용도로 사용가능
  • Advanced-control Timer : TIM1, TIM8이 이 타이머에 해당.
    • 모든 General-purpose Timer기능 지원
    • 타이머 출력 신호를 Reset상태 또는 알려진 상태로 설정하는 Break 입력 기능.
    • 모터 제어, 전력 변환에 관련된 기능 제공

 

4. 타이머의 용도

말그대로 타이머는 내부 카운터에 기반해 시간의 흐름을 체크하는 회로이다. 그래서 동일한 주기의 신호가 필요한 경우등에 주로 사용되는데 이 타이머를 활용하여 여러가지 기능을 수행할 수 있다. 

이번에는 이 타이머를 어떤 용도로 사용할 수 있는지 용도별로 살펴보자.

 

A. 기본적인 타이머

일정 주기의 반복적인 신호 발생용으로 사용. 가장 기본적인 타이머의 용도이다.

 

B. Input Capture

Input Capture는 타이머에 연결된 특정 외부 신호발생시 해당 타이머의 카운터 값을 TIMx_CCRn 레지스터에 기록해 놓는 기능이다. 이 기능을 통해 외부신호의 신호 주기나 펄스 폭을 알아낼 수 있다.

설정에 따라 외부 신호의 Rising/Falling Edge를 설정할 수 있고 둘 다 설정할 수도 있다. 또한 신호의 노이즈를 걸러내기 위한 Input Filter기능도 제공한다.

이 기능은 Basic Timer에서는 지원되지 않는다.

 

C. Output Compare

타이머의 카운터 값이 미리 설정해놓은 TIMx_CCRn 레지스터의 값과 동일할 경우 신호를 발생시키도록 하는 기능이다.

하나의 타이머 기반하에 동기화된 별도 주기의 신호를 발생시키거나 위상차가 있는 신호를 발생시킬 때 주로 사용된다.

이 기능 역시 Basic Timer에서는 지원되지 않는다.

[Output Compare mode]

D. PWM

타이머를 펄스폭 변조(Pulse Width Modulation)방식으로 사용하는 기능이다.

PWM은 정해진 출력전압을 기준으로 듀티비를 적용해 듀티비만큼은 High상태 나머지는 Low상태를 반복하는 펄스를 말한다. 이 방식으로 고정된 출력전압을 듀티비에 의해 아날로그 신호처럼 출력할 수 있게 되므로 LED의 밝기 조절, 모터의 위치제어등에 활용될 수 있다.

 

E. One Pulse Mode

타이머의 입력신호등에 의해 별개의 펄스를 출력할 수 있는 기능이다.

특정 발동 조건에 의해 펄스를 출력하는데 이때 Delay시간, Duration등을 설정해서 펄스를 출력할 수 있다. One Pulse라고 되어 있지만 반복해서 출력할 수도 있다.

 

 

 

5. 예제

이번시간에는 간단히 Basic Timer의 구현만 해보자.

 

0.5초 타이머에 의해 NUCLEO보드의 Green LED를 점멸하는 예제이다.

아래와 같이 TIM6의 Activated란을 체크해 타이머를 활성화시킨다.

그리고 TIM6은 APB1에 연결되어 있으므로 인가되는 클럭은 84MHz이다.

따라서 위의 공식으로 ARR은 42000000이 되는데 TIM6은 16bit이므로 ARR로 42000를 사용하고 Prescaler에 1000을 사용한다.(입력시 -1하는걸 잊지말자)

또, 타이머 신호는 인터럽트를 통해 받을 것이므로 NVIC설정에서 TIM6의 인터럽트에 체크표시를 해야 한다.

 

[TIM6 설정]

 

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if(htim->Instance == TIM6)
  {
    HAL_GPIO_TogglePin(LD1_GPIO_Port, LD1_Pin);    
  }
}


int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART3_UART_Init();
  MX_TIM6_Init();
  /* USER CODE BEGIN 2 */

  HAL_TIM_Base_Start_IT(&htim6);	// 인터럽트를 위해 반드시 호출해야 한다.

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

 

소스는 인터럽트 처리를 위해 HAL_TIM_PeriodElapsedCallback()함수를 재정의 한 것과 main()함수에서 타이머 초기화 후 인터럽트를 받기 위해 HAL_TIM_Base_Start_IT()함수를 호출한게 전부다.

 

만일 인터럽트 방식이 아니라 폴링방식으로 구현하려면 HAL_TIM_Base_Start_IT()함수 대신 HAL_TIM_Base_Start()함수를 먼저 호출해 주어야 한다.

다음은 폴링방식으로 구현된 소스이다.

당연히 CubeMX의 NVIC에서 TIM6인터럽트는 체크 해제해야 한다.

int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART3_UART_Init();
  MX_TIM6_Init();
  /* USER CODE BEGIN 2 */

  HAL_TIM_Base_Start(&htim6);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	if(__HAL_TIM_GET_FLAG(&htim6, TIM_FLAG_UPDATE))
	{
		__HAL_TIM_CLEAR_IT(&htim6, TIM_IT_UPDATE);
		HAL_GPIO_TogglePin(LD1_GPIO_Port, LD1_Pin);
	}
  }
  /* USER CODE END 3 */
}

 

 

7. 마무리

이상으로 STM32의 타이머에 대해 개략적으로 알아보았고 간단히 Basic Timer의 구현도 해보았다.

타이머는 사용처가 다양하므로 그에 따라 구현방식이나 사용방법도 다양해서 모두 설명하기에는 내용이 너무 방대하다.

따라서 이후 장들에서 몇가지만 간추려 알아보기로 하겠다.

 

'임베디드 > STM32' 카테고리의 다른 글

21. [STM32] Timer - PWM  (0) 2025.01.10
20. [STM32] Timer - Output Compare  (0) 2025.01.06
18. [STM32] Clock  (0) 2024.11.14
17. [STM32] Power - PVD  (0) 2024.11.08
16. [STM32] Power - Backup Domain Access  (0) 2024.11.07