STM32 #9

홍태준·2026년 1월 24일

STM32

목록 보기
9/15
post-thumbnail

Week 2 Day 4: Input Capture 모드 - 주파수 측정

학습 목표

  • Timer의 Input Capture 모드 동작 원리를 이해한다
  • 외부 신호의 주파수와 듀티 사이클을 측정할 수 있다
  • Rising/Falling Edge 감지 및 Polarity 설정 방법을 익힌다
  • Input Capture 인터럽트를 활용한 정밀 측정을 구현한다
  • PWM 입력 모드를 사용하여 주파수와 듀티를 동시 측정한다

1. Input Capture의 이해

1.1 Input Capture란?

Input Capture는 Timer의 특수 기능으로, 외부 핀에서 들어오는 신호의 특정 엣지(Rising/Falling)를 감지하여 그 순간의 타이머 카운터 값을 자동으로 캡처(저장)하는 기능입니다.

기본 개념

외부 신호 입력 → 엣지 감지 → 카운터 값 캡처 → 인터럽트 발생

예시:
Timer 카운터: 0 → 1 → 2 → 3 → 4 → 5 → ...
외부 신호:    _____|‾‾‾‾‾‾‾‾|_____
                   ↑ Rising Edge
캡처 값: CCR1 = 5 (이 시점의 카운터 값)

왜 필요한가?

1. 주파수 측정
   - RPM 센서 (회전 속도)
   - 펄스 카운터
   
2. 펄스 폭 측정
   - 초음파 센서 (거리 측정)
   - RC 수신기 신호
   
3. 이벤트 타이밍
   - 신호 도착 시간
   - 펄스 간격 측정

1.2 하드웨어 구조

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

1.3 동작 모드

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, 듀티 측정)

자동으로 주파수와 듀티 사이클 계산

2. Input Capture 기본 설정

2.1 CubeMX 설정

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();
  }
}

2.2 수동 설정 (레지스터 직접 제어)

기본 설정

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;
}

2.3 Polarity 설정

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

3. 주파수 측정

3.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

3.2 단일 채널 주파수 측정

구현 코드

#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);
  }
}

3.3 정밀도 향상

문제점

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);
  
  // ...
}

3.4 필터링 및 평균화

이동 평균 필터

#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];
}

4. 펄스 폭 측정

4.1 HIGH/LOW 시간 측정

양 엣지 캡처 방식

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);
  }
}

4.2 듀티 사이클 측정

듀티 사이클 계산

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);
  }
}

5. PWM Input 모드

5.1 PWM Input 모드란?

PWM Input 모드는 2개의 Input Capture 채널을 동시에 사용하여 한 번에 주기와 듀티 사이클을 측정하는 특수 모드입니다.

동작 원리

CH1: Rising Edge 감지 → 주기 측정
CH2: Falling Edge 감지 → HIGH 시간 측정

입력 신호:  ___|‾‾‾‾|____|‾‾‾‾|____
CH1:         ↑       ↑       ↑     (주기)
CH2:             ↓       ↓         (HIGH 시간)

자동으로:
- CCR1: 주기 값
- CCR2: HIGH 시간 값
- 인터럽트: CH1만 발생

장점

1. 한 번의 캡처로 주기와 듀티 모두 측정
2. Polarity 전환 불필요
3. 더 정확한 측정
4. 코드 간소화

5.2 CubeMX 설정

설정 방법

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();
  }
}

5.3 PWM Input 측정 구현

기본 구현

#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);
  }
}

5.4 고급 PWM Input

통계 정보 추가

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);
  }
}

6. 실전 응용: RC 수신기 디코더

6.1 RC PWM 신호 특성

RC PWM 규격

주파수: 50Hz (20ms 주기)
펄스 폭: 1000μs ~ 2000μs
- 1000μs: 최소 (0%)
- 1500μs: 중립 (50%)
- 2000μs: 최대 (100%)

신호 예시:
_____|‾‾‾|_______________|‾‾‾|_______________
     1ms    19ms         1.5ms   18.5ms

6.2 RC 디코더 구현

전체 구현

#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);
  }
}

7. 실전 응용: RPM 센서

7.1 홀 센서 RPM 측정

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);
  }
}

7.2 속도 제어 피드백

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);
  }
}

8. 노이즈 필터링

시스템 설계 순서상 노이즈 필터링을 우선 시행한 후 PWM 필터링(PWM to DC conversion/Smoothing)을 진행하는게 맞습니다.

8.1 하드웨어 필터

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

8.2 소프트웨어 필터

글리치 제거

글리치(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
  {
    // 급격한 변화 → 노이즈로 판단, 무시
  }
  
  // ...
}

9. 실습 과제

과제 1: 주파수 카운터

다양한 주파수를 측정할 수 있는 주파수 카운터를 구현하세요.

요구사항

  • 1Hz ~ 100kHz 범위 측정
  • 0.1% 이내 정확도
  • LCD에 주파수 표시
  • 자동 단위 변환 (Hz, kHz)
  • 최소/최대/평균 표시

힌트

typedef struct {
  uint32_t frequency;
  uint32_t min_freq;
  uint32_t max_freq;
  float avg_freq;
  uint32_t sample_count;
} FreqCounter_t;

과제 2: 듀티 사이클 모니터

PWM 신호의 듀티 사이클을 실시간으로 모니터링하세요.

요구사항

  • PWM Input 모드 사용
  • 주파수와 듀티 동시 표시
  • 그래프 형태로 시각화
  • 변화 추이 저장 (최근 10개)
  • UART로 데이터 전송

힌트

typedef struct {
  uint32_t timestamp;
  float duty_cycle;
} DutyHistory_t;

DutyHistory_t history[10];

과제 3: 모터 RPM 제어

홀 센서를 이용한 모터 RPM 제어 시스템을 구현하세요.

요구사항

  • RPM 측정 (0 ~ 5000 RPM)
  • PID 속도 제어
  • 목표 RPM 설정 가능
  • 현재/목표 RPM 표시
  • 제어 그래프 출력

힌트

typedef struct {
  uint32_t target_rpm;
  uint32_t current_rpm;
  float pwm_output;
  PID_t pid;
} MotorControl_t;

10. 트러블슈팅

문제 1: 측정값이 불안정함

증상

주파수가 계속 변함
예: 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();

문제 2: 낮은 주파수 측정 안됨

증상

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++;
}

문제 3: 인터럽트가 발생하지 않음

증상

신호는 들어오는데 콜백이 호출되지 않음

진단 및 해결

// 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

문제 4: PWM Input 모드에서 값이 이상함

증상

듀티 사이클이 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");
}

11. 학습 정리

오늘 배운 내용

Input Capture 기본

  • 외부 신호의 엣지를 감지하여 타이머 값 캡처
  • Rising/Falling/Both Edge 설정 가능
  • 인터럽트로 캡처 이벤트 처리

주파수 측정

  • 주기 측정 방식 (연속 엣지 간격)
  • 주파수 = 1 / 주기
  • 32비트 타이머로 넓은 범위 측정

펄스 폭 측정

  • 양 엣지 캡처로 HIGH/LOW 시간 측정
  • 듀티 사이클 = HIGH 시간 / 주기 × 100%
  • Polarity 전환 방식

PWM Input 모드

  • 2채널 동시 사용
  • 주기와 듀티 자동 측정
  • 가장 효율적인 방법

실전 응용

  • RC 수신기 디코더
  • RPM 센서 및 속도 제어
  • 노이즈 필터링

핵심 개념

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-11MHz15.3Hz1MHz
16비트840-1100kHz1.5Hz100kHz
32비트84-11MHz0.0002Hz1MHz
32비트8-110.5MHz0.002Hz10.5MHz

권장 사항

  1. 일반 주파수 측정: 32비트 타이머 + PWM Input 모드
  2. 고속 신호: Prescaler 낮게 설정
  3. 저속 신호: Prescaler 높게 설정
  4. 노이즈 환경: Input Filter 사용
  5. 안정성: 이동 평균 또는 중앙값 필터
profile
당신의 코딩 메이트

0개의 댓글