Week 2 Day 1: 타이머 기초

학습 목표

  • 타이머의 동작 원리와 구조 이해
  • STM32 타이머의 종류와 특징
  • 클럭 트리 분석 및 타이머 클럭 설정
  • 타이머 인터럽트를 이용한 주기적 작업 처리
  • 타이머 카운터 값 읽기 및 활용

1. 타이머 기본 개념

1.1 타이머란?

타이머(Timer)는 클럭 신호를 세는 하드웨어 카운터입니다. 일정한 주기로 증가 또는 감소하며, 특정 값에 도달하면 이벤트를 발생시킵니다.

타이머의 주요 용도:

  • 정확한 시간 측정
  • 주기적인 인터럽트 생성
  • PWM(Pulse Width Modulation, 펄스 폭 변조) 신호 생성
  • 입력 신호의 주파수/펄스 폭 측정
  • 이벤트 카운팅
  • 타임아웃 처리

1.2 타이머의 기본 구조

Clock Source → Prescaler → Counter → Auto-Reload Register
                              ↓
                         Compare/Match
                              ↓
                          Interrupt

주요 구성 요소:

  1. Clock Source (클럭 소스)

    • 타이머에 공급되는 기준 클럭
    • 일반적으로 시스템 클럭에서 파생
  2. Prescaler (프리스케일러)

    • 클럭을 분주하여 타이머 속도 조절
    • 16비트 값 (0 ~ 65535)
    • 실제 분주비 = Prescaler + 1
  3. Counter (카운터)

    • 클럭을 세는 레지스터
    • 16비트 또는 32비트
    • Up-counting, Down-counting, Up/Down-counting 모드
  4. Auto-Reload Register (ARR)

    • 카운터의 최대값 설정
    • 카운터가 이 값에 도달하면 오버플로우 발생
  5. Capture/Compare Register (CCR)

    • 특정 카운터 값에서 이벤트 발생
    • PWM, Input Capture 등에 사용

카운터가 ARR에 도달한 후 다음 클럭이 오면 Update 이벤트가 발생합니다. 이 때 Update 인터럽트 플래그가 셋(Set)되면서 설정에 따라 CPU에 인터럽트를 요청하게 되고, 동시에 카운터는 0으로 돌아가 처음부터 다시 카운팅을 시작합니다.


1.3 타이머 동작 원리

Up-counting 모드:

Counter Value
    ↑
ARR |------------------------→ (오버플로우, 인터럽트 발생)
    |                    ↓
    |                    |
    |                    |
    0 |______________________|→ Time

타이머 주기 계산:

Timer Update Frequency = Timer Clock / ((Prescaler + 1) * (ARR + 1))

예시:
- Timer Clock: 84 MHz
- Prescaler: 8399 (분주비 8400)
- ARR: 9999 (카운트 10000)

Update Frequency = 84,000,000 / (8400 * 10000) = 1 Hz (1초 주기)

2. STM32 타이머 종류

2.1 타이머 분류

STM32F4는 여러 종류의 타이머를 제공합니다:

Advanced-control Timer (TIM1, TIM8)

  • 16비트 Up/Down/Up-Down 카운터
  • PWM 생성 (최대 4채널)
  • 보완 출력 (모터 제어용)
  • 데드타임 생성
  • 브레이크 입력

General-purpose Timer (TIM2~TIM5)

  • TIM2, TIM5: 32비트 카운터
  • TIM3, TIM4: 16비트 카운터
  • PWM 생성 (최대 4채널)
  • Input Capture, Output Compare
  • Encoder 모드

General-purpose Timer (TIM9~TIM14)

  • 16비트 카운터
  • 간단한 PWM 생성
  • Input Capture, Output Compare

Basic Timer (TIM6, TIM7)

  • 16비트 Up 카운터만 지원
  • DAC 트리거용
  • 간단한 시간 측정

다음은 각 타이머들의 주요 사용처입니다.

TIM1/TIM8: 주로 고성능 AC/DC 모터 제어, 드론 변속기(ESC),  태양광 인버터 등
TIM2~TIM5: 거리센서(초음파) 신호 분석, 서보 모터 제어, 엔코더 읽기.
TIM9~TIM14: 단순한 LED 밝기 조절(PWM), 단순 센서 신호 대기, 시스템의 보조 타이머
TIM6/TIM7: DAC(디지털-아날로그 변환) 타이밍 제어, 정확인 1ms/1s 인터럽트 발생

2.2 타이머 선택 가이드

용도추천 타이머이유
간단한 주기 인터럽트TIM6, TIM7리소스 절약
긴 시간 측정TIM2, TIM532비트 카운터
PWM (4채널)TIM2~TIM5많은 채널
모터 제어TIM1, TIM8보완 출력, 데드타임
EncoderTIM2~TIM5Encoder 모드 지원

2.3 타이머 클럭 소스

STM32F4의 타이머 클럭은 두 가지 버스에서 공급됩니다:

APB1 Timer Clock (TIM2~TIM7, TIM12~TIM14)

  • 최대 84 MHz
  • APB1 Prescaler가 1이 아니면 자동으로 2배
  • 일반적으로 84 MHz

APB2 Timer Clock (TIM1, TIM8~TIM11)

  • 최대 168 MHz
  • APB2 Prescaler가 1이 아니면 자동으로 2배
  • 일반적으로 168 MHz

3. 클럭 트리 분석

3.1 STM32F4 클럭 트리

HSE (8MHz) → PLL → SYSCLK (168MHz)
                     ↓
                   AHB Prescaler (/1)
                     ↓
                   HCLK (168MHz)
                     ↓
        +-----------+-----------+
        ↓                       ↓
    APB1 Prescaler         APB2 Prescaler
        (/4)                   (/2)
        ↓                       ↓
    APB1 (42MHz)            APB2 (84MHz)
        ↓                       ↓
    Timer Clock             Timer Clock
    Multiplier (x2)         Multiplier (x2)
        ↓                       ↓
    84 MHz                  168 MHz

중요: APB Prescaler가 1이 아니면 타이머 클럭은 자동으로 2배가 됩니다.


3.2 STM32CubeMX 클럭 설정 확인

  1. Clock Configuration 탭 열기
  2. 타이머 클럭 주파수 확인:
    • APB1 Timer clocks: 84 MHz
    • APB2 Timer clocks: 168 MHz

3.3 타이머 클럭 계산 예제

예제 1: 1ms 주기 타이머 (TIM6 사용)

목표: 1ms마다 인터럽트 발생
Timer Clock: 84 MHz

방법 1: Prescaler = 83, ARR = 999
Update Frequency = 84,000,000 / ((83 + 1) * (999 + 1))
                 = 84,000,000 / (84 * 1000)
                 = 1,000 Hz = 1ms

방법 2: Prescaler = 8399, ARR = 9
Update Frequency = 84,000,000 / ((8399 + 1) * (9 + 1))
                 = 84,000,000 / (8400 * 10)
                 = 1,000 Hz = 1ms

어떤 방법을 선택할까?

  • Prescaler 작게, ARR 크게: 정밀도 높음
  • Prescaler 크게, ARR 작게: 유연성 높음 (ARR 변경으로 주기 조절 가능)
비교 항목A: PSC 작게 / ARR 크게B: PSC 크게 / ARR 작게
카운팅 속도매우 빠름 (촘촘함)느림 (듬성듬성함)
제어 정밀도매우 높음 (섬세함)낮음 (거칠음)
PWM 해상도우수 (예: 10,000단계)부족 (예: 100단계)
주파수 범위저주파~중주파 유리고주파 생성 가능
추천 용도정밀 모터 제어, 오디오, 밝기 제어단순 신호 발생, 아주 긴 시간 측정

4. 타이머 인터럽트 설정

4.1 STM32CubeMX 설정

Step 1: 타이머 활성화

  1. Timers → TIM6 선택
  2. Activated 체크

Step 2: 타이머 설정

Configuration → TIM6:

Prescaler: 8399  (분주비 8400)
Counter Mode: Up
Counter Period (ARR): 9999  (카운트 10000)
Auto-reload preload: Enable

계산: 84,000,000 / (8400 * 10000) = 1 Hz (1초 주기)

Step 3: 인터럽트 활성화

NVIC Settings:

TIM6 global interrupt: Enabled
Priority: 0

Step 4: 코드 생성

Project Manager → Generate Code


4.2 HAL 타이머 함수

4.2.1 타이머 시작/정지

// 타이머 시작 (인터럽트 모드)
HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim);

// 타이머 정지
HAL_StatusTypeDef HAL_TIM_Base_Stop_IT(TIM_HandleTypeDef *htim);

// 타이머 시작 (폴링 모드)
HAL_StatusTypeDef HAL_TIM_Base_Start(TIM_HandleTypeDef *htim);

// 타이머 정지 (폴링 모드)
HAL_StatusTypeDef HAL_TIM_Base_Stop(TIM_HandleTypeDef *htim);

4.2.2 카운터 값 읽기/쓰기

// 카운터 값 읽기
uint32_t __HAL_TIM_GET_COUNTER(TIM_HandleTypeDef *htim);

// 카운터 값 설정
#define __HAL_TIM_SET_COUNTER(htim, value) ((htim)->Instance->CNT = (value))

// Auto-Reload 값 설정
#define __HAL_TIM_SET_AUTORELOAD(htim, value) ((htim)->Instance->ARR = (value))

4.3 타이머 인터럽트 콜백 함수

/* USER CODE BEGIN 0 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if(htim->Instance == TIM6) {
    // TIM6 인터럽트 발생 시 처리
    HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
  }
}
/* USER CODE END 0 */

/* USER CODE BEGIN 2 */
// 타이머 시작
HAL_TIM_Base_Start_IT(&htim6);
/* USER CODE END 2 */

4.4 1초 타이머 예제

/* USER CODE BEGIN 0 */
uint32_t seconds_counter = 0;

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if(htim->Instance == TIM6) {
    seconds_counter++;
    HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);  // LED 토글
    
    // 10초마다 다른 LED 토글
    if(seconds_counter % 10 == 0) {
      HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_13);
    }
  }
}
/* USER CODE END 0 */

/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim6);
/* USER CODE END 2 */

/* USER CODE BEGIN WHILE */
while (1)
{
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */
  // 메인 루프는 다른 작업 수행 가능
}
/* USER CODE END 3 */

5. 타이머 응용

5.1 정밀한 지연 함수

HAL_Delay()보다 정밀한 마이크로초 단위 지연:

/* USER CODE BEGIN 0 */
void delay_us(uint16_t us)
{
  // TIM2 사용 (1MHz, 1us 해상도)
  // Prescaler: 83 (84MHz / 84 = 1MHz)
  __HAL_TIM_SET_COUNTER(&htim2, 0);
  while(__HAL_TIM_GET_COUNTER(&htim2) < us);
}
/* USER CODE END 0 */

/* USER CODE BEGIN 2 */
// TIM2 시작 (폴링 모드)
HAL_TIM_Base_Start(&htim2);
/* USER CODE END 2 */

/* USER CODE BEGIN 3 */
// 사용 예
delay_us(100);  // 100 마이크로초 지연
HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
delay_us(100);
HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
/* USER CODE END 3 */

TIM2 설정 (STM32CubeMX):

Prescaler: 83  (84MHz / 84 = 1MHz)
Counter Period: 0xFFFFFFFF  (최대값, 32비트)

5.2 타임아웃 처리

/* USER CODE BEGIN 0 */
#define TIMEOUT_MS 1000

uint8_t Wait_For_Button_With_Timeout(uint32_t timeout_ms)
{
  uint32_t start_time = HAL_GetTick();
  
  while(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET) {
    if((HAL_GetTick() - start_time) > timeout_ms) {
      return 0;  // 타임아웃
    }
  }
  
  return 1;  // 버튼 눌림
}
/* USER CODE END 0 */

/* USER CODE BEGIN 3 */
if(Wait_For_Button_With_Timeout(5000)) {
  // 5초 이내에 버튼 눌림
  HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET);
} else {
  // 타임아웃
  HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, GPIO_PIN_SET);
}
/* USER CODE END 3 */

5.3 여러 타이머 동시 사용

/* USER CODE BEGIN 0 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if(htim->Instance == TIM6) {
    // TIM6: 1초마다
    HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
  }
  else if(htim->Instance == TIM7) {
    // TIM7: 100ms마다
    HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_13);
  }
}
/* USER CODE END 0 */

/* USER CODE BEGIN 2 */
// TIM6 설정: 1초 주기
// Prescaler: 8399, ARR: 9999

// TIM7 설정: 100ms 주기
// Prescaler: 8399, ARR: 999

HAL_TIM_Base_Start_IT(&htim6);
HAL_TIM_Base_Start_IT(&htim7);
/* USER CODE END 2 */

5.4 타이머를 이용한 디바운싱

/* USER CODE BEGIN 0 */
#define DEBOUNCE_TIME_MS 50

typedef struct {
  GPIO_TypeDef* port;
  uint16_t pin;
  uint32_t last_change_tick;
  GPIO_PinState stable_state;
  GPIO_PinState last_raw_state;
} TimerDebounceButton_t;

TimerDebounceButton_t btn = {
  .port = GPIOA,
  .pin = GPIO_PIN_0,
  .last_change_tick = 0,
  .stable_state = GPIO_PIN_SET,
  .last_raw_state = GPIO_PIN_SET
};

// TIM6 인터럽트에서 10ms마다 호출
void Button_TimerDebounce_Process(TimerDebounceButton_t* btn)
{
  static uint32_t tick_counter = 0;
  GPIO_PinState current_state = HAL_GPIO_ReadPin(btn->port, btn->pin);
  
  if(current_state != btn->last_raw_state) {
    btn->last_raw_state = current_state;
    btn->last_change_tick = tick_counter;
  }
  
  // 일정 시간 동안 상태가 안정적이면 확정
  if((tick_counter - btn->last_change_tick) >= (DEBOUNCE_TIME_MS / 10)) {
    if(btn->stable_state != current_state) {
      btn->stable_state = current_state;
      
      // 버튼 눌림 이벤트
      if(current_state == GPIO_PIN_RESET) {
        HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
      }
    }
  }
  
  tick_counter++;
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if(htim->Instance == TIM6) {
    // 10ms마다 버튼 상태 확인
    Button_TimerDebounce_Process(&btn);
  }
}
/* USER CODE END 0 */

/* USER CODE BEGIN 2 */
// TIM6 설정: 10ms 주기
// Prescaler: 8399, ARR: 99
HAL_TIM_Base_Start_IT(&htim6);
/* USER CODE END 2 */

6. 타이머 레지스터 직접 제어

6.1 주요 타이머 레지스터

TIMx_CR1 (Control Register 1)

TIM6->CR1 |= TIM_CR1_CEN;   // 타이머 활성화
TIM6->CR1 &= ~TIM_CR1_CEN;  // 타이머 비활성화
TIM6->CR1 |= TIM_CR1_ARPE;  // Auto-reload preload 활성화

TIMx_PSC (Prescaler)

TIM6->PSC = 8399;  // Prescaler 설정

TIMx_ARR (Auto-Reload Register)

TIM6->ARR = 9999;  // ARR 설정

TIMx_CNT (Counter)

uint16_t counter = TIM6->CNT;  // 현재 카운터 값 읽기
TIM6->CNT = 0;                 // 카운터 리셋

TIMx_DIER (DMA/Interrupt Enable Register)

TIM6->DIER |= TIM_DIER_UIE;   // Update 인터럽트 활성화
TIM6->DIER &= ~TIM_DIER_UIE;  // Update 인터럽트 비활성화

TIMx_SR (Status Register)

if(TIM6->SR & TIM_SR_UIF) {
  // Update 인터럽트 플래그 확인
  TIM6->SR &= ~TIM_SR_UIF;  // 플래그 클리어
}

6.2 레지스터 직접 제어 예제

/* USER CODE BEGIN 0 */
void TIM6_Custom_Init(void)
{
  // 클럭 활성화
  __HAL_RCC_TIM6_CLK_ENABLE();
  
  // Prescaler 설정 (84MHz / 8400 = 10kHz)
  TIM6->PSC = 8399;
  
  // ARR 설정 (10kHz / 10000 = 1Hz)
  TIM6->ARR = 9999;
  
  // Auto-reload preload 활성화
  TIM6->CR1 |= TIM_CR1_ARPE;
  
  // Update 인터럽트 활성화
  TIM6->DIER |= TIM_DIER_UIE;
  
  // NVIC 설정
  HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);
  
  // 타이머 시작
  TIM6->CR1 |= TIM_CR1_CEN;
}

void TIM6_DAC_IRQHandler(void)
{
  if(TIM6->SR & TIM_SR_UIF) {
    TIM6->SR &= ~TIM_SR_UIF;  // 플래그 클리어
    
    // 인터럽트 처리
    HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
  }
}
/* USER CODE END 0 */

7. 실전 예제

7.1 스톱워치 구현

/* USER CODE BEGIN 0 */
typedef struct {
  uint16_t milliseconds;
  uint8_t seconds;
  uint8_t minutes;
  uint8_t running;
} Stopwatch_t;

Stopwatch_t stopwatch = {0, 0, 0, 0};

void Stopwatch_Start(void)
{
  stopwatch.running = 1;
  HAL_TIM_Base_Start_IT(&htim6);
}

void Stopwatch_Stop(void)
{
  stopwatch.running = 0;
  HAL_TIM_Base_Stop_IT(&htim6);
}

void Stopwatch_Reset(void)
{
  stopwatch.milliseconds = 0;
  stopwatch.seconds = 0;
  stopwatch.minutes = 0;
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if(htim->Instance == TIM6 && stopwatch.running) {
    // 10ms마다 호출 (Prescaler: 8399, ARR: 99)
    stopwatch.milliseconds += 10;
    
    if(stopwatch.milliseconds >= 1000) {
      stopwatch.milliseconds = 0;
      stopwatch.seconds++;
      
      // LED로 초 표시
      HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
      
      if(stopwatch.seconds >= 60) {
        stopwatch.seconds = 0;
        stopwatch.minutes++;
      }
    }
  }
}

void Stopwatch_Display(void)
{
  // LED로 시간 표시 (간단한 예)
  // 실제로는 7-segment나 LCD 사용
  uint8_t total_seconds = stopwatch.minutes * 60 + stopwatch.seconds;
  LEDs_SetBinary(total_seconds & 0x0F);  // 하위 4비트만 표시
}
/* USER CODE END 0 */

/* USER CODE BEGIN 2 */
// TIM6 설정: 10ms 주기
// Prescaler: 8399, ARR: 99
/* USER CODE END 2 */

/* USER CODE BEGIN 3 */
// 버튼으로 제어
if(Start_Button_Pressed) {
  Stopwatch_Start();
}
if(Stop_Button_Pressed) {
  Stopwatch_Stop();
}
if(Reset_Button_Pressed) {
  Stopwatch_Reset();
}

Stopwatch_Display();
/* USER CODE END 3 */

7.2 주기적인 센서 읽기

/* USER CODE BEGIN 0 */
#define SENSOR_READ_INTERVAL_MS 100

uint16_t sensor_values[10];
uint8_t sensor_index = 0;

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if(htim->Instance == TIM6) {
    // 100ms마다 센서 읽기
    // ADC를 사용한다고 가정 (다음 강의에서 학습)
    sensor_values[sensor_index] = Read_Sensor();
    sensor_index = (sensor_index + 1) % 10;
    
    // 평균 계산
    uint32_t sum = 0;
    for(int i = 0; i < 10; i++) {
      sum += sensor_values[i];
    }
    uint16_t average = sum / 10;
    
    // 평균값에 따라 LED 제어
    if(average > 500) {
      HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET);
    } else {
      HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_RESET);
    }
  }
}
/* USER CODE END 0 */

7.3 다중 작업 스케줄러

/* USER CODE BEGIN 0 */
typedef struct {
  void (*task_function)(void);
  uint32_t period_ms;
  uint32_t last_run_tick;
} Task_t;

void Task_LED1(void) {
  HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
}

void Task_LED2(void) {
  HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_13);
}

void Task_LED3(void) {
  HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_14);
}

void Task_LED4(void) {
  HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_15);
}

Task_t tasks[] = {
  {Task_LED1, 100, 0},   // 100ms마다
  {Task_LED2, 200, 0},   // 200ms마다
  {Task_LED3, 500, 0},   // 500ms마다
  {Task_LED4, 1000, 0}   // 1초마다
};

#define NUM_TASKS (sizeof(tasks) / sizeof(Task_t))

uint32_t system_tick_ms = 0;

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if(htim->Instance == TIM6) {
    // 10ms마다 호출
    system_tick_ms += 10;
    
    // 각 태스크의 실행 시간 확인
    for(uint8_t i = 0; i < NUM_TASKS; i++) {
      if((system_tick_ms - tasks[i].last_run_tick) >= tasks[i].period_ms) {
        tasks[i].task_function();
        tasks[i].last_run_tick = system_tick_ms;
      }
    }
  }
}
/* USER CODE END 0 */

/* USER CODE BEGIN 2 */
// TIM6 설정: 10ms 주기
HAL_TIM_Base_Start_IT(&htim6);
/* USER CODE END 2 */

8. 타이머 인터럽트 우선순위

8.1 우선순위 설정

여러 타이머를 사용할 때 우선순위를 적절히 설정해야 합니다.

/* USER CODE BEGIN 2 */
// TIM6: 높은 우선순위 (긴급 작업)
HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 0, 0);

// TIM7: 낮은 우선순위 (일반 작업)
HAL_NVIC_SetPriority(TIM7_IRQn, 1, 0);

HAL_TIM_Base_Start_IT(&htim6);
HAL_TIM_Base_Start_IT(&htim7);
/* USER CODE END 2 */

8.2 인터럽트 중첩

/* USER CODE BEGIN 0 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if(htim->Instance == TIM6) {
    // 높은 우선순위 - 빠른 처리
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET);
    
    // 짧은 작업만 수행
    for(volatile int i = 0; i < 100; i++);
    
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_RESET);
  }
  else if(htim->Instance == TIM7) {
    // 낮은 우선순위 - 시간이 걸리는 작업
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, GPIO_PIN_SET);
    
    // 긴 작업 (TIM6 인터럽트가 중첩 가능)
    for(volatile int i = 0; i < 10000; i++);
    
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, GPIO_PIN_RESET);
  }
}
/* USER CODE END 0 */

중요: 인터럽트 내에서는 가능한 한 짧게 처리하고, 플래그만 설정한 뒤 메인 루프에서 실제 작업을 수행하는 것이 좋습니다.


9. 디버깅 및 최적화

9.1 타이머 동작 확인

/* USER CODE BEGIN 0 */
void Debug_Timer_Status(void)
{
  // 타이머가 실행 중인지 확인
  if(TIM6->CR1 & TIM_CR1_CEN) {
    // 타이머 실행 중
  }
  
  // 현재 카운터 값
  uint16_t counter = TIM6->CNT;
  
  // Prescaler 값
  uint16_t prescaler = TIM6->PSC;
  
  // ARR 값
  uint16_t arr = TIM6->ARR;
  
  // 인터럽트 활성화 상태
  if(TIM6->DIER & TIM_DIER_UIE) {
    // 인터럽트 활성화됨
  }
}
/* USER CODE END 0 */

9.2 인터럽트 실행 시간 측정

/* USER CODE BEGIN 0 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if(htim->Instance == TIM6) {
    // GPIO를 HIGH로 설정
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_15, GPIO_PIN_SET);
    
    // 실제 작업
    HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
    
    // GPIO를 LOW로 설정
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_15, GPIO_PIN_RESET);
  }
}
/* USER CODE END 0 */

오실로스코프로 PD15 핀을 측정하면 인터럽트 실행 시간을 확인할 수 있습니다.


9.3 타이머 오버플로우 방지

32비트 타이머(TIM2, TIM5)를 사용하여 오버플로우를 최소화:

/* USER CODE BEGIN 0 */
uint32_t Get_Microseconds(void)
{
  // TIM2: 1MHz로 설정 (Prescaler: 83)
  return __HAL_TIM_GET_COUNTER(&htim2);
}

uint32_t Get_Elapsed_Time_us(uint32_t start_time)
{
  uint32_t current_time = Get_Microseconds();
  
  if(current_time >= start_time) {
    return current_time - start_time;
  } else {
    // 오버플로우 발생
    return (0xFFFFFFFF - start_time) + current_time + 1;
  }
}
/* USER CODE END 0 */

/* USER CODE BEGIN 3 */
uint32_t start = Get_Microseconds();

// 작업 수행
Some_Function();

uint32_t elapsed = Get_Elapsed_Time_us(start);
// elapsed: 작업 소요 시간 (마이크로초)
/* USER CODE END 3 */

10. 실습 과제

10.1 과제 1: 디지털 시계

요구사항:
1. 타이머로 1초마다 시간 업데이트
2. 시, 분, 초 카운터 구현
3. 4개 LED로 초의 하위 4비트 표시
4. 버튼으로 시간 설정 기능


10.2 과제 2: 주기적인 LED 패턴

요구사항:
1. TIM6: 100ms마다 LED 패턴 변경
2. TIM7: 1초마다 패턴 모드 변경
3. 최소 3가지 패턴 구현 (순차, 교차, 랜덤 등)


10.3 과제 3: 반응 속도 측정기

요구사항:
1. 랜덤한 시간 후 LED 켜기
2. 사용자가 버튼을 누르는 시간 측정
3. 반응 시간을 ms 단위로 측정
4. LED 개수로 반응 속도 등급 표시

힌트:

uint32_t start_time_ms = 0;
uint8_t waiting = 0;

// LED 켜기
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET);
start_time_ms = system_tick_ms;
waiting = 1;

// 버튼 인터럽트에서
if(waiting) {
  uint32_t reaction_time = system_tick_ms - start_time_ms;
  waiting = 0;
  // reaction_time에 따라 등급 표시
}

11. 정리

11.1 오늘 배운 내용

  1. 타이머의 기본 개념과 구조
  2. STM32 타이머의 종류와 특징
  3. 클럭 트리 분석 및 타이머 클럭 계산
  4. 타이머 인터럽트 설정 및 사용
  5. 타이머 응용 (디바운싱, 스케줄러 등)
  6. 타이머 레지스터 직접 제어
  7. 인터럽트 우선순위와 중첩

11.2 타이머 주기 계산 요약

Update Frequency = Timer Clock / ((Prescaler + 1) * (ARR + 1))

예제:
- 1ms 주기: PSC=83, ARR=999 (84MHz 기준)
- 10ms 주기: PSC=8399, ARR=99
- 100ms 주기: PSC=8399, ARR=999
- 1초 주기: PSC=8399, ARR=9999

11.3 타이머 선택 요약

용도타이머설정
간단한 주기 인터럽트TIM6, TIM7Basic Timer
마이크로초 지연TIM2, TIM532비트, 1MHz
다중 작업 스케줄러TIM610ms 주기
PWM (다음 강의)TIM2~TIM5고급 기능

11.4 참고 자료

profile
당신의 코딩 메이트

0개의 댓글