PWM 제어의 핵심, STM32 타이머 출력 비교란?
임베디드 시스템에서 모터 속도 조절, LED 밝기 조정, 서보 제어 등 아날로그적인 효과를 디지털 방식으로 구현하는 핵심 기술은 바로 PWM(Pulse Width Modulation)입니다. 이 신호를 안정적으로 생성하는 하드웨어 장치가 STM32의 타이머 출력 비교(Output Compare) 기능입니다.
단순히 GPIO 핀을 소프트웨어로 주기적으로 토글하는 방식은 CPU 점유율이 높고 정밀도가 낮습니다. 반면 출력 비교는 타이머 카운터(CNT)와 비교 레지스터(CCR) 간의 자동 하드웨어 비교를 통해 신호를 생성하므로, 설정 후에는 CPU 개입 없이도 지속적인 PWM 파형을 출력할 수 있습니다.
출력 비교 작동 원리: 하드웨어 기반 자동 제어
이 기능을 이해하기 위해 다음과 같은 비유를 생각해보세요:
- CNT(Counter): 매 클럭마다 증가하는 카운터 값 (0 → 1 → 2...).
- ARR(Auto-Reload Register): 카운터의 최대값. 이 값에 도달하면 CNT가 0으로 리셋됩니다.
- CCR(Capture/Compare Register): 사용자가 설정한 비교 값.
예를 들어, 타이머가 상향 카운팅(upcounting) 모드이고 PWM Mode 1로 설정된 경우:
- CNT 값이 CCR보다 작을 때: 출력 핀은 High
- CNT 값이 CCR 이상일 때: 출력 핀은 Low
- CNT 값이 ARR에 도달했을 때: CNT는 0으로 되돌아가며 새로운 사이클 시작
이 과정이 반복되면서 일정 주기와 듀티비를 가진 사각파(PWM 신호)가 생성됩니다.
PWM 주파수 및 듀티비 계산 공식
생성되는 PWM 신호의 특성은 다음 두 가지 식으로 결정됩니다:
주파수(fPWM) = 입력 클럭 주파수 / [(PSC + 1) × (ARR + 1)]
드티비(Duty Cycle) = CCR / (ARR + 1) × 100%
예시: 시스템 클럭 72MHz, 목표 주파수 10kHz, 듀티비 50%인 경우
- 분주계(PSC): 71 → 타이머 클럭 = 72MHz / 72 = 1MHz
- 주기(ARR): 1MHz / 10kHz = 100 → ARR = 99 (0부터 시작하므로 -1)
- 드티비(CCR): 50% × 100 = 50 → CCR = 50
HAL 라이브러리를 이용한 PWM 설정 코드
다음은 TIM3 채널 1을 사용하여 위 설정을 적용하는 HAL 초기화 예제입니다:
void Init_PWM_Timer(void) {
TIM_HandleTypeDef htim3;
TIM_OC_InitTypeDef sConfigOC = {0};
// 타이머 기본 설정
htim3.Instance = TIM3;
htim3.Init.Prescaler = 71; // 1MHz 타이머 클럭
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 99; // 10kHz 주기
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
// 출력 비교 설정
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 50; // 듀티비 50%
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1);
}
실시간 듀티비 조절 방법
PWM 동작 중에도 듀티비를 변경해야 할 경우, 다음 함수를 사용합니다. 이는 내부적으로 CCR 레지스터를 직접 수정하여 즉시 반영됩니다.
void Set_Duty_Percent(uint8_t percent) {
uint32_t period = htim3.Init.Period + 1;
uint32_t pulse = (period * percent) / 100;
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, pulse);
}
실전 응용 사례
1. LED 밝기 제어 (브레싱 라이트)
사람의 눈은 일반적으로 1kHz 이상의 주파수에서는 깜빡임을 인지하지 못합니다. 따라서 부드러운 밝기 조절을 위해서는 1~2kHz 이상의 주파수를 권장합니다.
// 서서히 밝아지고 어두워지는 루틴
while (1) {
for (int i = 0; i <= 100; i++) {
Set_Duty_Percent(i);
HAL_Delay(10); // 1초간 점등
}
for (int i = 100; i >= 0; i--) {
Set_Duty_Percent(i);
HAL_Delay(10);
}
}
2. 서보 모터 각도 제어
SG90과 같은 일반적인 서보 모터는 50Hz(주기 20ms)의 PWM 신호를 요구하며, 듀티비에 따라 각도가 결정됩니다:
- 0.5ms 고电平: -90도
- 1.5ms 고电平: 0도
- 2.5ms 고电平: +90도
ARR 값을 19999로 설정하면 타이머 클럭 1MHz 기준 20ms 주기가 됩니다. 각도에 따른 펄스 길이는 다음과 같이 계산됩니다:
void Set_Servo_Angle(int angle) {
if (angle < -90) angle = -90;
if (angle > 90) angle = 90;
float pulse_ms = 0.5f + (angle + 90.0f) * 2.0f / 180.0f;
uint32_t ccr_value = (uint32_t)(pulse_ms * 1000.0f); // 1MHz 기준
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, ccr_value);
}
3. DC 모터 속도 및 방향 제어
STM32 GPIO로 모터를 직접 구동하면 치명적인 오류가 발생할 수 있으므로, 반드시 H-브릿지 드라이버(IC: L298N, TB6612 등)를 사용해야 합니다.
- PWM 핀: 속도 제어 (평균 전압 조절)
- IN1, IN2 핀: 회전 방향 제어
// 정방향 70% 속도
HAL_GPIO_WritePin(IN1_GPIO_Port, IN1_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(IN2_GPIO_Port, IN2_Pin, GPIO_PIN_RESET);
Set_Duty_Percent(70);
// 역방향 40% 속도
HAL_GPIO_WritePin(IN1_GPIO_Port, IN1_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(IN2_GPIO_Port, IN2_Pin, GPIO_PIN_SET);
Set_Duty_Percent(40);
모터 구동용 PWM 주파수는 10~20kHz를 권장합니다. 너무 낮으면 소음이 발생하고, 너무 높으면 스위칭 손실이 커집니다.
자주 발생하는 문제 및 해결 방안
| 문제 현상 | 원인 | 해결 방법 |
|---|---|---|
| PWM 신호 미출력 | GPIO가 AF(Alternate Function) 모드로 설정되지 않음 | RTC 설정 확인 및 GPIO 모드 재설정 |
| 드티비 이상 | CCR 값이 ARR보다 큼 | CCR ≤ ARR 조건 유지 |
| 다중 채널 동기화 실패 | 서로 다른 타이머 사용 | 동일 타이머의 다중 채널 활용 |
| 모터 발열 심함 | PWM 주파수 과도 저하 | 주파수 10kHz 이상으로 설정 |
| 서보 진동 | 전원 노이즈 또는 공급 전압 불안정 | 필터 커패시터 추가, 별도 전원 공급 |
최적의 설계 관행
- 처음에는 상향 카운팅 + PWM Mode 1 사용 추천
- 높은 해상도 필요 시 ARR 값을 크게 설정 (예: 999 → 0.1% 단위 조절 가능)
- 실행 중 타이머 재시작은 파형 불연속 유발 가능성이 있으므로 피할 것
- 고전력 애플리케이션에서는 Dead Time 설정 필수 (고급 타이머 사용)