MCU 타이머로 PWM 주파수와 듀티비 만들기

타이머 클럭 분주부터 자동 재장전과 비교 일치까지, 하드웨어 카운터로 정밀한 PWM 주파수와 듀티비를 생성하는 원리를 알아봅니다.

무료 공개 · 최근 수정

PWM은 켜져 있는 시간 비율로 평균 전압을 만든다

LED 밝기를 조절하거나 모터 회전 속도를 바꾸고 부저로 멜로디를 연주할 때 모두 PWM 기술을 씁니다. 마이크로컨트롤러(MCU)의 디지털 핀은 0V(꺼짐)와 3.3V(켜짐)라는 두 가지 상태만 출력할 수 있습니다.

하지만 초보자가 모터를 돌려 보면 귀를 찌르는 "삐–" 소음이 나곤 합니다. LED 밝기를 서서히 올릴 때는 밝기가 부드럽지 않고 계단처럼 뚝뚝 끊기기도 합니다. 레지스터의 프리스케일러와 주기 설정값을 원리 없이 감으로 바꾸다 보면 원하는 속도나 음정을 맞추기 어렵습니다.

목표로 하는 주파수와 세밀한 제어 단계 수에 맞춰 타이머 레지스터 설정값을 직접 계산하는 방법을 알아봅니다.

디지털 신호가 켜져 있는 시간 비율을 듀티비(Duty Cycle)라고 부릅니다. 3.3V 구형파 펄스에서 켜져 있는 시간이 25%라면 평균 전압은 3.3V × 0.25 = 0.825V가 됩니다. 고속으로 켜고 끄는 비율을 바꾸어 전력을 조절하는 기술이 펄스 폭 변조(PWM; Pulse Width Modulation)입니다.

타이머는 일정한 박자로 수를 세다 CCR에서 출력을 바꾼다

음악 연습을 할 때 메트로놈을 켜 두면 일정한 박자로 똑딱이며 박자를 세어 줍니다. 마이크로컨트롤러 내부의 타이머도 클럭 신호에 맞춰 숫자를 하나씩 세는 하드웨어 메트로놈입니다.

CPU가 매번 핀을 껐다 켜지 않아도 타이머 회로가 스스로 펄스를 만듭니다. STM32 칩의 타이머는 네 가지 부품이 순서대로 맞물려 동작합니다.

  1. 원천 클럭: 시스템 내부에서 타이머로 들어오는 고속 박자 신호입니다(예: 84 MHz).
  2. 프리스케일러(Prescaler): 고속 클럭을 나누어 카운터가 세기 좋은 속도로 늦춰 줍니다(PSC).
  3. 자동 재장전 레지스터(Auto-Reload Register): 숫자가 어디까지 올라가면 다시 0으로 돌아갈지 상한선을 정합니다(ARR).
  4. 비교 레지스터(Capture/Compare Register): 카운터 숫자와 대조하여 핀을 켜고 끌 기준점을 정합니다(CCR).

카운터(CNT)는 0부터 출발하여 숫자를 1씩 올립니다. 카운터 숫자가 ARR에 도달하면 다시 0으로 리셋되며 이 과정을 규칙적으로 반복합니다.

카운터의 톱니파 숫자와 비교 레지스터 기준선이 출력 파형으로 바뀌는 시간 관계를 도면으로 확인합니다.

카운터의 톱니파 증가와 CCR 비교 일치에 따른 PWM 구형파 출력 파형

그림: 카운터 톱니파 계수와 CCR 비교 일치로 합성되는 PWM 구형파 출력 파형

위쪽 그래프에서 카운터는 0에서 ARR까지 오르다가 0으로 떨어집니다. 아래쪽 핀 출력은 카운터 숫자가 CCR보다 작은 동안 High(3.3V)를 유지합니다. 카운터가 CCR과 일치하는 순간 Low(0V)로 떨어져 펄스의 폭을 결정합니다.

같은 주파수에서도 PSC가 작을수록 단계 수가 늘어난다

PWM 출력 주파수는 입력 클럭을 프리스케일러 분주비와 자동 재장전 값의 곱으로 나누어 구합니다.

PWM 주파수 = 타이머 클럭 / ((PSC + 1) × (ARR + 1))

이 공식은 중요한 맞교환 관계를 보여 줍니다. 듀티비를 몇 단계로 잘게 나눌 수 있는지를 뜻하는 분해능은 (ARR + 1)로 결정됩니다.

  • ARR 값이 크면 듀티비를 더 미세하게 조절할 수 있지만 주파수는 낮아집니다.
  • 반대로 주파수를 아주 높이려면 ARR 값을 줄여야 하므로 밝기나 속도 조절 단계가 성기어집니다.

84 MHz 클럭을 쓰는 STM32F4 보드에서 두 가지 대표적인 설정 예를 살펴봅니다.

1 kHz: LED 밝기 조절

사람 눈의 잔상 효과를 이용해 LED 밝기를 떨림 없이 제어할 때는 1 kHz 주파수가 적합합니다.

  • 목표 주파수: 1 kHz (주기 1 ms)
  • 프리스케일러(PSC): 83 (84 MHz / 84 = 1 MHz 카운팅 속도)
  • 자동 재장전(ARR): 999 (1 MHz / 1000 = 1 kHz)
  • 제어 분해능: 1,000단계 (0.1% 단위 조절)

이 설정에서 CCR = 250을 쓰면 듀티비는 250 / 1000 = 25.0%가 됩니다. CCR = 750을 쓰면 75%가 됩니다.

20 kHz: 소음 없는 모터 구동

모터에 1 kHz 신호를 넣으면 코일이 떨리면서 귀에 거슬리는 고주파 소음이 납니다. 따라서 사람의 가청 한계를 벗어난 20 kHz 주파수를 주로 씁니다.

  • 목표 주파수: 20 kHz (주기 50 µs)
  • 프리스케일러(PSC): 3 (84 MHz / 4 = 21 MHz 카운팅 속도)
  • 자동 재장전(ARR): 1049 (21 MHz / 1050 = 20 kHz)
  • 제어 분해능: 1,050단계 (약 0.095% 단위 조절)

84 MHz 기저 클럭 덕분에 20 kHz의 높은 주파수에서도 1,000단계 이상의 미세한 속도 제어가 가능합니다. 모터 코일은 빠른 전압 깜빡임을 통과시키지 않고 평균 전류로 흡수하므로 매끄러운 회전력이 만들어집니다.

[참고] 16비트 타이머와 32비트 타이머

범용 타이머인 TIM3·TIM4는 16비트 레지스터를 써서 ARR이 최대 65,535까지만 들어갑니다. 반면 TIM2·TIM5는 32비트 레지스터를 지원하므로 클럭을 나누지 않고도 긴 주기를 큰 분해능으로 생성할 수 있습니다.

PC에서 PSC와 ARR 후보를 계산한다

다음은 목표 주파수와 타이머 클럭을 넣었을 때 네 가지 PSC 후보에 따른 ARR 값과 오차, 제어 분해능을 계산해 주는 독립 C 프로그램입니다.

#include <stdio.h>
#include <stdint.h>
#include <math.h>
 
void evaluate_pwm_psc(uint32_t timer_clk, double target_freq) {
    const uint32_t psc_candidates[] = {0, 3, 83, 839};
    const int num_candidates = 4;
 
    printf("목표 %g Hz\n", target_freq);
    for (int i = 0; i < num_candidates; i++) {
        uint32_t psc = psc_candidates[i];
        double cnt_freq = (double)timer_clk / (psc + 1);
        long arr = lround(cnt_freq / target_freq) - 1;
 
        if (arr > 65535) {
            printf("  PSC %4u | ARR %6ld | 16비트 초과로 사용 불가\n", psc, arr);
        } else {
            double actual_freq = cnt_freq / (arr + 1);
            double error = actual_freq - target_freq;
            uint32_t resolution = (uint32_t)(arr + 1);
            printf("  PSC %4u | ARR %6ld | 실제 %10.3f Hz | 오차 %+8.3f Hz | 분해능 %6u단계\n",
                   psc, arr, actual_freq, error, resolution);
        }
    }
}
 
int main(void) {
    const uint32_t timer_clock = 84000000; // STM32F4 APB1 타이머 클럭 (84 MHz)
 
    evaluate_pwm_psc(timer_clock, 1000.0);
    evaluate_pwm_psc(timer_clock, 20000.0);
    evaluate_pwm_psc(timer_clock, 440.0);
    return 0;
}

프로그램을 컴파일하여 실행했을 때 터미널에 표시되는 실행 결과는 다음과 같습니다.

목표 1000 Hz
  PSC    0 | ARR  83999 | 16비트 초과로 사용 불가
  PSC    3 | ARR  20999 | 실제   1000.000 Hz | 오차   +0.000 Hz | 분해능  21000단계
  PSC   83 | ARR    999 | 실제   1000.000 Hz | 오차   +0.000 Hz | 분해능   1000단계
  PSC  839 | ARR     99 | 실제   1000.000 Hz | 오차   +0.000 Hz | 분해능    100단계
목표 20000 Hz
  PSC    0 | ARR   4199 | 실제  20000.000 Hz | 오차   +0.000 Hz | 분해능   4200단계
  PSC    3 | ARR   1049 | 실제  20000.000 Hz | 오차   +0.000 Hz | 분해능   1050단계
  PSC   83 | ARR     49 | 실제  20000.000 Hz | 오차   +0.000 Hz | 분해능     50단계
  PSC  839 | ARR      4 | 실제  20000.000 Hz | 오차   +0.000 Hz | 분해능      5단계
목표 440 Hz
  PSC    0 | ARR 190908 | 16비트 초과로 사용 불가
  PSC    3 | ARR  47726 | 실제    440.003 Hz | 오차   +0.003 Hz | 분해능  47727단계
  PSC   83 | ARR   2272 | 실제    439.947 Hz | 오차   -0.053 Hz | 분해능   2273단계
  PSC  839 | ARR    226 | 실제    440.529 Hz | 오차   +0.529 Hz | 분해능    227단계

결과에서 볼 수 있듯이 프리스케일러(PSC) 값을 작게 줄일수록 ARR 상한선이 커져 듀티비 조절 단계가 훨씬 세밀해집니다. 반면 440 Hz처럼 클럭이 정수로 딱 떨어지지 않는 주파수에서는 나눗셈 오차가 남아 소수점 단위의 주파수 편차가 생깁니다.

GPIO와 타이머를 다룰 수 있게 된 다음에는 센서나 PC와 바이트 단위로 데이터를 주고받는 UART 직렬 통신의 프레임 구조와 통신 속도 생성 원리로 이어집니다.

참고 문서

설명이 어렵거나 잘못된 부분을 발견하셨나요?

문서 수정 의견 보내기