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를 사용하여 타이머, 시스템 클럭, 디버깅 인터페이스를 설정하는 단계는 다음과 같습니다:
- 프로젝트 생성:
- STM32CubeMX를 열고 "New Project"를 선택.
- MCU 선택 창에서 "STM32G474"를 검색하고, 사용하는 패키지(예: LQFP64)를 선택.
- 프로젝트 이름을 지정하고 저장 경로 설정.
- 시스템 클럭 설정 (최대 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 동작 시 권장).
- 클럭 트리에서 에러가 없음을 확인.
- 타이머 설정:
- 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.
- 예제 1 (기본 타이머): TIM2를 활성화, 내부 클럭 사용, 1초 주기로 인터럽트 발생.
- Pinout & Configuration 탭에서 사용할 타이머 설정.
- 디버깅 인터페이스 설정:
- "System Core" > "SYS"에서 Debug를 "Serial Wire" (SWD)로 설정.
- SWD 핀 (PA13: SWDIO, PA14: SWCLK)이 자동으로 예약됨.
- 프로젝트 설정 및 코드 생성:
- Project Manager 탭에서:
- Project Name과 저장 경로 확인.
- Toolchain/IDE를 "STM32CubeIDE"로 설정.
- Code Generator 옵션에서 "Copy only the necessary library files" 선택.
- "Generate Code"를 클릭하여 프로젝트 파일 생성.
- Project Manager 탭에서:
3.2. 타이머 동작 원리
- 시스템 초기화:
- 시스템 클럭을 170 MHz로 설정하고, 타이머 클럭 활성화 (__HAL_RCC_TIMx_CLK_ENABLE()).
- HAL 라이브러리와 시스템 타이머(Systick) 초기화 (HAL_Init()).
- 타이머 설정:
- TIM_HandleTypeDef 구조체를 사용하여 프리스케일러, 주기, 카운터 모드 등을 설정.
- HAL_TIM_Base_Init 또는 HAL_TIM_PWM_Init 함수로 설정 적용.
- 인터럽트 설정:
- 타이머 인터럽트를 활성화하고 NVIC 우선순위 설정 (HAL_NVIC_SetPriority, HAL_NVIC_EnableIRQ).
- 사용자 콜백 함수 (HAL_TIM_PeriodElapsedCallback 등)를 오버라이드하여 인터럽트 처리.
- 타이머 동작:
- 기본 타이머: HAL_TIM_Base_Start_IT로 주기적 인터럽트 생성.
- PWM: HAL_TIM_PWM_Start로 PWM 신호 출력.
- 입력 캡처: HAL_TIM_IC_Start_IT로 외부 신호의 주기/펄스 폭 측정.
- 디버깅: 변수 (예: 타이머 카운터 값, 캡처 값)를 STM32CubeIDE로 모니터링.
3.3. 디버깅 설정 절차 (STM32CubeIDE)
- 디버그 인터페이스 연결:
- STM32G474 보드와 ST-LINK 디버거를 연결 (SWDIO: PA13, SWCLK: PA14, GND, 3.3V).
- STM32CubeIDE에서 프로젝트를 열고, "Run" > "Debug Configurations" 선택.
- 디버그 구성:
- "STM32 Cortex-M MCU Debugging"에서 새 구성 생성.
- Debugger 탭에서:
- Debug Probe: ST-LINK (OpenOCD).
- Interface: Serial Wire (SWD).
- "Enable" 체크.
- Startup 탭에서:
- "Load image"와 "Load symbols" 활성화.
- 생성된 .elf 파일 경로 확인.
- "Apply" 후 "Debug" 클릭.
- 디버깅 기능 사용:
- 브레이크포인트 설정: main.c에서 원하는 코드 라인에 더블 클릭.
- 변수 모니터링: "Expressions" 또는 "Variables" 창에서 timerStatus, captureValue 등 확인.
- 디버깅 실행: "Resume" (F8), "Step Over" (F6), "Step Into" (F5)로 코드 실행 제어.
3.4. 프로그램 다운로드 절차 (STM32CubeIDE)
- 프로그램 빌드:
- STM32CubeIDE에서 프로젝트를 열고 "Project" > "Build Project" 클릭.
- 빌드 오류가 없음을 확인 (Console 창 확인).
- 프로그램 다운로드:
- ST-LINK 디버거가 연결된 상태에서 "Run" > "Run Configurations" 선택.
- "STM32 Cortex-M MCU"에서 새 구성 생성.
- Main 탭에서 .elf 파일 선택 확인.
- "Run" 클릭하여 프로그램을 STM32G474에 다운로드.
- 다운로드 완료 후, 보드가 자동으로 리셋되고 프로그램 실행.
- 검증:
- 예제 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 클럭, 디버깅