본문 바로가기
MCU/STM32

[STM32G474] 타이머 사용법: HAL API로 타이머 설정 및 코드 예제

by linuxgo 2025. 8. 19.
반응형

1. STM32G474 타이머 개요

STM32G474는 STMicroelectronics의 STM32G4 시리즈에 속하는 고성능 32비트 ARM Cortex-M4 마이크로컨트롤러로, 최대 170 MHz로 동작하며 다양한 타이머 모듈을 제공합니다. 타이머는 시간 측정, PWM 출력, 인터럽트 생성, 외부 이벤트 트리거 등 다양한 용도로 사용됩니다. 이 문서에서는 STM32G474의 타이머를 HAL API를 사용하여 설정하고 사용하는 방법을 상세히 다룹니다. 모든 예제 코드는 STM32CubeMX로 생성된 완전한 코드로 구성되며, STM32CubeIDE에서 실행 가능합니다. 각 코드에는 상세한 주석이 포함되어 있습니다.

타이머 모듈의 주요 특징

  • 타이머 종류: General-purpose timers (TIM2~TIM5), Advanced-control timers (TIM1, TIM8), Basic timers (TIM6, TIM7), High-resolution timers (HRTIM) 등을 제공.
  • 모드: 시간 측정(타이머 카운트), PWM 출력, 입력 캡처, 출력 비교, 원샷 모드.
  • 클럭 소스: 내부 클럭 (APB1/APB2), 외부 클럭 (ETR, TI1, TI2).
  • 인터럽트: 업데이트 이벤트, 비교 매치, 입력 캡처 등 다양한 인터럽트 지원.
  • 프리스케일러 및 주기: 타이머 클럭 분주 및 카운트 주기 설정 가능.
  • DMA 지원: 타이머 이벤트에 따른 DMA 전송 가능.
  • 클럭: AHB2 및 APB1/APB2 클럭을 통해 동작하며, 저전력 모드 지원.

HAL API는 하드웨어 레지스터 직접 조작 대신 추상화된 함수를 제공하여 타이머 설정을 간소화합니다.

2. 주요 타이머 HAL API 함수

2.1. 타이머 초기화 및 설정 함수

  • HAL_TIM_Base_Init(TIM_HandleTypeDef *htim)
    •   설명: 타이머 기본 설정(프리스케일러, 주기, 카운터 모드 등)을 초기화.
    •   매개변수: htim (타이머 핸들러 구조체).
    •   사용 예: TIM2를 1초마다 인터럽트 발생하도록 설정.
  • HAL_TIM_Base_Start(TIM_HandleTypeDef *htim)
    •   설명: 타이머 카운터 시작 (인터럽트 없이).
  • HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim)
    •   설명: 타이머 카운터를 인터럽트 모드로 시작.
  • HAL_TIM_PWM_Init(TIM_HandleTypeDef *htim)
    •   설명: PWM 모드로 타이머 초기화.
  • HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel)
    •   설명: 지정된 채널에서 PWM 출력 시작.
  • HAL_TIM_ConfigClockSource(TIM_HandleTypeDef *htim, TIM_ClockConfigTypeDef *sClockSourceConfig)
    •   설명: 타이머 클럭 소스 설정 (내부/외부 클럭).
  • HAL_TIM_OC_Init(TIM_HandleTypeDef *htim)
    •   설명: 출력 비교(Output Compare) 모드로 타이머 초기화.

2.2. 인터럽트 관련 함수

  • HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    •   설명: 타이머 업데이트 이벤트 발생 시 호출되는 사용자 정의 콜백 함수.
  • HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
    •   설명: PWM 펄스 완료 시 호출되는 콜백 함수.
  • HAL_NVIC_SetPriority(IRQn_Type IRQn, uint32_t PreemptPriority, uint32_t SubPriority)
    •   설명: 타이머 인터럽트 우선순위 설정.
  • HAL_NVIC_EnableIRQ(IRQn_Type IRQn)
    •   설명: 타이머 인터럽트 활성화.

3. 타이머 설정 및 동작 원리

STM32G474의 타이머를 효과적으로 사용하려면 시스템 클럭, 타이머 설정, 인터럽트 및 디버깅 절차를 정확히 구성해야 합니다. 아래는 STM32CubeMX와 STM32CubeIDE를 사용한 설정 절차와 동작 원리를 상세히 설명합니다.

3.1. STM32CubeMX 설정 절차

STM32CubeMX를 사용하여 타이머, 시스템 클럭, 디버깅 인터페이스를 설정하는 단계는 다음과 같습니다:

  1. 프로젝트 생성:
    •   STM32CubeMX를 열고 "New Project"를 선택.
    •   MCU 선택 창에서 "STM32G474"를 검색하고, 사용하는 패키지(예: LQFP64)를 선택.
    •   프로젝트 이름을 지정하고 저장 경로 설정.
  2. 시스템 클럭 설정 (최대 170 MHz, HSE 사용):
    •   Pinout & Configuration 탭에서 "System Core" > "RCC"를 선택.
    •   "HSE"를 "Crystal/Ceramic Resonator"로 설정 (외부 8 MHz 크리스털 가정).
    •   Clock Configuration 탭으로 이동.
      •   HSE를 8 MHz로 입력.
      •   PLL Source Mux를 "HSE"로 설정.
      •   PLL 설정: PLLM = 1, PLLN = 85, PLLR = 2, PLLR Enable 활성화.
      •   System Clock Mux를 "PLLCLK"로 설정.
      •   결과: SYSCLK = (8 MHz / 1) * 85 / 2 = 170 MHz.
      •   HCLK (AHB), PCLK1 (APB1), PCLK2 (APB2)를 170 MHz로 설정 (Divider = 1).
      •   Flash Latency를 4 WS로 설정 (170 MHz 동작 시 권장).
      •   클럭 트리에서 에러가 없음을 확인.
  3. 타이머 설정:
    •   Pinout & Configuration 탭에서 사용할 타이머 설정.
      •   예제 1 (기본 타이머): TIM2를 활성화, 내부 클럭 사용, 1초 주기로 인터럽트 발생.
        •   Clock Source: Internal Clock.
        •   Prescaler: 16999 (170 MHz / (16999+1) = 10 kHz).
        •   Counter Period: 9999 (10 kHz / (9999+1) = 1 Hz, 즉 1초).
        •   Interrupt: Update Event 활성화.
      •   예제 2 (PWM 출력): TIM1 채널 1 (PA8)을 PWM 출력으로 설정.
        •   Clock Source: Internal Clock.
        •   Prescaler: 169 (170 MHz / (169+1) = 1 MHz).
        •   Counter Period: 999 (1 MHz / (999+1) = 1 kHz).
        •   PWM Mode: Mode 1, Duty Cycle 50% (Pulse = 500).
      •   예제 3 (입력 캡처): TIM3 채널 1 (PC6)을 입력 캡처로 설정.
        •   Clock Source: Internal Clock.
        •   Prescaler: 169 (170 MHz / (169+1) = 1 MHz).
        •   Input Capture: Channel 1, Rising Edge.
  4. 디버깅 인터페이스 설정:
    •   "System Core" > "SYS"에서 Debug를 "Serial Wire" (SWD)로 설정.
    •   SWD 핀 (PA13: SWDIO, PA14: SWCLK)이 자동으로 예약됨.
  5. 프로젝트 설정 및 코드 생성:
    •   Project Manager 탭에서:
      •   Project Name과 저장 경로 확인.
      •   Toolchain/IDE를 "STM32CubeIDE"로 설정.
      •   Code Generator 옵션에서 "Copy only the necessary library files" 선택.
    •   "Generate Code"를 클릭하여 프로젝트 파일 생성.

3.2. 타이머 동작 원리

  1. 시스템 초기화:
    •   시스템 클럭을 170 MHz로 설정하고, 타이머 클럭 활성화 (__HAL_RCC_TIMx_CLK_ENABLE()).
    •   HAL 라이브러리와 시스템 타이머(Systick) 초기화 (HAL_Init()).
  2. 타이머 설정:
    •   TIM_HandleTypeDef 구조체를 사용하여 프리스케일러, 주기, 카운터 모드 등을 설정.
    • HAL_TIM_Base_Init 또는 HAL_TIM_PWM_Init 함수로 설정 적용.
  3. 인터럽트 설정:
    •   타이머 인터럽트를 활성화하고 NVIC 우선순위 설정 (HAL_NVIC_SetPriority, HAL_NVIC_EnableIRQ).
    •   사용자 콜백 함수 (HAL_TIM_PeriodElapsedCallback 등)를 오버라이드하여 인터럽트 처리.
  4. 타이머 동작:
    •   기본 타이머: HAL_TIM_Base_Start_IT로 주기적 인터럽트 생성.
    •   PWM: HAL_TIM_PWM_Start로 PWM 신호 출력.
    •   입력 캡처: HAL_TIM_IC_Start_IT로 외부 신호의 주기/펄스 폭 측정.
  5. 디버깅: 변수 (예: 타이머 카운터 값, 캡처 값)를 STM32CubeIDE로 모니터링.

3.3. 디버깅 설정 절차 (STM32CubeIDE)

  1. 디버그 인터페이스 연결:
    •   STM32G474 보드와 ST-LINK 디버거를 연결 (SWDIO: PA13, SWCLK: PA14, GND, 3.3V).
    •   STM32CubeIDE에서 프로젝트를 열고, "Run" > "Debug Configurations" 선택.
  2. 디버그 구성:
    •   "STM32 Cortex-M MCU Debugging"에서 새 구성 생성.
    •   Debugger 탭에서:
      •   Debug Probe: ST-LINK (OpenOCD).
      •   Interface: Serial Wire (SWD).
      •   "Enable" 체크.
    • Startup 탭에서:
      •   "Load image"와 "Load symbols" 활성화.
      •   생성된 .elf 파일 경로 확인.
    •   "Apply" 후 "Debug" 클릭.
  3. 디버깅 기능 사용:
    •   브레이크포인트 설정: main.c에서 원하는 코드 라인에 더블 클릭.
    •   변수 모니터링: "Expressions" 또는 "Variables" 창에서 timerStatus, captureValue 등 확인.
    •   디버깅 실행: "Resume" (F8), "Step Over" (F6), "Step Into" (F5)로 코드 실행 제어.

3.4. 프로그램 다운로드 절차 (STM32CubeIDE)

  1. 프로그램 빌드:
    •   STM32CubeIDE에서 프로젝트를 열고 "Project" > "Build Project" 클릭.
    •   빌드 오류가 없음을 확인 (Console 창 확인).
  2. 프로그램 다운로드:
    •   ST-LINK 디버거가 연결된 상태에서 "Run" > "Run Configurations" 선택.
    •   "STM32 Cortex-M MCU"에서 새 구성 생성.
    •   Main 탭에서 .elf 파일 선택 확인.
    •   "Run" 클릭하여 프로그램을 STM32G474에 다운로드.
    •   다운로드 완료 후, 보드가 자동으로 리셋되고 프로그램 실행.
  3. 검증:
    •   예제 1: LED가 1초 간격으로 점멸.
    •   예제 2: PWM 신호가 PA8에서 1 kHz, 50% 듀티 사이클로 출력.
    •   예제 3: 입력 신호의 주기가 captureValue 변수에 저장됨.

4. 타이머 예제 코드

아래는 STM32G474의 타이머를 HAL API로 제어하는 완전한 STM32CubeMX 생성 예제 코드입니다. 각 코드는 STM32CubeIDE에서 바로 실행 가능하며, 외부 HSE 클럭(8 MHz)을 사용하여 시스템 클럭을 170 MHz로 설정합니다. STM32CubeMX에서 생성된 주석은 그대로 유지되며, 추가로 상세한 한글 주석이 포함됩니다.

4.1. 예제 1: 기본 타이머 (1초 주기 인터럽트)

TIM2를 사용하여 1초마다 인터럽트를 발생시켜 GPIOA 핀 5의 LED를 토글합니다.

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * STM32CubeMX-generated code for TIM2 with 1-second interrupt to toggle LED on GPIOA Pin 5.
  * System clock set to 170 MHz using HSE.
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
TIM_HandleTypeDef htim2; // TIM2 핸들러 구조체
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define LED_Pin GPIO_PIN_5 // GPIOA 핀 5를 LED로 사용
#define LED_GPIO_Port GPIOA // LED가 연결된 GPIO 포트
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
volatile uint32_t timerStatus = 0; // 타이머 상태 변수: 0 (초기화 완료), 1 (동작 중), 0xFFFF (오류)
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void); // 시스템 클럭 설정 함수 선언
static void MX_GPIO_Init(void); // GPIO 초기화 함수 선언
static void MX_TIM2_Init(void); // TIM2 초기화 함수 선언

/* USER CODE BEGIN PFP */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim); // 타이머 인터럽트 콜백 함수 선언
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if (htim->Instance == TIM2) // TIM2 인터럽트 확인
  {
    HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); // LED 상태 토글
    timerStatus = 1; // 동작 상태 업데이트
  }
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init(); // HAL 라이브러리 및 시스템 타이머(Systick) 초기화

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config(); // 시스템 클럭을 170 MHz로 설정 (HSE 8 MHz 사용)

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init(); // GPIOA 핀 5 초기화
  MX_TIM2_Init(); // TIM2 초기화
  /* USER CODE BEGIN 2 */
  HAL_TIM_Base_Start_IT(&htim2); // TIM2 인터럽트 모드로 시작
  if (timerStatus != 1) // 타이머 초기화 성공 여부 확인
  {
    timerStatus = 0xFFFF; // 초기화 실패 시 오류 상태 설정
    while (1); // 오류 발생 시 무한 루프
  }
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    if (timerStatus == 0xFFFF) // 오류 상태 확인
    {
      HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET); // 오류 시 LED 끄기
    }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 1;
  RCC_OscInitStruct.PLL.PLLN = 85;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
  RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief TIM2 Initialization Function
  * @param None
  * @retval None
  */
static void MX_TIM2_Init(void)
{
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 16999; // 170 MHz / (16999+1) = 10 kHz
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 9999; // 10 kHz / (9999+1) = 1 Hz (1초 주기)
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  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();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }

  HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0); // TIM2 인터럽트 우선순위 설정
  HAL_NVIC_EnableIRQ(TIM2_IRQn); // TIM2 인터럽트 활성화

  timerStatus = 1; // 타이머 초기화 성공 표시
}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOA_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin : PA5 */
  GPIO_InitStruct.Pin = LED_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_MEDIUM;
  HAL_GPIO_Init(LED_GPIO_Port, &GPIO_InitStruct);
}

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  timerStatus = 0xFFFF;
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  while (1)
  {
  }
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

설명:

  • 기능: TIM2를 사용하여 1초마다 인터럽트를 발생시켜 GPIOA 핀 5의 LED를 토글.
  • 설정: TIM2 프리스케일러 16999, 주기 9999로 1초 주기 설정.
  • GPIO: GPIOA 핀 5 (LED, 푸시-풀 출력).
  • 출력: LED가 1초마다 ON/OFF 전환.
  • 클럭: HSE 8 MHz, PLL로 170 MHz SYSCLK.

4.2. 예제 2: PWM 출력

TIM1 채널 1 (PA8)을 사용하여 1 kHz, 50% 듀티 사이클의 PWM 신호를 출력합니다.

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * STM32CubeMX-generated code for TIM1 Channel 1 (PA8) PWM output at 1 kHz, 50% duty cycle.
  * System clock set to 170 MHz using HSE.
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
TIM_HandleTypeDef htim1; // TIM1 핸들러 구조체
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define PWM_Pin GPIO_PIN_8 // GPIOA 핀 8를 PWM 출력으로 사용
#define PWM_GPIO_Port GPIOA // PWM이 연결된 GPIO 포트
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
volatile uint32_t timerStatus = 0; // 타이머 상태 변수: 0 (초기화 완료), 1 (동작 중), 0xFFFF (오류)
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM1_Init(void);

/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM1_Init();
  /* USER CODE BEGIN 2 */
  HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); // TIM1 채널 1 PWM 시작
  if (timerStatus != 1)
  {
    timerStatus = 0xFFFF;
    while (1);
  }
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    if (timerStatus == 0xFFFF)
    {
      HAL_GPIO_WritePin(PWM_GPIO_Port, PWM_Pin, GPIO_PIN_RESET);
    }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 1;
  RCC_OscInitStruct.PLL.PLLN = 85;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
  RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief TIM1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_TIM1_Init(void)
{
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};

  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 169; // 170 MHz / (169+1) = 1 MHz
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.Period = 999; // 1 MHz / (999+1) = 1 kHz
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim1.Init.RepetitionCounter = 0;
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 500; // 50% 듀티 사이클
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
  sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }

  timerStatus = 1; // 타이머 초기화 성공 표시
}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOA_CLK_ENABLE();

  /*Configure GPIO pin : PA8 */
  GPIO_InitStruct.Pin = PWM_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 대체 기능 (PWM)
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  GPIO_InitStruct.Alternate = GPIO_AF1_TIM1; // TIM1 채널 1에 매핑
  HAL_GPIO_Init(PWM_GPIO_Port, &GPIO_InitStruct);
}

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  timerStatus = 0xFFFF;
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  while (1)
  {
  }
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

설명:

  • 기능: TIM1 채널 1 (PA8)에서 1 kHz, 50% 듀티 사이클의 PWM 신호 출력.
  • 설정: TIM1 프리스케일러 169, 주기 999, 펄스 500.
  • GPIO: GPIOA 핀 8 (PWM, 대체 기능 모드).
  • 출력: PA8에서 1 kHz PWM 신호 출력.
  • 클럭: HSE 8 MHz, PLL로 170 MHz SYSCLK.

4.3. 예제 3: 입력 캡처

TIM3 채널 1 (PC6)을 사용하여 외부 신호의 주기를 측정합니다.

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * STM32CubeMX-generated code for TIM3 Channel 1 (PC6) input capture to measure signal period.
  * System clock set to 170 MHz using HSE.
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
TIM_HandleTypeDef htim3; // TIM3 핸들러 구조체
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define IC_Pin GPIO_PIN_6 // GPIOC 핀 6을 입력 캡처로 사용
#define IC_GPIO_Port GPIOC // 입력 캡처가 연결된 GPIO 포트
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
volatile uint32_t timerStatus = 0; // 타이머 상태 변수: 0 (초기화 완료), 1 (동작 중), 0xFFFF (오류)
volatile uint32_t captureValue = 0; // 캡처된 값 저장
volatile uint32_t prevCapture = 0; // 이전 캡처 값
volatile uint32_t period = 0; // 신호 주기 (단위: 타이머 틱)
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM3_Init(void);

/* USER CODE BEGIN PFP */
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim); // 입력 캡처 콜백 함수 선언
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
  if (htim->Instance == TIM3 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
  {
    captureValue = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); // 캡처 값 읽기
    if (prevCapture != 0)
    {
      period = captureValue - prevCapture; // 주기 계산
    }
    prevCapture = captureValue; // 현재 값을 이전 값으로 저장
    timerStatus = 1; // 동작 상태 업데이트
  }
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM3_Init();
  /* USER CODE BEGIN 2 */
  HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1); // TIM3 채널 1 입력 캡처 시작
  if (timerStatus != 1)
  {
    timerStatus = 0xFFFF;
    while (1);
  }
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    if (timerStatus == 0xFFFF)
    {
      // 오류 처리
    }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 1;
  RCC_OscInitStruct.PLL.PLLN = 85;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
  RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief TIM3 Initialization Function
  * @param None
  * @retval None
  */
static void MX_TIM3_Init(void)
{
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_IC_InitTypeDef sConfigIC = {0};

  htim3.Instance = TIM3;
  htim3.Init.Prescaler = 169; // 170 MHz / (169+1) = 1 MHz
  htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim3.Init.Period = 65535; // 최대 카운트 값
  htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_IC_Init(&htim3) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &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(&htim3, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }

  HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0); // TIM3 인터럽트 우선순위 설정
  HAL_NVIC_EnableIRQ(TIM3_IRQn); // TIM3 인터럽트 활성화

  timerStatus = 1; // 타이머 초기화 성공 표시
}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOC_CLK_ENABLE();

  /*Configure GPIO pin : PC6 */
  GPIO_InitStruct.Pin = IC_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 대체 기능 (입력 캡처)
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  GPIO_InitStruct.Alternate = GPIO_AF2_TIM3; // TIM3 채널 1에 매핑
  HAL_GPIO_Init(IC_GPIO_Port, &GPIO_InitStruct);
}

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  timerStatus = 0xFFFF;
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  while (1)
  {
  }
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

설명:

  • 기능: TIM3 채널 1 (PC6)에서 외부 신호의 주기를 측정.
  • 설정: TIM3 프리스케일러 169 (1 MHz), 상승 에지 캡처.
  • GPIO: GPIOC 핀 6 (입력 캡처, 대체 기능 모드).
  • 출력: 주기 값이 period 변수에 저장 (단위: 타이머 틱).
  • 클럭: HSE 8 MHz, PLL로 170 MHz SYSCLK.

5. 추가 고려 사항

  • 클럭 설정: STM32CubeMX로 HSE 8 MHz를 사용하여 170 MHz SYSCLK 구성. 실제 보드의 HSE 주파수에 따라 PLL 설정 조정 필요.
  • 타이머 선택: General-purpose timers (TIM2~TIM5)는 다목적 용도, Advanced timers (TIM1, TIM8)은 PWM과 같은 고급 기능에 적합.
  • 인터럽트: 타이머 인터럽트는 NVIC 우선순위 설정에 주의. 다중 인터럽트 사용 시 충돌 방지.
  • PWM 해상도: 프리스케일러와 주기 설정으로 PWM 주파수와 해상도 조절 가능.
  • 디버깅: SWD와 ST-LINK를 사용하여 캡처 값 또는 타이머 상태 실시간 모니터링.
  • 저전력: 타이머 클럭과 인터럽트 빈도를 최적화하여 전력 소모 감소.

키워드: STM32G474, 타이머, HAL API, STM32G4, 마이크로컨트롤러, STM32CubeIDE, STM32CubeMX, 인터럽트, PWM, 입력 캡처, HSE 클럭, 디버깅

반응형