How PWM timers work
A PWM peripheral counts from 0 to ARR (Auto-Reload Register), then resets. The output pin is high while the counter is below CCR (Capture/Compare Register), giving you duty cycle control. The core formula on STM32:
f_PWM = f_TIM / ((PSC + 1) × (ARR + 1))
PSC divides the timer clock before the counter sees it. ARR sets the period. CCR sets the duty cycle threshold. That’s the whole model.
The APB×2 timer clock trap
The most common source of miscalculation: STM32 timers run at a different clock than PCLK.
On STM32F4 at 168 MHz, the default CubeMX clock tree sets APB1 = 42 MHz (HCLK ÷ 4). But timers on APB1 run at 84 MHz, not 42 MHz — the hardware automatically doubles the peripheral clock when the APB prescaler is not 1. This is documented in the RCC chapter of the reference manual under “Timer clock frequencies.”
The rule: if APB prescaler = 1, timer clock = APBCLK. If APB prescaler > 1, timer clock = 2 × APBCLK.
Verify in firmware before trusting your calculations:
uint32_t pclk1 = HAL_RCC_GetPCLK1Freq();
// On STM32F4 @ 168 MHz: pclk1 = 42000000
// Timer clock = 2 × 42000000 = 84000000 — NOT 168 MHz
This calculator uses the actual timer clock, not PCLK. On STM32F4, TIM3/4 = 84 MHz and TIM2/5 = 84 MHz. Enter the correct value in the override field if your clock tree differs.
ARR sets both period and resolution
Maximize ARR to maximize duty cycle resolution. Every step in CCR represents 1 / (ARR + 1) of the full duty range. With ARR = 999: 1000 steps, 0.1% minimum duty. With ARR = 65535: 65536 steps, 0.0015% minimum duty.
The algorithm here finds the PSC value that keeps ARR as high as possible for the target frequency. For 1 kHz on STM32F4 at 84 MHz:
PSC = 0: ARR = 84,000,000 / (1 × 1,000) − 1 = 83,999 → fits in 16-bit (65535 max)? No.
PSC = 1: ARR = 84,000,000 / (2 × 1,000) − 1 = 41,999 → fits. ~15.4 bits.
PSC = 2: ARR = 84,000,000 / (3 × 1,000) − 1 = 27,999 → fits. ~14.8 bits. Worse.
PSC = 1, ARR = 41999 wins because it gives the most resolution while staying within the 16-bit register limit.
For TIM2 (32-bit ARR), PSC = 0 and ARR = 83999 is achievable — 16+ bits of resolution at 1 kHz.
nRF52 COUNTERTOP register
nRF52 PWM is simpler: no separate PSC register in the classic sense. Instead, PRESCALER[2:0] selects a power-of-two clock divider (1, 2, 4, 8, 16, 32, 64, or 128), and COUNTERTOP directly sets the number of counts in one period.
f_PWM = 16_000_000 / (prescaler_divider × COUNTERTOP)
resolution_bits = log2(COUNTERTOP)
COUNTERTOP must be 3 to 32767. It sets both period and maximum duty cycle count in one register — the duty value in your sequence buffer must be ≤ COUNTERTOP. A common mistake is setting duty to a value larger than COUNTERTOP, which clamps the output to 100% permanently.
To hit 1 kHz with maximum resolution: PRESCALER = 0 (÷1), COUNTERTOP = 16000 → exactly 1 kHz with log2(16000) ≈ 13.97 bits of resolution.
RP2040 PWM fractional divider
RP2040 PWM has an integer + 4-bit fractional clock divider, plus a WRAP register (equivalent to ARR). The fractional divider allows non-integer division but introduces frequency dither (jitter at a rate equal to f_PWM × fractional_error). For motor control and audio applications, use integer division only.
f_PWM = f_SYS / (CLK_DIV_INT × (WRAP + 1))
This calculator uses integer CLK_DIV for predictable, jitter-free output. With 125 MHz system clock and CLK_DIV = 125, WRAP = 999: exactly 1 kHz.
ESP32 LEDC duty resolution
ESP32 LEDC maps differently: you choose the duty resolution in bits (1–20), and the hardware derives the divider. Higher bit count = more PWM steps but limits maximum frequency:
f_PWM = APB_CLK / (2^duty_bits × divider)
At 13-bit resolution (8192 steps) with 80 MHz APB: maximum clean frequency ≈ 80 MHz / 8192 ≈ 9.77 kHz per integer divider step. For 20 kHz, drop to 11-bit (2048 steps). For LED dimming at 1 kHz, 13-bit gives excellent smoothness.
Common mistakes
Setting CCR instead of ARR for period control. ARR sets when the counter resets. CCR sets the compare threshold. Writing to ARR changes your PWM period. Writing to CCR changes duty cycle. Getting these backwards produces a signal with the wrong frequency that appears correct on a scope if you only check duty cycle visually.
Forgetting TIM_CR1_ARPE for glitch-free duty updates. Without the Auto-Reload Preload Enable bit, writing a new ARR value takes effect immediately, mid-period. The counter may overshoot the new ARR and count for a very long (or very short) period until it wraps. Set TIM_CR1_ARPE to buffer ARR changes until the next update event:
TIMx->CR1 |= TIM_CR1_ARPE;
The same applies to CCR registers: enable OCxPE (output compare preload) in TIM_CCMRx to prevent glitchy duty updates:
TIM3->CCMR1 |= TIM_CCMR1_OC1PE;
PSC too high, resolution lost. Setting PSC = 999 and ARR = 83 for 1 kHz on a 84 MHz clock gives only 84 steps of duty cycle resolution — barely 6 bits. The rule is lowest PSC, highest ARR. The calculator enforces this automatically.
Forgetting to call HAL_TIM_PWM_Start() after configuring. The timer runs after HAL_TIM_Base_Start(), but the PWM output is not driven until the compare channel is started separately. On bare metal, ensure TIM_CCER_CC1E is set in TIM_CCER for channel 1, or equivalent for other channels.
nRF52: COUNTERTOP not updated before enabling the PWM peripheral. Write COUNTERTOP before calling nrfx_pwm_simple_playback(). Changing COUNTERTOP while PWM is running requires stopping and restarting the peripheral.