STM32 TIM_TimeBase

김상인·2025년 4월 28일

오늘은 타이머3를 이용한 실습을 진행했다. STM32F103에 여러가지 타이머 종류가 있다는데 책에는 종류만 적어두고 자세하게 적혀있지않아 찍먹으로 찾아봤다.

  • advenced-control timer: 고급 타이머라고 하는데 주로 복잡한 사이클의 PWM, 모터제어 등의 어렵고 복잡한 동작에 쓰이는 타이머 / TIM1 핀 해당
  • general-purpose timer: 범용 타이머, 일반적인 PWM, 주기적인 인터럽트 등 복잡한 작업 이외에 제일 많이 쓰이는 타이머 / TIM2, 3, 4 핀 해당
  • watchdog timer: 안정성, 신뢰성을 위한 장치, 시스템이 오류나 무한루프에 빠졌든지 주기적으로 확인하고 스스로 리셋시키는 타이머 / MCU 내장
  • SysTick timer: ARM Cortex-M 프로세서 안에 기본적으로 내장된 타이머, 주로 운영체제(RTOS)나 딜레이 함수에 씀 / MCU 내장

이렇게 간단하게 알아보았고 타이머3(TIM3)를 이용한다니까 범용 타이머에 해당되는 모양이다. 1초 주기로 인터럽트를 발생시켜 LED를 깜빡이게하는 실험이다. 프로젝트 생성은 이전과 동일하고 "TIM_TimeBase"라고 이름을 지어줬다. 마찬가지로 내부클럭만 사용하기 때문에 HSE, LSE는 비활성화 시켜주고 GPIO설정에 LED 설정이 잘 되어있는지 확인한다.

클럭 설정에서는 그대로 64MHz인지 확인해준다. 타이머 설정을 위해 타이머 탭에서 TIM3을 눌러주면 해당 타이머의 설정을 확인할 수 있다. Clock Source부분을 Internal Clock으로 설정해주고, 아래 Parameter Settings를 눌러 Prescaler는 63, Counter Period는 999, auto-reload preload는 활성화 해준다.

prescaler와 counter period는 실제로 설정한 값에 +1이 적용돼 각 64, 1000으로 계산되니 원하는 값 -1을 적어넣어야 한다. 이유는 count를 0부터 세서 원하는 카운트에 -1을 해야 오버플로우가 발생하고, 이 오버플로우 발생하는 타이밍에 인터럽트가 발생하기 때문에 의도적으로 -1을 해준 것이다.

그리고 prescaler랑 counter period, auto-reload preload가 정확히 어떤 용어인지 찾아보면

  • prescaler: 클럭을 느리게 나누는 분주기
  • counter period(ARR: Auto-Reload Register): 카운터 몇번 셀지 설정
  • auto-reload preload(ARPE): counter period 변경 시점 조절

ARPE는 무슨말이냐하면, 예를 들어 1000까지 설정하고 500까지 카운트했을 때 갑작스럽게 600으로 변경한다면, 그 즉시 600으로 설정되게하거나(비활성) 1000까지 한 사이클돌고 다음부터 600으로 설정되게(활성)하는 것이다.
위의 값들로 타이머 주기의 계산식은 아래와 같다.

타이머 주기 = (Prescaler + 1) × (ARR + 1) / MCU 클럭

그럼 현재 우리가 설정한 주기는 0.001초마다 인터럽트가 발생한다는 뜻이다.

이제 카운터가 ARR값과 일치하면 인터럽트가 발생할 수 있도록 NVIC Settings에서 TIM3 global interrupt를 활성화 해주고 코드를 생성해준다.

생성된 코드에서 TIM3에 대해 생성된 코드도 확인할 수 있다. 인터럽트가 발생하면 TIM3_IRQHandler() -> HAL_TIM_IRQHandler() -> HAL_TIM_PeriodElapsedCallback() 순서로 호출된다고 한다. 그러면 저번과 마찬가지로 콜백 함수에 사용자 코드를 정의하면 되는건가? 확인해보자.

저번과 마찬가지로 __weak 심볼이 적혀있다. 이 함수를 main.c에서 정의해주자.

/* USER CODE BEGIN PV */
volatile int gTimerCnt;
/* USER CODE END PV */
.
.
.
/* USER CODE BEGIN 4 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
	gTimerCnt++;
	if(gTimerCnt == 1000) {
		gTimerCnt = 0;
		HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
	}
}
/* USER CODE END 4 */

gTimerCnt가 0.001초마다 증가하는데 1000이 되면 1초이므로 1초마다 LED가 깜빡이는 인터럽트가 발생한다. 여기서 오랜만에 volatile이란 변수가 나왔는데 volatile은 컴파일러에게 해당 변수를 사용할 때, 최적화하지않고 항상 메모리에 접근하게 하는 것이다. 외부에 의해 해당 변수값이 바뀌는데(메모리에 있는 값이 바뀜) 컴파일러는 최적화에 의해 메모리가 아닌 저장레지스터에서 읽어오면, 원하는대로 작동하지 않을 가능성이 크다. 그래서 외부에 의해 값이 바뀔 수 있는 변수들은 volatile을 써줘야한다.

실행해서 결과를 확인해보면?

profile
이것저것 해보기

0개의 댓글