
Input Capture는 Timer의 특수 기능으로, 외부 핀에서 들어오는 신호의 특정 엣지(Rising/Falling)를 감지하여 그 순간의 타이머 카운터 값을 자동으로 캡처(저장)하는 기능입니다.
기본 개념
외부 신호 입력 → 엣지 감지 → 카운터 값 캡처 → 인터럽트 발생
예시:
Timer 카운터: 0 → 1 → 2 → 3 → 4 → 5 → ...
외부 신호: _____|‾‾‾‾‾‾‾‾|_____
↑ Rising Edge
캡처 값: CCR1 = 5 (이 시점의 카운터 값)
왜 필요한가?
1. 주파수 측정
- RPM 센서 (회전 속도)
- 펄스 카운터
2. 펄스 폭 측정
- 초음파 센서 (거리 측정)
- RC 수신기 신호
3. 이벤트 타이밍
- 신호 도착 시간
- 펄스 간격 측정
STM32 Timer Input Capture 블록도
외부 핀 (TIMx_CHy)
↓
Input Filter (노이즈 제거)
↓
Edge Detector (Rising/Falling/Both)
↓
Prescaler (1, 2, 4, 8분주)
↓
Capture/Compare Register (CCRy)
↓
Interrupt (TIMx_CCy_IRQn)
레지스터 구성
// 주요 레지스터
TIMx->CNT // 현재 카운터 값
TIMx->CCR1 // 캡처된 값 (Channel 1)
TIMx->CCR2 // 캡처된 값 (Channel 2)
TIMx->CCER // Capture/Compare Enable Register
TIMx->CCMR1 // Capture/Compare Mode Register 1
TIMx->DIER // DMA/Interrupt Enable Register
TIMx->SR // Status Register
Mode 1: 단일 엣지 캡처
설정: Rising Edge 감지
동작: Rising Edge마다 카운터 값 캡처
신호: ___|‾‾‾|___|‾‾‾|___
↑ ↑ ↑
캡처: T1 T2 T3
주파수 = 1 / (T2 - T1)
Mode 2: 양 엣지 캡처
설정: Rising + Falling Edge 감지
동작: 모든 엣지에서 캡처
신호: ___|‾‾‾|___|‾‾‾|___
↑ ↓ ↑ ↓ ↑
캡처: T1 T2 T3 T4 T5
HIGH 시간 = T2 - T1
LOW 시간 = T3 - T2
주기 = T3 - T1
듀티 = (T2-T1) / (T3-T1) * 100%
Mode 3: PWM Input 모드
설정: 2개 채널 동시 사용
동작: CH1=주기, CH2=듀티
신호: ___|‾‾‾‾|____|‾‾‾‾|___
CH1: ↑ ↑ ↑ (Rising, 주기 측정)
CH2: ↓ ↓ (Falling, 듀티 측정)
자동으로 주파수와 듀티 사이클 계산
Timer 설정 (TIM2 예시)
1. Pinout & Configuration
- Timers → TIM2 선택
- Clock Source: Internal Clock
- Channel1: Input Capture direct mode
2. Parameter Settings
- Prescaler: 84-1 (1MHz 카운터)
- Counter Period: 0xFFFF-1 (최대값)
- Input Capture Channel1:
- Polarity: Rising Edge
- IC Selection: Direct
- Prescaler: DIV1
- Filter: 0
3. NVIC Settings
- TIM2 global interrupt: Enabled
4. GPIO Settings
- PA0: TIM2_CH1 (Input)
생성된 초기화 코드
void MX_TIM2_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_IC_InitTypeDef sConfigIC = {0};
htim2.Instance = TIM2;
htim2.Init.Prescaler = 84-1; // 84MHz → 1MHz
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 0xFFFF-1;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
if (HAL_TIM_IC_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
if (HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
}
기본 설정
void TIM2_IC_Init_Manual(void)
{
// 1. 클럭 활성화
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
// 2. GPIO 설정 (PA0: TIM2_CH1)
GPIOA->MODER &= ~(3U << 0); // Clear
GPIOA->MODER |= (2U << 0); // Alternate function
GPIOA->AFR[0] &= ~(0xF << 0); // Clear
GPIOA->AFR[0] |= (1U << 0); // AF1 (TIM2)
// 3. Timer 기본 설정
TIM2->PSC = 84 - 1; // 1MHz
TIM2->ARR = 0xFFFF; // 최대값
// 4. Input Capture 설정
// CCMR1: CC1S[1:0] = 01 (IC1 mapped to TI1)
TIM2->CCMR1 &= ~TIM_CCMR1_CC1S;
TIM2->CCMR1 |= TIM_CCMR1_CC1S_0;
// IC1 Prescaler = 1 (no division)
TIM2->CCMR1 &= ~TIM_CCMR1_IC1PSC;
// IC1 Filter = 0 (no filter)
TIM2->CCMR1 &= ~TIM_CCMR1_IC1F;
// 5. Polarity 설정 (Rising Edge)
TIM2->CCER &= ~TIM_CCER_CC1P; // Rising edge
// 6. Capture Enable
TIM2->CCER |= TIM_CCER_CC1E;
// 7. Interrupt 활성화
TIM2->DIER |= TIM_DIER_CC1IE;
NVIC_EnableIRQ(TIM2_IRQn);
// 8. Timer 시작
TIM2->CR1 |= TIM_CR1_CEN;
}
Rising Edge 감지
// HAL 방식
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
// 레지스터 방식
TIM2->CCER &= ~TIM_CCER_CC1P; // CC1P = 0
TIM2->CCER &= ~TIM_CCER_CC1NP; // CC1NP = 0
Falling Edge 감지
// HAL 방식
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING;
// 레지스터 방식
TIM2->CCER |= TIM_CCER_CC1P; // CC1P = 1
TIM2->CCER &= ~TIM_CCER_CC1NP; // CC1NP = 0
Both Edges 감지
// HAL 방식
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_BOTHEDGE;
// 레지스터 방식
TIM2->CCER |= TIM_CCER_CC1P; // CC1P = 1
TIM2->CCER |= TIM_CCER_CC1NP; // CC1NP = 1
주파수 측정 방법
방법 1: 주기 측정
- 연속된 2개의 Rising Edge 시간 차이 측정
- 주파수 = 1 / 주기
방법 2: 펄스 카운팅
- 일정 시간(예: 1초) 동안 펄스 개수 카운트
- 주파수 = 펄스 개수 / 시간
주기 측정이 더 정확하므로 일반적으로 방법 1 사용
계산 공식
타이머 주파수 = APB 클럭 / (Prescaler + 1)
측정 시간 = 캡처 차이 / 타이머 주파수
입력 주파수 = 1 / 측정 시간
예시:
APB 클럭 = 84MHz
Prescaler = 84 - 1
타이머 주파수 = 84MHz / 84 = 1MHz = 1,000,000 Hz
캡처1 = 1000
캡처2 = 2000
차이 = 1000 틱
측정 시간 = 1000 / 1,000,000 = 0.001초 = 1ms
입력 주파수 = 1 / 0.001 = 1000 Hz = 1 kHz
구현 코드
#include "main.h"
#include "stdio.h"
// 주파수 측정 변수
volatile uint32_t ic_value1 = 0;
volatile uint32_t ic_value2 = 0;
volatile uint8_t is_first_captured = 0;
volatile uint32_t frequency = 0;
// Input Capture 인터럽트 콜백
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
{
if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
if (is_first_captured == 0)
{
// 첫 번째 엣지 캡처
ic_value1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
is_first_captured = 1;
}
else
{
// 두 번째 엣지 캡처
ic_value2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
// 주파수 계산
uint32_t difference;
if (ic_value2 >= ic_value1)
{
difference = ic_value2 - ic_value1;
}
else
{
// 오버플로우 발생
difference = (0xFFFF - ic_value1) + ic_value2 + 1;
}
// 주파수 계산
// Timer 주파수 = 1MHz (84MHz / 84)
// 입력 주파수 = 1,000,000 / difference
if (difference > 0)
{
frequency = 1000000 / difference;
}
// 다음 측정을 위한 초기화
is_first_captured = 0;
}
}
}
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_TIM2_Init();
printf("\r\n=== Frequency Measurement ===\r\n");
printf("Connect signal to PA0 (TIM2_CH1)\r\n\r\n");
// Input Capture 시작
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
while (1)
{
if (frequency > 0)
{
printf("Frequency: %lu Hz\r\n", frequency);
}
else
{
printf("Waiting for signal...\r\n");
}
HAL_Delay(1000);
}
}
문제점
1. 낮은 주파수 측정 시 오버플로우
- 예: 10Hz 신호, 주기 = 100ms
- 필요 틱 = 100,000 (1MHz 기준)
- 16비트 타이머 최대 = 65,535
- 오버플로우 발생!
2. 높은 주파수 측정 시 분해능 부족
- 예: 100kHz 신호, 주기 = 10μs
- 틱 = 10 (1MHz 기준)
- 낮은 정밀도
해결 방법 1: 32비트 타이머 사용
// TIM2, TIM5는 32비트 타이머
void MX_TIM2_Init(void)
{
htim2.Instance = TIM2;
htim2.Init.Prescaler = 84-1; // 1MHz
htim2.Init.Period = 0xFFFFFFFF; // 32비트 최대값
// 최대 측정 시간 = 4,294,967,295 / 1,000,000 = 4294초
// 최소 측정 주파수 = 1 / 4294 = 0.0002 Hz
// ... 나머지 설정
}
// 오버플로우 처리 불필요
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
{
if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
if (is_first_captured == 0)
{
ic_value1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
is_first_captured = 1;
}
else
{
ic_value2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
// 32비트이므로 단순 뺄셈
uint32_t difference = ic_value2 - ic_value1;
if (difference > 0)
{
frequency = 1000000 / difference;
}
is_first_captured = 0;
}
}
}
}
해결 방법 2: 적응형 Prescaler
void adjust_prescaler(uint32_t estimated_freq)
{
uint32_t new_prescaler;
if (estimated_freq < 100) // 낮은 주파수
{
new_prescaler = 840 - 1; // 100kHz 타이머
}
else if (estimated_freq < 10000)
{
new_prescaler = 84 - 1; // 1MHz 타이머
}
else // 높은 주파수
{
new_prescaler = 8 - 1; // 10.5MHz 타이머
}
// Prescaler 변경
HAL_TIM_IC_Stop_IT(&htim2, TIM_CHANNEL_1);
__HAL_TIM_SET_PRESCALER(&htim2, new_prescaler);
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
}
해결 방법 3: 오버플로우 카운터
volatile uint32_t overflow_count = 0;
// 오버플로우 인터럽트
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
{
overflow_count++;
}
}
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
{
if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
static uint32_t overflow1 = 0;
static uint32_t overflow2 = 0;
if (is_first_captured == 0)
{
ic_value1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
overflow1 = overflow_count;
is_first_captured = 1;
}
else
{
ic_value2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
overflow2 = overflow_count;
// 전체 틱 계산 (오버플로우 포함)
uint64_t total_ticks =
(uint64_t)(overflow2 - overflow1) * 0xFFFF +
(ic_value2 - ic_value1);
if (total_ticks > 0)
{
frequency = 1000000ULL / total_ticks;
}
is_first_captured = 0;
}
}
}
}
int main(void)
{
// ...
// Update 인터럽트도 활성화
HAL_TIM_Base_Start_IT(&htim2);
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
// ...
}
이동 평균 필터
#define FREQ_BUFFER_SIZE 10
uint32_t freq_buffer[FREQ_BUFFER_SIZE];
uint8_t freq_buffer_index = 0;
uint8_t freq_buffer_full = 0;
uint32_t get_average_frequency(void)
{
uint32_t sum = 0;
uint8_t count = freq_buffer_full ? FREQ_BUFFER_SIZE : freq_buffer_index;
if (count == 0)
return 0;
for (uint8_t i = 0; i < count; i++)
{
sum += freq_buffer[i];
}
return sum / count;
}
void add_frequency_sample(uint32_t freq)
{
freq_buffer[freq_buffer_index] = freq;
freq_buffer_index = (freq_buffer_index + 1) % FREQ_BUFFER_SIZE;
if (freq_buffer_index == 0)
{
freq_buffer_full = 1;
}
}
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
{
if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
if (is_first_captured == 0)
{
ic_value1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
is_first_captured = 1;
}
else
{
ic_value2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
uint32_t difference = ic_value2 - ic_value1;
if (difference > 0)
{
uint32_t freq = 1000000 / difference;
add_frequency_sample(freq);
frequency = get_average_frequency();
}
is_first_captured = 0;
}
}
}
}
중앙값 필터
// 노이즈에 더 강함
uint32_t get_median_frequency(void)
{
if (!freq_buffer_full && freq_buffer_index == 0)
return 0;
uint8_t count = freq_buffer_full ? FREQ_BUFFER_SIZE : freq_buffer_index;
// 복사본 생성 (원본 유지)
uint32_t temp[FREQ_BUFFER_SIZE];
memcpy(temp, freq_buffer, count * sizeof(uint32_t));
// 버블 정렬
for (uint8_t i = 0; i < count - 1; i++)
{
for (uint8_t j = 0; j < count - i - 1; j++)
{
if (temp[j] > temp[j + 1])
{
uint32_t swap = temp[j];
temp[j] = temp[j + 1];
temp[j + 1] = swap;
}
}
}
// 중앙값 반환
return temp[count / 2];
}
양 엣지 캡처 방식
typedef enum {
EDGE_RISING,
EDGE_FALLING
} EdgeType_t;
volatile EdgeType_t current_edge = EDGE_RISING;
volatile uint32_t rising_time = 0;
volatile uint32_t falling_time = 0;
volatile uint32_t high_time = 0;
volatile uint32_t low_time = 0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
{
if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
uint32_t captured_value = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
if (current_edge == EDGE_RISING)
{
rising_time = captured_value;
// Falling Edge로 변경
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1,
TIM_INPUTCHANNELPOLARITY_FALLING);
current_edge = EDGE_FALLING;
}
else // EDGE_FALLING
{
falling_time = captured_value;
// HIGH 시간 계산
if (falling_time >= rising_time)
{
high_time = falling_time - rising_time;
}
else
{
high_time = (0xFFFFFFFF - rising_time) + falling_time + 1;
}
// Rising Edge로 변경
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1,
TIM_INPUTCHANNELPOLARITY_RISING);
current_edge = EDGE_RISING;
}
}
}
}
int main(void)
{
// ...
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
while (1)
{
if (high_time > 0)
{
float high_ms = (float)high_time / 1000.0f; // μs → ms
printf("HIGH time: %.2f ms\r\n", high_ms);
}
HAL_Delay(1000);
}
}
듀티 사이클 계산
typedef struct {
uint32_t period; // 주기 (μs)
uint32_t high_time; // HIGH 시간 (μs)
uint32_t low_time; // LOW 시간 (μs)
float duty_cycle; // 듀티 사이클 (%)
uint32_t frequency; // 주파수 (Hz)
} PulseInfo_t;
volatile PulseInfo_t pulse_info = {0};
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
{
if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
static uint32_t rising1 = 0;
static uint32_t rising2 = 0;
static uint32_t falling = 0;
static EdgeType_t edge = EDGE_RISING;
uint32_t captured = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
if (edge == EDGE_RISING)
{
rising2 = rising1;
rising1 = captured;
// 주기 계산 (연속된 2개의 Rising Edge)
if (rising2 > 0)
{
pulse_info.period = rising1 - rising2;
if (pulse_info.period > 0)
{
pulse_info.frequency = 1000000 / pulse_info.period;
}
}
// Falling Edge로 전환
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1,
TIM_INPUTCHANNELPOLARITY_FALLING);
edge = EDGE_FALLING;
}
else // EDGE_FALLING
{
falling = captured;
// HIGH 시간 계산
pulse_info.high_time = falling - rising1;
// LOW 시간 계산
if (pulse_info.period > pulse_info.high_time)
{
pulse_info.low_time = pulse_info.period - pulse_info.high_time;
}
// 듀티 사이클 계산
if (pulse_info.period > 0)
{
pulse_info.duty_cycle =
(float)pulse_info.high_time * 100.0f / pulse_info.period;
}
// Rising Edge로 전환
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1,
TIM_INPUTCHANNELPOLARITY_RISING);
edge = EDGE_RISING;
}
}
}
}
int main(void)
{
// ...
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
while (1)
{
if (pulse_info.frequency > 0)
{
printf("Frequency: %lu Hz\r\n", pulse_info.frequency);
printf("Period: %lu μs\r\n", pulse_info.period);
printf("HIGH: %lu μs\r\n", pulse_info.high_time);
printf("LOW: %lu μs\r\n", pulse_info.low_time);
printf("Duty: %.1f%%\r\n\r\n", pulse_info.duty_cycle);
}
HAL_Delay(1000);
}
}
PWM Input 모드는 2개의 Input Capture 채널을 동시에 사용하여 한 번에 주기와 듀티 사이클을 측정하는 특수 모드입니다.
동작 원리
CH1: Rising Edge 감지 → 주기 측정
CH2: Falling Edge 감지 → HIGH 시간 측정
입력 신호: ___|‾‾‾‾|____|‾‾‾‾|____
CH1: ↑ ↑ ↑ (주기)
CH2: ↓ ↓ (HIGH 시간)
자동으로:
- CCR1: 주기 값
- CCR2: HIGH 시간 값
- 인터럽트: CH1만 발생
장점
1. 한 번의 캡처로 주기와 듀티 모두 측정
2. Polarity 전환 불필요
3. 더 정확한 측정
4. 코드 간소화
설정 방법
1. Pinout & Configuration
- Timers → TIM2 선택
- Channel1: PWM Input on CH1
- (Channel2는 자동으로 연결됨)
2. Parameter Settings
- Prescaler: 84-1
- Counter Period: 0xFFFFFFFF
- Input Capture Channel1:
- Polarity: Rising Edge
- IC Selection: Direct
- Prescaler: DIV1
- Filter: 0
- Input Capture Channel2:
- Polarity: Falling Edge
- IC Selection: Indirect
- Prescaler: DIV1
- Filter: 0
3. NVIC Settings
- TIM2 global interrupt: Enabled
생성된 코드
void MX_TIM2_Init(void)
{
TIM_IC_InitTypeDef sConfigIC = {0};
htim2.Instance = TIM2;
htim2.Init.Prescaler = 84-1;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 0xFFFFFFFF;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_IC_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
// CH1: Rising Edge (주기 측정)
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
if (HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
// CH2: Falling Edge (듀티 측정)
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING;
sConfigIC.ICSelection = TIM_ICSELECTION_INDIRECTTI;
if (HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_2) != HAL_OK)
{
Error_Handler();
}
}
기본 구현
#include "main.h"
#include "stdio.h"
volatile uint32_t period = 0;
volatile uint32_t high_time = 0;
volatile float duty_cycle = 0;
volatile uint32_t frequency = 0;
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
{
if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
// CH1에서 주기 읽기
period = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
// CH2에서 HIGH 시간 읽기
high_time = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2);
// 주파수 계산
if (period > 0)
{
frequency = 1000000 / period; // Hz
duty_cycle = (float)high_time * 100.0f / period; // %
}
}
}
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_TIM2_Init();
printf("\r\n=== PWM Input Mode ===\r\n");
printf("Connect PWM signal to PA0\r\n\r\n");
// PWM Input 시작
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_2);
while (1)
{
if (frequency > 0)
{
printf("Frequency: %lu Hz\r\n", frequency);
printf("Period: %lu μs\r\n", period);
printf("HIGH: %lu μs\r\n", high_time);
printf("Duty: %.1f%%\r\n\r\n", duty_cycle);
}
else
{
printf("Waiting for PWM signal...\r\n");
}
HAL_Delay(1000);
}
}
통계 정보 추가
typedef struct {
uint32_t frequency; // 주파수 (Hz)
uint32_t period; // 주기 (μs)
uint32_t high_time; // HIGH 시간 (μs)
uint32_t low_time; // LOW 시간 (μs)
float duty_cycle; // 듀티 사이클 (%)
// 통계
uint32_t min_freq;
uint32_t max_freq;
float avg_freq;
uint32_t sample_count;
uint32_t last_update; // 마지막 업데이트 시간
uint8_t is_valid; // 유효한 데이터 여부
} PWMInputInfo_t;
volatile PWMInputInfo_t pwm_info = {0};
void update_pwm_statistics(void)
{
static uint32_t freq_sum = 0;
// 최소/최대 업데이트
if (pwm_info.sample_count == 0)
{
pwm_info.min_freq = pwm_info.frequency;
pwm_info.max_freq = pwm_info.frequency;
}
else
{
if (pwm_info.frequency < pwm_info.min_freq)
pwm_info.min_freq = pwm_info.frequency;
if (pwm_info.frequency > pwm_info.max_freq)
pwm_info.max_freq = pwm_info.frequency;
}
// 평균 계산
freq_sum += pwm_info.frequency;
pwm_info.sample_count++;
pwm_info.avg_freq = (float)freq_sum / pwm_info.sample_count;
}
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
{
if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
// 데이터 읽기
pwm_info.period = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
pwm_info.high_time = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2);
// 계산
if (pwm_info.period > 0)
{
pwm_info.frequency = 1000000 / pwm_info.period;
pwm_info.duty_cycle = (float)pwm_info.high_time * 100.0f / pwm_info.period;
pwm_info.low_time = pwm_info.period - pwm_info.high_time;
// 통계 업데이트
update_pwm_statistics();
pwm_info.last_update = HAL_GetTick();
pwm_info.is_valid = 1;
}
}
}
}
void check_pwm_timeout(void)
{
// 1초 이상 업데이트 없으면 무효 처리
if (HAL_GetTick() - pwm_info.last_update > 1000)
{
pwm_info.is_valid = 0;
}
}
int main(void)
{
// ...
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_2);
while (1)
{
check_pwm_timeout();
if (pwm_info.is_valid)
{
printf("=== PWM Input ===\r\n");
printf("Frequency: %lu Hz\r\n", pwm_info.frequency);
printf("Period: %lu μs\r\n", pwm_info.period);
printf("HIGH: %lu μs\r\n", pwm_info.high_time);
printf("LOW: %lu μs\r\n", pwm_info.low_time);
printf("Duty: %.1f%%\r\n", pwm_info.duty_cycle);
printf("\n--- Statistics ---\r\n");
printf("Min: %lu Hz\r\n", pwm_info.min_freq);
printf("Max: %lu Hz\r\n", pwm_info.max_freq);
printf("Avg: %.1f Hz\r\n", pwm_info.avg_freq);
printf("Samples: %lu\r\n\r\n", pwm_info.sample_count);
}
else
{
printf("No signal detected\r\n");
}
HAL_Delay(1000);
}
}
RC PWM 규격
주파수: 50Hz (20ms 주기)
펄스 폭: 1000μs ~ 2000μs
- 1000μs: 최소 (0%)
- 1500μs: 중립 (50%)
- 2000μs: 최대 (100%)
신호 예시:
_____|‾‾‾|_______________|‾‾‾|_______________
1ms 19ms 1.5ms 18.5ms
전체 구현
#include "main.h"
#include "stdio.h"
#define RC_MIN_PULSE 1000 // μs
#define RC_CENTER_PULSE 1500 // μs
#define RC_MAX_PULSE 2000 // μs
#define RC_DEADBAND 50 // μs
typedef struct {
uint32_t pulse_width; // 펄스 폭 (μs)
int16_t position; // -100 ~ +100
uint8_t is_valid; // 신호 유효성
uint32_t last_update; // 마지막 업데이트
} RCChannel_t;
RCChannel_t rc_ch[4]; // 4채널
// 펄스 폭을 위치 값으로 변환
int16_t pulse_to_position(uint32_t pulse)
{
// 범위 제한
if (pulse < RC_MIN_PULSE)
pulse = RC_MIN_PULSE;
if (pulse > RC_MAX_PULSE)
pulse = RC_MAX_PULSE;
// 중립점 데드밴드
if (pulse >= RC_CENTER_PULSE - RC_DEADBAND &&
pulse <= RC_CENTER_PULSE + RC_DEADBAND)
{
return 0;
}
// -100 ~ +100 스케일링
int16_t position;
if (pulse < RC_CENTER_PULSE)
{
// 1000 ~ 1500 → -100 ~ 0
position = (int16_t)((pulse - RC_MIN_PULSE) * 100 /
(RC_CENTER_PULSE - RC_MIN_PULSE)) - 100;
}
else
{
// 1500 ~ 2000 → 0 ~ +100
position = (int16_t)((pulse - RC_CENTER_PULSE) * 100 /
(RC_MAX_PULSE - RC_CENTER_PULSE));
}
return position;
}
// PWM Input 콜백
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
uint32_t pulse_width;
uint8_t ch_index;
// 채널 식별
if (htim->Instance == TIM2 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
ch_index = 0;
pulse_width = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2);
}
else if (htim->Instance == TIM3 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
ch_index = 1;
pulse_width = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2);
}
else if (htim->Instance == TIM4 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
ch_index = 2;
pulse_width = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2);
}
else if (htim->Instance == TIM5 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
ch_index = 3;
pulse_width = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2);
}
else
{
return;
}
// 유효성 검사
if (pulse_width >= RC_MIN_PULSE && pulse_width <= RC_MAX_PULSE)
{
rc_ch[ch_index].pulse_width = pulse_width;
rc_ch[ch_index].position = pulse_to_position(pulse_width);
rc_ch[ch_index].is_valid = 1;
rc_ch[ch_index].last_update = HAL_GetTick();
}
}
void check_rc_timeout(void)
{
uint32_t now = HAL_GetTick();
for (uint8_t i = 0; i < 4; i++)
{
if (now - rc_ch[i].last_update > 100) // 100ms 타임아웃
{
rc_ch[i].is_valid = 0;
rc_ch[i].position = 0;
}
}
}
void print_rc_status(void)
{
printf("\r\n=== RC Receiver Status ===\r\n");
for (uint8_t i = 0; i < 4; i++)
{
printf("CH%d: ", i + 1);
if (rc_ch[i].is_valid)
{
printf("%4lu μs [%+4d] ", rc_ch[i].pulse_width, rc_ch[i].position);
// 바 그래프
printf("[");
int16_t pos = rc_ch[i].position;
for (int8_t j = -100; j <= 100; j += 10)
{
if (j < pos)
printf("=");
else if (j == pos || (j - 10 < pos && pos < j))
printf("*");
else
printf(" ");
}
printf("]\r\n");
}
else
{
printf("NO SIGNAL\r\n");
}
}
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_TIM2_Init();
MX_TIM3_Init();
MX_TIM4_Init();
MX_TIM5_Init();
printf("\r\n=== RC Receiver Decoder ===\r\n");
printf("4 Channel PWM Input\r\n\r\n");
// 모든 채널 시작
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_2);
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_2);
HAL_TIM_IC_Start_IT(&htim4, TIM_CHANNEL_1);
HAL_TIM_IC_Start_IT(&htim4, TIM_CHANNEL_2);
HAL_TIM_IC_Start_IT(&htim5, TIM_CHANNEL_1);
HAL_TIM_IC_Start_IT(&htim5, TIM_CHANNEL_2);
while (1)
{
check_rc_timeout();
print_rc_status();
HAL_Delay(100);
}
}
RPM 계산 원리
펄스 간격 측정 → 주파수 계산 → RPM 변환
예시:
엔코더: 1회전당 4펄스
측정 주파수: 100Hz
→ 회전수 = 100Hz / 4펄스 = 25 RPS
→ RPM = 25 * 60 = 1500 RPM
구현
#include "main.h"
#include "stdio.h"
#define PULSES_PER_REVOLUTION 4 // 엔코더 펄스 수
typedef struct {
uint32_t pulse_frequency; // Hz
uint32_t rpm; // RPM
float rps; // RPS (회전/초)
uint32_t last_update;
uint8_t is_valid;
} RPMSensor_t;
volatile RPMSensor_t rpm_sensor = {0};
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
static uint32_t ic_value1 = 0;
static uint32_t ic_value2 = 0;
static uint8_t is_first = 0;
if (htim->Instance == TIM2 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
if (is_first == 0)
{
ic_value1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
is_first = 1;
}
else
{
ic_value2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
uint32_t difference = ic_value2 - ic_value1;
if (difference > 0)
{
// 펄스 주파수 계산 (1MHz 타이머)
rpm_sensor.pulse_frequency = 1000000 / difference;
// RPS 계산
rpm_sensor.rps = (float)rpm_sensor.pulse_frequency / PULSES_PER_REVOLUTION;
// RPM 계산
rpm_sensor.rpm = (uint32_t)(rpm_sensor.rps * 60);
rpm_sensor.last_update = HAL_GetTick();
rpm_sensor.is_valid = 1;
}
is_first = 0;
}
}
}
void check_rpm_timeout(void)
{
if (HAL_GetTick() - rpm_sensor.last_update > 500)
{
rpm_sensor.is_valid = 0;
rpm_sensor.rpm = 0;
}
}
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_TIM2_Init();
printf("\r\n=== RPM Sensor ===\r\n");
printf("Pulses per revolution: %d\r\n\r\n", PULSES_PER_REVOLUTION);
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
while (1)
{
check_rpm_timeout();
if (rpm_sensor.is_valid)
{
printf("RPM: %5lu | RPS: %5.1f | Freq: %lu Hz\r\n",
rpm_sensor.rpm,
rpm_sensor.rps,
rpm_sensor.pulse_frequency);
}
else
{
printf("RPM: ----- (No rotation)\r\n");
}
HAL_Delay(100);
}
}
PID 속도 제어
typedef struct {
float Kp;
float Ki;
float Kd;
float integral;
float prev_error;
float output;
} PID_t;
PID_t speed_pid = {
.Kp = 0.5f,
.Ki = 0.1f,
.Kd = 0.01f,
.integral = 0.0f,
.prev_error = 0.0f,
.output = 0.0f
};
float calculate_pid(PID_t *pid, float setpoint, float measured, float dt)
{
float error = setpoint - measured;
// P항
float p_term = pid->Kp * error;
// I항
pid->integral += error * dt;
float i_term = pid->Ki * pid->integral;
// D항
float derivative = (error - pid->prev_error) / dt;
float d_term = pid->Kd * derivative;
// 출력
pid->output = p_term + i_term + d_term;
// 제한
if (pid->output > 100.0f)
pid->output = 100.0f;
if (pid->output < 0.0f)
pid->output = 0.0f;
pid->prev_error = error;
return pid->output;
}
void control_motor_speed(uint32_t target_rpm)
{
static uint32_t last_control = 0;
uint32_t now = HAL_GetTick();
if (now - last_control >= 50) // 50ms마다
{
float dt = (now - last_control) / 1000.0f;
if (rpm_sensor.is_valid)
{
float pwm_duty = calculate_pid(&speed_pid,
(float)target_rpm,
(float)rpm_sensor.rpm,
dt);
// PWM 출력 설정
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, (uint16_t)pwm_duty);
printf("Target: %lu RPM | Current: %lu RPM | PWM: %.1f%%\r\n",
target_rpm, rpm_sensor.rpm, pwm_duty);
}
last_control = now;
}
}
int main(void)
{
// ...
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
uint32_t target_rpm = 1000; // 목표 RPM
while (1)
{
check_rpm_timeout();
control_motor_speed(target_rpm);
HAL_Delay(10);
}
}
시스템 설계 순서상 노이즈 필터링을 우선 시행한 후 PWM 필터링(PWM to DC conversion/Smoothing)을 진행하는게 맞습니다.
Input Filter 설정
// CubeMX에서 설정 또는 코드로 설정
void configure_input_filter(void)
{
TIM_IC_InitTypeDef sConfigIC = {0};
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 8; // 0~15, 값이 클수록 강한 필터
HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1);
}
// 필터 레벨 설명
// 0: 필터 없음
// 1-3: 약한 필터 (빠른 신호)
// 4-8: 중간 필터 (일반적)
// 9-15: 강한 필터 (느린 신호, 노이즈 많음)
외부 RC 필터
입력 핀 --- [저항 1kΩ] --- 측정점 --- [커패시터 100nF] --- GND
차단 주파수 = 1 / (2π * R * C)
= 1 / (2π * 1000 * 0.0000001)
= 1591 Hz
글리치 제거
글리치(Glitch)란 아주 짧은 시간동안 발생하는 예기치 않은 가짜 신호를 의미합니다.
#define MIN_PULSE_WIDTH 10 // 최소 펄스 폭 (μs)
#define MAX_PULSE_WIDTH 100000 // 최대 펄스 폭
uint8_t is_valid_pulse(uint32_t pulse_width)
{
return (pulse_width >= MIN_PULSE_WIDTH &&
pulse_width <= MAX_PULSE_WIDTH);
}
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
{
if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
static uint32_t last_value = 0;
uint32_t current_value = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
uint32_t difference = current_value - last_value;
// 유효성 검사
if (is_valid_pulse(difference))
{
// 처리
frequency = 1000000 / difference;
}
else
{
// 무시 (글리치)
}
last_value = current_value;
}
}
}
변화율 제한
#define MAX_FREQ_CHANGE 1000 // 최대 주파수 변화량 (Hz)
uint8_t is_reasonable_change(uint32_t new_freq, uint32_t old_freq)
{
if (old_freq == 0)
return 1; // 첫 측정
int32_t change = (int32_t)new_freq - (int32_t)old_freq;
return (abs(change) <= MAX_FREQ_CHANGE);
}
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
// ...
uint32_t new_frequency = 1000000 / difference;
if (is_reasonable_change(new_frequency, frequency))
{
frequency = new_frequency;
}
else
{
// 급격한 변화 → 노이즈로 판단, 무시
}
// ...
}
다양한 주파수를 측정할 수 있는 주파수 카운터를 구현하세요.
요구사항
힌트
typedef struct {
uint32_t frequency;
uint32_t min_freq;
uint32_t max_freq;
float avg_freq;
uint32_t sample_count;
} FreqCounter_t;
PWM 신호의 듀티 사이클을 실시간으로 모니터링하세요.
요구사항
힌트
typedef struct {
uint32_t timestamp;
float duty_cycle;
} DutyHistory_t;
DutyHistory_t history[10];
홀 센서를 이용한 모터 RPM 제어 시스템을 구현하세요.
요구사항
힌트
typedef struct {
uint32_t target_rpm;
uint32_t current_rpm;
float pwm_output;
PID_t pid;
} MotorControl_t;
증상
주파수가 계속 변함
예: 1000Hz → 980Hz → 1020Hz → 995Hz ...
원인 및 해결
// 원인 1: 노이즈
// 해결: Input Filter 설정
sConfigIC.ICFilter = 8;
// 원인 2: 타이머 분해능 부족
// 해결: Prescaler 조정
htim2.Init.Prescaler = 84 - 1; // 1MHz
// → 84 / 10 - 1; // 8.4MHz (더 정밀)
// 원인 3: 소프트웨어 처리
// 해결: 이동 평균 필터 사용
frequency = get_average_frequency();
증상
10Hz 이하 신호 측정 불가
타이머 오버플로우 발생
원인 및 해결
// 원인: 16비트 타이머 오버플로우
// 10Hz = 100ms 주기
// 1MHz 타이머: 100,000 틱 필요
// 16비트 최대: 65,535 → 오버플로우!
// 해결 1: 32비트 타이머 사용 (TIM2, TIM5)
htim2.Init.Period = 0xFFFFFFFF;
// 해결 2: Prescaler 증가
htim2.Init.Prescaler = 840 - 1; // 100kHz
// 100ms = 10,000 틱 → OK
// 해결 3: 오버플로우 카운터
volatile uint32_t overflow_count = 0;
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM2)
overflow_count++;
}
증상
신호는 들어오는데 콜백이 호출되지 않음
진단 및 해결
// 1. 인터럽트 활성화 확인
void MX_NVIC_Init(void)
{
HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM2_IRQn); // 필수!
}
// 2. Input Capture 시작 확인
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1); // 필수!
// 3. 채널 Enable 확인
TIM2->CCER |= TIM_CCER_CC1E;
// 4. 인터럽트 Enable 확인
TIM2->DIER |= TIM_DIER_CC1IE;
// 5. 핀 설정 확인
// PA0 = TIM2_CH1 (AF1)
GPIOA->MODER |= (2U << 0); // Alternate function
GPIOA->AFR[0] |= (1U << 0); // AF1
증상
듀티 사이클이 100% 이상으로 나옴
또는 음수 값
원인 및 해결
// 원인: 채널 설정 오류
// CH1: Direct, CH2: Indirect 확인
// 올바른 설정
void MX_TIM2_Init(void)
{
// CH1: Rising, Direct
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; // Direct
HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1);
// CH2: Falling, Indirect
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING;
sConfigIC.ICSelection = TIM_ICSELECTION_INDIRECTTI; // Indirect
HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_2);
}
// 값 계산 확인
period = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
high_time = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2);
// high_time은 항상 period보다 작아야 함
if (high_time > period)
{
// 설정 오류!
printf("ERROR: Configuration problem\r\n");
}
Input Capture 기본
주파수 측정
펄스 폭 측정
PWM Input 모드
실전 응용
1. Input Capture 시작
HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
2. 값 읽기
uint32_t value = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1);
3. 주파수 계산
frequency = timer_clock / captured_difference;
4. 듀티 계산
duty = (float)high_time * 100.0f / period;
| 타이머 비트 | Prescaler | 타이머 주파수 | 최소 주파수 | 최대 주파수 |
|---|---|---|---|---|
| 16비트 | 84-1 | 1MHz | 15.3Hz | 1MHz |
| 16비트 | 840-1 | 100kHz | 1.5Hz | 100kHz |
| 32비트 | 84-1 | 1MHz | 0.0002Hz | 1MHz |
| 32비트 | 8-1 | 10.5MHz | 0.002Hz | 10.5MHz |