← Back to Embedded C series
🔌
STM32
STM32 · Prereq: lesson 16

17. General-Purpose Timer · PWM Output

General-purpose timers (TIM) add channels·PWM·input capture. PWM is the most used — rapidly adjusting the on-time ratio (duty) controls LED brightness or motor speed "like analog". We make 1kHz PWM on TIM2 channel1 (PA0) and adjust the duty. Understand the three core registers PSC(prescale)·ARR(period)·CCR(duty), and the trap that an APB1 timer clock is 2× PCLK1.

timerPWMPSCARRCCRduty
Duration
~1.5–2 hours
Level
📊 Intermediate
Prerequisite
🎯 Lesson 16
OUTCOME
Compute and set PWM frequency and duty from PSC/ARR/CCR, and output TIM2 channel1 PWM via an AF push-pull pin.

What you'll learn

  • 1Compute the timer time base (PSC·ARR) and counter clock
  • 2Understand PWM mode1 (CNT vs CCR compare)
  • 3Configure TIM2 channel1 to output PWM
  • 4Adjust duty via CCR and frequency via ARR
  • 5Know the rule that APB1 timer clock is 2× PCLK1

Introduction

A digital pin outputs only 0/1, but rapidly changing the on-ratio changes the average voltage, acting analog. Timer output is a GPIO alternate function (AF), so PA0 must be AF push-pull (0xB).

Key concepts

1) Time base — PSC·ARR·CCR

c
counter_clk = TIMxCLK / (PSC + 1)
pwm_freq    = counter_clk / (ARR + 1)
duty(%)     = CCR * 100 / (ARR + 1)
/* PSC=71 → 72MHz/72 = 1MHz, ARR=999 → 1kHz, CCR=250 → 25% */
⚠️

Timer-clock trap: TIM2~4 are on APB1, but if the APB1 prescaler isn't 1, the timer clock is 2× PCLK1. In the standard 72MHz config PCLK1=36MHz but TIM2 clock is 72MHz.

2) PWM mode1 and setup order

PWM mode1: output High while CNT < CCR. Order: clock → pin(AF) → time base(PSC/ARR) → duty(CCR) → mode(CCMR1 OC1M=110+OC1PE) → output(CCER CC1E) → UG(force load) → CEN(start).

c
TIM2->CCMR1 |= TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1;  /* PWM1 (110) */
TIM2->CCMR1 |= TIM_CCMR1_OC1PE;  TIM2->CCER |= TIM_CCER_CC1E;
TIM2->CR1 |= TIM_CR1_ARPE;  TIM2->EGR |= TIM_EGR_UG;  TIM2->CR1 |= TIM_CR1_CEN;

Core example

c
#include "stm32f10x.h"
int main(void){
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
    RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
    GPIOA->CRL &= ~0xFu; GPIOA->CRL |= 0xBu;   /* PA0 = AF push-pull */
    TIM2->PSC = 71u; TIM2->ARR = 999u; TIM2->CCR1 = 250u;  /* 1kHz, 25% */
    TIM2->CCMR1 |= TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1 | TIM_CCMR1_OC1PE;
    TIM2->CCER |= TIM_CCER_CC1E;
    TIM2->CR1 |= TIM_CR1_ARPE; TIM2->EGR |= TIM_EGR_UG; TIM2->CR1 |= TIM_CR1_CEN;
    while (1) { /* changing CCR1 changes duty instantly */ }
}

Calc check: PSC=71,ARR=999,CCR=250 → 1000Hz·25%. CCR=500 → 50%. ARR=499 → 2000Hz. Add PORTA.0 to the Logic Analyzer to check the duty ratio.

Common mistakes

Q. No PWM comes out of the pin.

A. Check ① PA0 set to AF push-pull (0xB), ② output enabled via CCER's CC1E, ③ counter started via CR1's CEN.

Q. The frequency is half/double my calculation.

A. Wrong timer clock. If the APB1 prescaler isn't 1, TIM2 clock is 2× PCLK1 (72MHz). Calculating with 36MHz is off by 2×.

Q. A config change isn't reflected in the first period.

A. Forcing an update event via EGR's UG loads PSC/ARR/CCR immediately. Skip it and the old values run until the next update.

Summary

  • Timer: PSC sets counter clock, ARR the period, CCR the duty
  • PWM mode1 is High while CNT < CCR → duty = CCR/(ARR+1)
  • Timer output goes out an AF pin (PA0=0xB)
  • Order: clock → pin → time base → mode/output → UG → CEN
  • An APB1 timer clock is 2× PCLK1 (72MHz) — beware

Exercises

  1. Make a breathing LED by sweeping duty 0→100%→0
  2. Compute PSC/ARR to change the PWM frequency to 500Hz
  3. Show that CCR ≥ ARR+1 makes output always High (100%)
Example code / lecture materials

All lecture materials and example code (with homework and answers) are openly available on GitHub.

View on GitHub ↗