ComputerScience & Embedded/NUCLEO & CAN Tranceiver

General Purpose Timer : Input Capture Unit

leecrossun 2025. 3. 1. 16:08

Timer with input capture block

  • 타이머를 이용해 연속적인 사각파 신호의 시간 주기를 측정
  • 이 사각파 신호는 외부 신호로 타이머에 연결

 

LSE와 HSI

  • LSE는 32.768 kHz, HSI는 16 MHz 주파수
  • 이 두 신호는 타이머 클럭 주파수가 아니라 외부 신호

 

Input capture channel

  • 타이머에 외부 신호를 입력하는 부분
  • 기본 타이머는 입력 캡처 채널이 없고, General Purpose Timer 에 ****존재
  • 외부 신호의 시간 주기 측정
  • 마이크로컨트롤러의 을 타이머의 입력 캡처 채널에 할당하여 외부 신호를 처리
    • 이 신호는 입력 필터와 엣지 감지기를 거쳐 캡처 레지스터에 저장

 

Timer block diagram

 

  • Basic time base generation unit
    • 프리스케일러, 카운터, 자동 재장전 레지스터 등
  • Capture/Compare register
    • Capture은 Input block 과 연관
    • Compare은 Output block 과 연관
    • 입력 캡처 블록은 Basic time base generation unit ****에 연결되어 외부 신호의 주기를 캡처


Input Capture Exercise

Mechanism to calculate the time frequency of the applied signal

  • 외부 신호의 주파수는 직접 측정하는 것이 아니라, 타이머에서 카운팅 횟수 계산
  • 타이머 카운터는 지정된 주기(예: 1kHz, 1ms마다)를 기준으로 카운팅
  • 외부 신호의 Rising Edge 발생 → 현재 카운터 값을 Capture/Compare Register 에 저장
    • Capture Interrupt 가 발생하고 Capture Callback 함수가 호출됨
  • 첫 번째 상승 신호와 두 번째 상승 신호 사이의 차이를 계산하여 시간 주기를 구함
    • 예를 들어, 첫 번째 상승 신호에서 카운터 값이 54, 두 번째 상승 신호에서 카운터 값이 61이라면, 두 값의 차이는 **7 (**타이머의 해상도가 1ms라면, 신호의 주기는 7ms)
  • 이렇게 계산된 신호의 주기를 바탕으로 주파수 계산

 

1) Initialize the TIMER Input Capture Time base

HAL_TIM_IC_Init(TIM_HandleTypeDef *htim)

// main.c
void TIMER2_Init(void)
{
	htimer2.Instance = TIM2;
	htimer2.Init.CounterMode = TIM_COUNTERMODE_UP;
	htimer2.Init.Period = 0xFFFFFFFF; // Maximum Period
	htimer2.Init.Prescaler = 1; // 50MHz -> 25MHz
	if(HAL_TIM_IC_Init(&htimer2) != HAL_OK) Error_handler();
}

 

2) Configure Input Channel of the Timer

HAL_TIM_IC_ConfigChannel(TIM_HandleTypeDef *htim, **TIM_IC_InitTypeDef*** sConfig, uint32_t Channel)

 

TIM_IC_InitTypeDef 구조체

  • Polarity(극성): 입력 신호의 활성 엣지
    • ICPOLARITY_RISING, ICPOLARITY_FALLING, ICPOLARITY_BOTHEDGE
  • ICSelection(입력 채널 선택): 입력 캡처 블록에 들어가는 신호
    • DIRECTTI, INDIRECTTI, TRC
  • Prescaler(프리스케일러): 입력 신호를 분할하여 속도 조절
  • Filter(필터): 입력 신호를 필터링하여 잡음 제거
    • 0~15, 0일 경우 필터 없이 샘플링 진행

 

Capture / Compare mode register

  • 총 2개의 레지스터가 있으며 타이머의 4개 channel에 대한 모드 관리
    • 레지스터 1 (채널 1, 2), 레지스터 2 (채널 3, 4)
  • Register 1 example )

  • CC1S : input capture 또는 output compare에 채널 1 을 사용할 것인지 여부
    • 00(output), 01(TI1, direct), 10(TI2, indirect), 11(TRC)
  • IC1F : Input Capture 1 Filter
    • IC1F 설정값의 의미
      • IC1F 값이 0이면?
        • 필터 없음 (No filter)
        • 신호가 들어오면 바로 이벤트 발생
        • 입력 신호가 f DTS(타이머 클럭에 의해 결정되는 주파수)로 샘플링됨.
        • 노이즈가 섞여 있어도 바로 이벤트로 감지됨.
      • IC1F 값이 1~15이면?
        • 필터가 활성화됨.
        • 신호가 여러 번(예: 8번) 연속해서 들어와야 이벤트로 인정됨.
        • 값이 클수록 더 많은 연속 신호가 필요함 → 노이즈 필터링 효과 증가.
        • 샘플링 주파수는 f DTS를 몇 배로 나눈 값(예: f DTS/4)으로 결정됨.
  • 타이머의 레지스터는 Output Compare(OC) 설정과 Input Capture(IC) 설정을 공유 (즉, 같은 공간을 사용) → 한가지 모드를 설정하면 다른 설정은 비활성화

void TIMER2_Init(void)
{
	TIM_IC_InitTypeDef timer2IC_Config; // 구조체 정의

	htimer2.Instance = TIM2;
	htimer2.Init.CounterMode = TIM_COUNTERMODE_UP;
	htimer2.Init.Period = 0xFFFFFFFF;
	htimer2.Init.Prescaler = 1;
	if(HAL_TIM_IC_Init(&htimer2) != HAL_OK) Error_handler();

	timer2IC_Config.ICFilter = 0; // 필터링
	timer2IC_Config.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING; // 상승엣지
	timer2IC_Config.ICPrescaler = TIM_ICPSC_DIV1; // 프리스케일러
	timer2IC_Config.ICSelection = TIM_ICSELECTION_DIRECTTI; // 직접입력 모드
	if(HAL_TIM_IC_ConfigChannel(&htimer2, &timer2IC_Config, TIM_CHANNEL_1) != HAL_OK) Error_handler();
}

 

msp.c Initialization

// msp.c
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
	GPIO_InitTypeDef tim2ch1_gpio;
	// 1) Enable the clock for timer 2
	__HAL_RCC_TIM2_CLK_ENABLE();

	// 2) Configure a gpio to behave as timer2 channel 1
	tim2ch1_gpio.Pin = GPIO_PIN_0;
	tim2ch1_gpio.Mode = GPIO_MODE_AF_PP;
	tim2ch1_gpio.Alternate = GPIO_AF1_TIM2;
	HAL_GPIO_Init(GPIOA, &tim2ch1_gpio);

	//3) NVIC setting
	HAL_NVIC_SetPriority(TIM2_IRQn, 15, 0);
	HAL_NVIC_EnableIRQ(TIM2_IRQn);

}

 

it.c Initialization

// it.c
void TIM2_IRQHandler(void)
{
	HAL_TIM_IRQHandler(&htimer2);
}

LSE Configuration

  • Timer2의 채널 1에 클럭을 공급해야 하며, 이를 위해 LSE를 사용
  • LSE는 보드에 기본적으로 포함되어 있지만, 기본적으로 활성화되지 않았으므로 설정 및 활성화 필요
  • 설정 완료 후, GPIO 핀을 통해 출력된 LSE 클럭을 측정
  • LSE의 주파수는 32.768 kHz이며, 주로 RTC(Real-Time Clock) 관련 애플리케이션에서 사용

 

LSE 설정 코드 구현

  1. LSE 활성화 함수 작성
    • RCC_OscInitTypeDef 구조체를 사용하여 설정을 적용 후 ****활성화 (HAL_RCC_OscConfig)
  2. LSE 클럭을 특정 GPIO 핀으로 출력
    • 활성화된 LSE를 특정 GPIO 핀으로 출력하여 측정 가능하도록 설정
    • STM32의 MCO기능을 활용하여 특정 핀으로 클럭을 출력
    • MCO1 (PA8 핀) 또는 MCO2 (PC9 핀) 을 사용할 수 있음

  • HAL_RCC_MCOConfig(RCC_MCO1, RCC_MCOSOURCE_LSE, RCC_MCODIV_1);→ LSE를 PA8(MCO1 핀)으로 출력하도록 설정
  • Logic analyzer을 PA8 핀에 연결하면 LSE clock을 확인할 수 있음
  • LSE_Configuration 에서 오실레이터 초기화를 다시 하면 오류가 발생하므로 기존 SystemClockConfig 코드에 LSE 초기화를 추가
void SystemClockConfig(uint8_t clock_freq)
{
	RCC_OscInitTypeDef osc_init;
	RCC_ClkInitTypeDef clk_init;
	uint32_t FLatency = 0;
	
	// LSE 관련 초기화 추가
	osc_init.OscillatorType = RCC_OSCILLATORTYPE_HSI | RCC_OSCILLATORTYPE_LSE;
	osc_init.HSIState = RCC_HSI_ON;
	osc_init.HSIState = RCC_LSE_ON;
	osc_init.HSICalibrationValue = 16;
	osc_init.PLL.PLLState = RCC_PLL_ON;
	osc_init.PLL.PLLSource = RCC_PLLSOURCE_HSI;

	switch(clock_freq){
	case SYS_CLOCK_FREQ_50_MHZ:
		osc_init.PLL.PLLM = 16;
		osc_init.PLL.PLLN = 100;
		osc_init.PLL.PLLP = 2;
		osc_init.PLL.PLLQ = 2;
		osc_init.PLL.PLLR = 2;

		clk_init.ClockType = RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
		clk_init.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
		clk_init.AHBCLKDivider = RCC_SYSCLK_DIV1;
		clk_init.APB1CLKDivider = RCC_HCLK_DIV2;
		clk_init.APB2CLKDivider = RCC_HCLK_DIV2;
		FLatency = FLASH_ACR_LATENCY_1WS;
		break;
	default:
		return;
	}

	if(HAL_RCC_OscConfig(&osc_init) != HAL_OK) Error_handler();
	if(HAL_RCC_ClockConfig(&clk_init, FLatency) != HAL_OK) Error_handler();


	HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);
	HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

}
// MC0 Configuration
void LSE_Configuration(void)
{
	HAL_RCC_MCOConfig(RCC_MCO1, RCC_MCO1SOURCE_LSE, RCC_MCODIV_1);
}

Timer Input Capture Callback Implementation

캡처 콜백 구현

  • 타이머2 IRQ 핸들러에서 STM32 Cube 라이브러리가 IC_CaptureCallback을 호출
  • IC_CaptureCallback 내부에서 캡처 및 비교 레지스터(CCR) 값을 저장
  • count 변수를 사용하여 첫 번째 이벤트와 두 번째 이벤트를 구분
  • HAL_TIM_GET_COMPARE 매크로를 사용하여 CCR1 값을 읽고 input_captures[]에 저장
// main.c
uint32_t input_captures[2] = {0};
uint8_t count = 1;
uint8_t is_capture_done = FALSE;

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	if(!is_capture_done)
	{
		if(count == 1)
		{
			input_captures[0] = __HAL_TIM_GET_COMPARE(htim, TIM_CHANNEL_1);
			count++;
		}
		else if(count == 2)
		{
			input_captures[1] = __HAL_TIM_GET_COMPARE(htim, TIM_CHANNEL_1);
			count = 1;
			is_capture_done = TRUE;
		}
	}
}

 

시간 주기 계산

  • 두 개의 캡처된 값(input_captures[1] - input_captures[0])의 차이를 계산
    • 만약 첫 번째 값이 두 번째 값보다 크다면 0xFFFFFFFF에서 빼는 방식으로 보정
  • 이후 신호 주기(user_signal_time_period)를 계산하는데, 이는 캡처 차이 × 타이머 분해능(timer2_cnt_res)입니다.

타이머 주파수 계산

  • timer2_cnt_res = 1 / timer2_cnt_freq
  • timer2_cnt_freq = (HAL_RCC_GetPCLK1Freq() * 2) / timer_prescaler + 1

주파수 출력

  • user_signal_frequency = 1 / user_signal_time_period
  • u_printf_float 플래그를 GCC 링크 설정에서 추가

 

int main(void)
{
	uint8_t clock_freq = SYS_CLOCK_FREQ_50_MHZ;
	uint32_t capture_difference = 0;
	double timer_cnt_freq = 0;
	double timer2_cnt_res = 0;
	double user_signal_time_period = 0;
	double user_signal_freq = 0;
	char usr_msg[100];

	HAL_Init();
	SystemClockConfig(clock_freq);
	UART2_Init();
	GPIO_Init();
	TIMER2_Init();

	HAL_TIM_IC_Start_IT(&htimer2, TIM_CHANNEL_1);

	while(1)
	{
		if(is_capture_done)
		{
			if(input_captures[1] > input_captures[0])
				capture_difference = input_captures[1] - input_captures[0];
			else
				capture_difference = (0XFFFFFFFF - input_captures[0]) + input_captures[1];

			timer_cnt_freq = (HAL_RCC_GetPCLK1Freq() * 2) / htimer2.Init.Prescaler+1;
			timer2_cnt_res = 1/timer_cnt_freq;
			user_signal_time_period = capture_difference * timer2_cnt_res;
			user_signal_freq = 1/user_signal_time_period;

			sprintf(usr_msg, "Frequency of the signal applied = %f\r\n", user_signal_freq);
			HAL_UART_Transmit(&huart2, (uint8_t*)usr_msg, strlen(usr_msg), HAL_MAX_DELAY);
		}
	}
	return 0;
}
반응형