1. STM32G474 PWM 개요
STM32G474는 STMicroelectronics의 STM32G4 시리즈에 속하는 고성능 32비트 ARM Cortex-M4 마이크로컨트롤러로, 최대 170MHz로 동작하며 고급 타이머 모듈을 포함한 다양한 타이머 기능을 제공합니다. PWM(Pulse Width Modulation)은 주로 모터 제어, LED 밝기 조절, 서보 모터 제어 등에 사용됩니다. 이 문서에서는 STM32G474의 Advanced-Control 타이머(TIM1)를 사용한 PWM 기능을 HAL API로 설정하고 사용하는 방법을 상세히 다룹니다. 모든 예제 코드는 STM32CubeMX로 생성된 완전한 코드로 구성되며, STM32CubeIDE에서 실행 가능합니다. 각 코드에는 상세한 주석이 포함되어 있습니다.
PWM 모듈의 주요 특징
- 타이머: STM32G474는 General-Purpose, Advanced-Control, Basic 타이머를 제공하며, PWM 출력은 TIM1, TIM8 등 Advanced-Control 타이머에서 지원.
- 채널: Advanced-Control 타이머는 최대 4개의 PWM 채널(CH1
CH4) 및 보완 출력(CH1NCH4N) 지원. - 모드: PWM Mode 1(에지 정렬), PWM Mode 2(중앙 정렬) 지원.
- 분해능: 타이머 카운터와 비교 레지스터를 통해 높은 PWM 분해능 제공.
- 클럭: AHB/APB 클럭을 통해 동작하며, 저전력 모드 지원.
- 인터럽트: 타이머 업데이트, 비교, 트리거, 브레이크 이벤트로 인터럽트 발생 가능.
- 추가 기능: Advanced-Control 타이머는 브레이크 입력, 데드타임 삽입, 보완 출력 등 고급 기능 제공.
HAL API는 복잡한 레지스터 설정을 추상화하여 PWM 설정을 간소화합니다.
2. 주요 PWM HAL API 함수
2.1. 타이머 및 PWM 초기화 함수
- *HAL_TIM_PWM_Init(TIM_HandleTypeDef htim)
- 설명: 타이머를 PWM 모드로 초기화.
- 매개변수: htim (타이머 핸들러 구조체).
- 사용 예: TIM1를 PWM 모드로 초기화.
- **HAL_TIM_PWM_ConfigChannel(TIM_HandleTypeDef htim, TIM_OC_InitTypeDef sConfig, uint32_t Channel)
- 설명: 특정 타이머 채널의 PWM 설정(듀티 사이클, 출력 극성 등).
- 매개변수: htim (타이머 핸들러), sConfig (PWM 설정 구조체), Channel (TIM_CHANNEL_x).
- *HAL_TIM_PWM_Start(TIM_HandleTypeDef htim, uint32_t Channel)
- 설명: PWM 출력 시작.
- 매개변수: htim (타이머 핸들러), Channel (TIM_CHANNEL_x).
- *HAL_TIM_PWM_Stop(TIM_HandleTypeDef htim, uint32_t Channel)
- 설명: PWM 출력 정지.
2.2. PWM 제어 함수
- *HAL_TIM_PWM_Pulse_Set(TIM_HandleTypeDef htim, uint32_t Channel, uint32_t Pulse)
- 설명: PWM 듀티 사이클 설정 (비교 레지스터 값 설정).
- 매개변수: Pulse (비교 값, 0~ARR).
- *HAL_TIM_PWM_Pulse_Get(TIM_HandleTypeDef htim, uint32_t Channel)
- 설명: 현재 PWM 듀티 사이클 값 반환.
2.3. 인터럽트 관련 함수
- *HAL_TIM_PWM_IRQHandler(TIM_HandleTypeDef htim)
- 설명: 타이머 PWM 인터럽트 핸들러.
- *HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef htim)
- 설명: PWM 펄스 완료 시 호출되는 사용자 정의 콜백 함수.
3. PWM 설정 및 동작 원리
STM32G474의 PWM을 효과적으로 사용하려면 시스템 클럭, 타이머 설정, GPIO 설정, 디버깅 및 프로그램 다운로드 절차를 정확히 구성해야 합니다. 아래는 STM32CubeMX와 STM32CubeIDE를 사용한 설정 절차와 동작 원리를 상세히 설명합니다.
3.1. STM32CubeMX 설정 절차
STM32CubeMX를 사용하여 PWM, 시스템 클럭, 디버깅 인터페이스를 설정하는 단계는 다음과 같습니다:
- 프로젝트 생성:
- 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 동작 시 권장).
- 클럭 트리에서 에러가 없음을 확인.
- PWM 설정:
- Pinout & Configuration 탭에서 "Timers" > "TIM1"를 선택.
- Channel 1 (PA8)을 "PWM Generation CH1"로 설정, User Label을 "PWM_OUT"로 지정.
- Parameter Settings:
- Prescaler (PSC): 169 (타이머 클럭 = 170 MHz / (169+1) = 1 MHz).
- Counter Period (ARR): 999 (PWM 주기 = (999+1) / 1 MHz = 1 ms, 주파수 = 1 kHz).
- Pulse: 500 (듀티 사이클 = 50%, 500/1000).
- Mode: PWM Mode 1 (에지 정렬) 또는 PWM Mode 2 (중앙 정렬, 예제별 설정).
- Dead Time: 100 ns (보완 출력 사용 시, 예제 2).
- GPIO 설정: PA8은 자동으로 TIM1_CH1로 설정됨, 속도를 "High"로 설정.
- 디버깅 인터페이스 설정:
- "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. PWM 동작 원리
- 시스템 초기화:
- 시스템 클럭을 170 MHz로 설정하고, 타이머 및 GPIO 클럭 활성화.
- HAL 라이브러리와 시스템 타이머(Systick) 초기화 (HAL_Init()).
- PWM 설정:
- TIM1 (Advanced-Control 타이머)를 PWM 모드로 설정 (TIM_OC_InitTypeDef 구조체 사용).
- Prescaler(PSC)와 Auto-Reload Register(ARR)를 설정하여 PWM 주파수 결정.
- Compare Register(CCR)를 설정하여 듀티 사이클 조절.
- HAL_TIM_PWM_Init 및 HAL_TIM_PWM_ConfigChannel로 설정 적용.
- 보완 출력 및 데드타임 설정 (예제 2).
- PWM 동작:
- HAL_TIM_PWM_Start로 PWM 출력 시작.
- HAL_TIM_PWM_Pulse_Set으로 듀티 사이클 동적 조절 가능.
- 인터럽트 설정 시 HAL_TIM_PWM_PulseFinishedCallback으로 특정 이벤트 처리.
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" 창에서 pwmPulse 등 확인.
- 디버깅 실행: "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에 다운로드.
- 다운로드 완료 후, 보드가 자동으로 리셋되고 프로그램 실행.
- 검증:
- PWM 출력: 오실로스코프로 PA8에서 1 kHz, 50% 듀티 사이클의 PWM 파형 확인.
- 동적 듀티 사이클 조절: 버튼 입력 또는 타이머 인터럽트로 듀티 사이클 변경 확인.
4. PWM 예제 코드
아래는 STM32G474의 Advanced-Control 타이머(TIM1)를 HAL API로 제어하는 완전한 STM32CubeMX 생성 예제 코드입니다. 코드는 STM32CubeIDE에서 바로 실행 가능하며, 외부 HSE 클럭(8 MHz)을 사용하여 시스템 클럭을 170 MHz로 설정합니다. 예제는 PWM Mode 1(에지 정렬)과 PWM Mode 2(중앙 정렬), 그리고 보완 출력 및 데드타임을 포함합니다.
4.1. 예제 1: PWM Mode 1 (에지 정렬)
TIM1 채널 1(PA8)을 사용하여 PWM Mode 1로 1 kHz PWM 신호를 출력하고, 듀티 사이클을 동적으로 변경하여 LED 밝기를 조절합니다.
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* STM32CubeMX-generated code for TIM1 Channel 1 (PA8) PWM output in Mode 1.
* PWM frequency: 1 kHz, initial duty cycle: 50%. Duty cycle changes dynamically.
* 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 // PA8 핀을 PWM 출력으로 사용 (TIM1_CH1)
#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 pwmStatus = 0; // PWM 상태 변수: 0 (초기화 완료), 1 (동작 중), 0xFFFF (오류)
volatile uint32_t pwmPulse = 500; // 초기 PWM 펄스 값 (듀티 사이클 50%, 500/1000)
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void); // 시스템 클럭 설정 함수 선언
static void MX_GPIO_Init(void); // GPIO 초기화 함수 선언
static void MX_TIM1_Init(void); // TIM1 초기화 함수 선언
/* 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(); // 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 핀 8를 PWM 출력으로 초기화
MX_TIM1_Init(); // TIM1를 PWM Mode 1로 초기화
/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); // TIM1 채널 1 PWM 출력 시작
if (pwmStatus != 1) // PWM 초기화 성공 여부 확인
{
pwmStatus = 0xFFFF; // 초기화 실패 시 오류 상태 설정
while (1); // 오류 발생 시 무한 루프
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
// 듀티 사이클을 0%에서 100%로 점진적으로 변경 (LED 밝기 조절)
for (pwmPulse = 0; pwmPulse <= 1000; pwmPulse += 10)
{
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, pwmPulse); // PWM 듀티 사이클 설정
HAL_Delay(50); // 50ms 대기
}
for (pwmPulse = 1000; pwmPulse >= 0; pwmPulse -= 10)
{
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, pwmPulse); // PWM 듀티 사이클 설정
HAL_Delay(50); // 50ms 대기
}
/* 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); // 전압 레귤레이터를 Scale 1로 설정 (최대 성능)
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; // 외부 HSE 오실레이터 사용
RCC_OscInitStruct.HSEState = RCC_HSE_ON; // HSE 활성화
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; // PLL 활성화
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; // PLL 소스를 HSE로 설정
RCC_OscInitStruct.PLL.PLLM = 1; // PLL 분주기 M = 1
RCC_OscInitStruct.PLL.PLLN = 85; // PLL 곱셈기 N = 85
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; // PLL P 출력 분주기 = 2
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2; // PLL Q 출력 분주기 = 2
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2; // PLL R 출력 분주기 = 2 (SYSCLK = 8 MHz * 85 / 2 = 170 MHz)
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; // 시스템 클럭 소스를 PLL로 설정
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // AHB 클럭 분주기 = 1 (HCLK = 170 MHz)
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; // APB1 클럭 분주기 = 1 (PCLK1 = 170 MHz)
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // APB2 클럭 분주기 = 1 (PCLK2 = 170 MHz)
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK) // 클럭 설정 및 플래시 레이턴시 4 WS 적용
{
Error_Handler(); // 설정 실패 시 오류 처리
}
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0}; // GPIO 설정 구조체 초기화
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOA_CLK_ENABLE(); // GPIOA 포트 클럭 활성화
/*Configure GPIO pin : PA8 */
GPIO_InitStruct.Pin = PWM_Pin; // 설정할 핀: GPIOA 핀 8
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 대체 기능, 푸시-풀
GPIO_InitStruct.Pull = GPIO_NOPULL; // 풀업/풀다운 저항 사용 안 함
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 출력 속도: 고속
GPIO_InitStruct.Alhistor = GPIO_AF1_TIM1; // PA8을 TIM1_CH1로 설정
HAL_GPIO_Init(PWM_GPIO_Port, &GPIO_InitStruct); // GPIOA 핀 8 설정 적용
pwmStatus = 1; // PWM 초기화 성공 표시
}
/**
* @brief TIM1 Initialization Function
* @param None
* @retval None
*/
static void MX_TIM1_Init(void)
{
TIM_OC_InitTypeDef sConfigOC = {0}; // 타이머 출력 비교 구조체 초기화
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0}; // 브레이크 및 데드타임 구조체 초기화
htim1.Instance = TIM1; // TIM1 선택
htim1.Init.Prescaler = 169; // 프리스케일러: 170 MHz / (169+1) = 1 MHz
htim1.Init.CounterMode = TIM_COUNTERMODE_UP; // 업 카운터 모드 (PWM Mode 1)
htim1.Init.Period = 999; // ARR: (999+1) / 1 MHz = 1 ms (1 kHz PWM)
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; // 클럭 분주 없음
htim1.Init.RepetitionCounter = 0; // 반복 카운터 비활성화
if (HAL_TIM_PWM_Init(&htim1) != HAL_OK) // TIM1 PWM 모드 초기화
{
Error_Handler(); // 초기화 실패 시 오류 처리
}
sConfigOC.OCMode = TIM_OCMODE_PWM1; // PWM 모드 1 (에지 정렬)
sConfigOC.Pulse = 500; // 초기 펄스 값: 50% 듀티 사이클 (500/1000)
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; // 출력 극성: High
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; // 빠른 모드 비활성화
if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK) // 채널 1 PWM 설정
{
Error_Handler(); // 설정 실패 시 오류 처리
}
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE; // 런 모드 오프 상태 비활성화
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE; // 유휴 상태 비활성화
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF; // 락 레벨 비활성화
sBreakDeadTimeConfig.DeadTime = 0; // 데드타임 비활성화 (예제 1)
sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE; // 브레이크 입력 비활성화
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH; // 브레이크 극성
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE; // 자동 출력 비활성화
if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK) // 브레이크 및 데드타임 설정
{
Error_Handler(); // 설정 실패 시 오류 처리
}
pwmStatus = 1; // PWM 초기화 성공 표시
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
pwmStatus = 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 PWM 신호를 PWM Mode 1(에지 정렬)로 출력하며, 듀티 사이클을 0%에서 100%로 점진적으로 변경하여 LED 밝기 조절.
- 설정: PWM 주파수 1 kHz (PSC=169, ARR=999), 초기 듀티 사이클 50% (Pulse=500), PWM Mode 1.
- GPIO: GPIOA 핀 8 (TIM1_CH1, 대체 기능).
- 출력: PWM 파형은 PA8에서 출력되며, 듀티 사이클이 5초마다 0%에서 100%로 변화.
- 클럭: HSE 8 MHz, PLL로 170 MHz SYSCLK.
4.2. 예제 2: PWM Mode 2 (중앙 정렬) 및 보완 출력
TIM1 채널 1(PA8)과 보완 출력(PA7, TIM1_CH1N)을 사용하여 PWM Mode 2(중앙 정렬)로 1 kHz PWM 신호를 출력하며, 데드타임을 삽입합니다.
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* STM32CubeMX-generated code for TIM1 Channel 1 (PA8) and Complementary Channel 1N (PA7) PWM output in Mode 2.
* PWM frequency: 1 kHz, initial duty cycle: 50%, dead time: 100 ns.
* 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 // PA8 핀을 PWM 출력으로 사용 (TIM1_CH1)
#define PWM_N_Pin GPIO_PIN_7 // PA7 핀을 보완 PWM 출력으로 사용 (TIM1_CH1N)
#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 pwmStatus = 0; // PWM 상태 변수: 0 (초기화 완료), 1 (동작 중), 0xFFFF (오류)
volatile uint32_t pwmPulse = 500; // 초기 PWM 펄스 값 (듀티 사이클 50%, 500/1000)
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void); // 시스템 클럭 설정 함수 선언
static void MX_GPIO_Init(void); // GPIO 초기화 함수 선언
static void MX_TIM1_Init(void); // TIM1 초기화 함수 선언
/* 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(); // 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 핀 8 및 7를 PWM 출력으로 초기화
MX_TIM1_Init(); // TIM1를 PWM Mode 2로 초기화
/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); // TIM1 채널 1 PWM 출력 시작
HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1); // TIM1 채널 1N 보완 출력 시작
if (pwmStatus != 1) // PWM 초기화 성공 여부 확인
{
pwmStatus = 0xFFFF; // 초기화 실패 시 오류 상태 설정
while (1); // 오류 발생 시 무한 루프
}
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
// 듀티 사이클을 0%에서 100%로 점진적으로 변경
for (pwmPulse = 0; pwmPulse <= 1000; pwmPulse += 10)
{
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, pwmPulse); // PWM 듀티 사이클 설정
HAL_Delay(50); // 50ms 대기
}
for (pwmPulse = 1000; pwmPulse >= 0; pwmPulse -= 10)
{
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, pwmPulse); // PWM 듀티 사이클 설정
HAL_Delay(50); // 50ms 대기
}
/* 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); // 전압 레귤레이터를 Scale 1로 설정 (최대 성능)
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; // 외부 HSE 오실레이터 사용
RCC_OscInitStruct.HSEState = RCC_HSE_ON; // HSE 활성화
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; // PLL 활성화
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; // PLL 소스를 HSE로 설정
RCC_OscInitStruct.PLL.PLLM = 1; // PLL 분주기 M = 1
RCC_OscInitStruct.PLL.PLLN = 85; // PLL 곱셈기 N = 85
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; // PLL P 출력 분주기 = 2
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2; // PLL Q 출력 분주기 = 2
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2; // PLL R 출력 분주기 = 2 (SYSCLK = 8 MHz * 85 / 2 = 170 MHz)
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; // 시스템 클럭 소스를 PLL로 설정
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // AHB 클럭 분주기 = 1 (HCLK = 170 MHz)
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; // APB1 클럭 분주기 = 1 (PCLK1 = 170 MHz)
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // APB2 클럭 분주기 = 1 (PCLK2 = 170 MHz)
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK) // 클럭 설정 및 플래시 레이턴시 4 WS 적용
{
Error_Handler(); // 설정 실패 시 오류 처리
}
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0}; // GPIO 설정 구조체 초기화
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOA_CLK_ENABLE(); // GPIOA 포트 클럭 활성화
/*Configure GPIO pins : PA8, PA7 */
GPIO_InitStruct.Pin = PWM_Pin | PWM_N_Pin; // 설정할 핀: GPIOA 핀 8 (CH1), 핀 7 (CH1N)
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 대체 기능, 푸시-풀
GPIO_InitStruct.Pull = GPIO_NOPULL; // 풀업/풀다운 저항 사용 안 함
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 출력 속도: 고속
GPIO_InitStruct.Alternate = GPIO_AF1_TIM1; // PA8, PA7을 TIM1_CH1, TIM1_CH1N으로 설정
HAL_GPIO_Init(PWM_GPIO_Port, &GPIO_InitStruct); // GPIOA 핀 설정 적용
pwmStatus = 1; // PWM 초기화 성공 표시
}
/**
* @brief TIM1 Initialization Function
* @param None
* @retval None
*/
static void MX_TIM1_Init(void)
{
TIM_OC_InitTypeDef sConfigOC = {0}; // 타이머 출력 비교 구조체 초기화
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0}; // 브레이크 및 데드타임 구조체 초기화
htim1.Instance = TIM1; // TIM1 선택
htim1.Init.Prescaler = 169; // 프리스케일러: 170 MHz / (169+1) = 1 MHz
htim1.Init.CounterMode = TIM_COUNTERMODE_CENTERALIGNED1; // 중앙 정렬 모드 1 (PWM Mode 2)
htim1.Init.Period = 999; // ARR: (999+1) / 1 MHz = 1 ms (1 kHz PWM)
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; // 클럭 분주 없음
htim1.Init.RepetitionCounter = 0; // 반복 카운터 비활성화
if (HAL_TIM_PWM_Init(&htim1) != HAL_OK) // TIM1 PWM 모드 초기화
{
Error_Handler(); // 초기화 실패 시 오류 처리
}
sConfigOC.OCMode = TIM_OCMODE_PWM2; // PWM 모드 2 (중앙 정렬)
sConfigOC.Pulse = 500; // 초기 펄스 값: 50% 듀티 사이클 (500/1000)
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; // 출력 극성: High
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH; // 보완 출력 극성: High
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; // 빠른 모드 비활성화
if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK) // 채널 1 PWM 설정
{
Error_Handler(); // 설정 실패 시 오류 처리
}
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE; // 런 모드 오프 상태 비활성화
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE; // 유휴 상태 비활성화
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF; // 락 레벨 비활성화
sBreakDeadTimeConfig.DeadTime = 100; // 데드타임: 100 ns (1 MHz 클럭 기준)
sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE; // 브레이크 입력 비활성화
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH; // 브레이크 극성
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE; // 자동 출력 비활성화
if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK) // 브레이크 및 데드타임 설정
{
Error_Handler(); // 설정 실패 시 오류 처리
}
pwmStatus = 1; // PWM 초기화 성공 표시
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
pwmStatus = 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)과 보완 출력(PA7, TIM1_CH1N)에서 1 kHz PWM 신호를 PWM Mode 2(중앙 정렬)로 출력하며, 100 ns 데드타임을 삽입. 듀티 사이클은 0%에서 100%로 변화.
- 설정: PWM 주파수 1 kHz (PSC=169, ARR=999), 초기 듀티 사이클 50% (Pulse=500), PWM Mode 2, 데드타임 100 ns.
- GPIO: GPIOA 핀 8 (TIM1_CH1), GPIOA 핀 7 (TIM1_CH1N, 대체 기능).
- 출력: PWM 파형은 PA8과 PA7에서 상보적으로 출력되며, 듀티 사이클이 5초마다 0%에서 100%로 변화.
- 클럭: HSE 8 MHz, PLL로 170 MHz SYSCLK.
5. 추가 고려 사항
- 클럭 설정: STM32CubeMX로 HSE 8 MHz를 사용하여 170 MHz SYSCLK 구성. 실제 보드의 HSE 주파수에 따라 PLL 설정 조정 필요.
- PWM 모드 선택: PWM Mode 1은 LED 제어에 적합, PWM Mode 2는 모터 제어 등에서 노이즈 감소 효과.
- 데드타임: 보완 출력 사용 시 데드타임 설정은 필수 (예: H-브리지 모터 드라이버).
- 인터럽트: PWM 펄스 완료 또는 브레이크 인터럽트를 사용하여 동적 제어 가능.
- 디버깅: SWD 인터페이스와 ST-LINK를 사용하여 PWM 듀티 사이클 및 타이머 레지스터 실시간 확인.
- 저전력: PWM 출력 시 GPIO 속도를 최적화하여 전력 소모 감소.
키워드: STM32G474, PWM, HAL API, STM32G4, 마이크로컨트롤러, STM32CubeIDE, STM32CubeMX, Advanced-Control 타이머, PWM Mode 1, PWM Mode 2, 보완 출력, 데드타임, HSE 클럭, 디버깅