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)으로 결정됨.
- IC1F 값이 0이면?
- IC1F 설정값의 의미
- 타이머의 레지스터는 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 설정 코드 구현
- LSE 활성화 함수 작성
- RCC_OscInitTypeDef 구조체를 사용하여 설정을 적용 후 ****활성화 (HAL_RCC_OscConfig)
- 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;
}
반응형