If SysTick provides "accurate time," the general-purpose timer (TIM) builds on that with functions such as channels, PWM, and input capture. Among them, the most commonly used is PWM (Pulse Width Modulation). Digital pins can only output 0 or 1, but by quickly adjusting the proportion of time they are on (duty), you can control LED brightness or motor speed "like analog."
In this section, we create a 1kHz PWM on TIM2 channel 1 (PA0) and adjust the duty to control the output ratio. By understanding the relationship among the timer’s three main registers: PSC (prescaler)·ARR (period)·CCR (duty), you can freely calculate the desired frequency and duty.
Learning Objectives
- Calculate the time axis (PSC·ARR) and counter clock of the general-purpose timer.
- Understand the operation of PWM mode 1 (comparison of CNT vs CCR).
- Set the registers to output PWM on TIM2 channel 1.
- Adjust duty with CCR and frequency with ARR.
- Know the rule that the APB1 timer clock becomes twice PCLK1.
Target Environment
- Compiler: Keil MDK-ARM (Arm Compiler 6).
- Device: STM32F103C8 (μVision simulator).
- Verification: μVision simulator (TIM2 registers, Logic Analyzer PA0). Calculations on PC.
Key Concepts
1) Timer Time Base — PSC and ARR
The timer divides the input clock with the PSC (prescaler) to create the counter clock, and when the counter reaches ARR (auto-reload), it resets to 0 (update event).
counter_clk = TIMxCLK / (PSC + 1)
pwm_freq = counter_clk / (ARR + 1)
Example: TIM2CLK=72MHz, PSC=71 → counter 1MHz. ARR=999 → 1MHz/1000 = 1kHz PWM.
2) Timer Clock Trap — APB1 ×2
TIM2~4 are on the APB1 bus. If the APB1 prescaler is not 1, the timer clock becomes twice PCLK1 (STM32 design rule). In the standard 72MHz setting, PCLK1=36MHz, but TIM2 clock is 72MHz. That's why we used 72MHz in the example above.
3) PWM Mode 1 — CNT vs CCR
In PWM Mode 1, the output is High while CNT < CCR, and Low afterward. Therefore, the ratio of being High in one period (ARR+1 clocks) is the duty cycle.
duty(%) = CCR * 100 / (ARR + 1)
CCR=250, ARR=999 → 250/1000 = 25%. Increasing CCR makes it brighter (High for longer).
4) PWM Setup Sequence
| Step | Register | Description |
|---|---|---|
| Clock | RCC->APB1ENR, APB2ENR |
Turn on TIM2, GPIOA |
| Pin | GPIOA->CRL |
PA0 = AF Push-Pull (0xB) |
| Timing | PSC, ARR |
Counter clock & period |
| Duty | CCR1 |
Compare value |
| Mode | CCMR1 |
OC1M=110(PWM1), OC1PE (preload) |
| Output | CCER |
CC1E (channel output enable) |
| Start | CR1, EGR |
ARPE, UG (forced load), CEN (start) |
5) Alternate Function (AF) Pin
Timer output is the GPIO Alternate Function. PA0 must be set to AF push-pull (CNF=10, MODE=11 → 0xB) instead of general output for the timer signal to come out on the pin.
Creating a μVision Project
Project → New µVision Project…→ name itstm32_pwm.- Device: STM32F103C8.
Manage Run-Time Environment→ CMSIS:CORE, Device:Startup.- After adding
main.c, copy and paste. - Apply
STM32F10X_MDtoC/C++ → Define. Debug → Use Simulator,-pSTM32F103C8.- Rebuild(F7) → Debug(Ctrl+F5).
Examples
Example 1 — main.c: TIM2 CH1 PWM (1kHz, 25%)
#include "stm32f10x.h"
/*
*Lesson 17 Example — TIM2 CH1 (PA0) PWM Output.
*Target: STM32F103C8, Compiler: Keil MDK-ARM.
*
*Divide the timer clock (here 72MHz) by PSC to get 1MHz, set the period with ARR,
*and set the duty cycle with CCR1. PWM Mode 1: Output High while CNT < CCR1.
*- PSC=71 → 72MHz/(71+1) = 1MHz counter
* - ARR=999 → 1MHz/(999+1) = 1kHz PWM
*- CCR1=250 → Duty 25%
*/
int main(void)
{
/*1) Clock: GPIOA is APB2, TIM2 is APB1*/
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
/*2) PA0 = TIM2_CH1 → Alternate function push-pull 50MHz (CNF=10, MODE=11 → 0xB)*/
GPIOA->CRL &= ~(0xFu << (0u * 4u));
GPIOA->CRL |= (0xBu << (0u * 4u));
/*3) Timer time axis*/
TIM2->PSC = 71u; /*72MHz / 72 = 1MHz counter*/
TIM2->ARR = 999u; /* 1MHz / 1000 = 1kHz PWM */
TIM2->CCR1 = 250u; /*Duty 25%*/
/*4) Channel 1 PWM Mode 1 (110) + CCR preload, enable output*/
TIM2->CCMR1 |= TIM_CCMR1_OC1M_2 | TIM_CCMR1_OC1M_1; /* OC1M = 110 */
TIM2->CCMR1 |= TIM_CCMR1_OC1PE; /*CCR1 preload*/
TIM2->CCER |= TIM_CCER_CC1E; /*Turn on CH1 output*/
/*5) ARR preload + Force update load (UG) + Start counter*/
TIM2->CR1 |= TIM_CR1_ARPE;
TIM2->EGR |= TIM_EGR_UG;
TIM2->CR1 |= TIM_CR1_CEN;
while (1) {
/*PWM is automatically generated by hardware. Changing CCR1 immediately changes the duty cycle.*/
}
}
Example 2 — pc_test.c: Frequency·Duty Calculation
#include <stdio.h>
#include <stdint.h>
/*
*Lesson 17 PC Check — Calculation of PWM Frequency and Duty from PSC/ARR/CCR.
* counter_clk = tim_clk / (PSC+1)
* pwm_freq = counter_clk / (ARR+1)
* duty(%) = CCR * 100 / (ARR+1)
*/
static void report(uint32_t tim_clk, uint32_t psc, uint32_t arr, uint32_t ccr)
{
uint32_t counter_clk = tim_clk / (psc + 1u);
uint32_t pwm_freq = counter_clk / (arr + 1u);
uint32_t duty = (ccr * 100u) / (arr + 1u);
printf("PSC=%lu ARR=%lu CCR=%lu -> %lu Hz, duty %lu%%\n",
(unsigned long)psc, (unsigned long)arr, (unsigned long)ccr,
(unsigned long)pwm_freq, (unsigned long)duty);
}
int main(void)
{
report(72000000u, 71u, 999u, 250u); /* 1kHz, 25% */
report(72000000u, 71u, 999u, 500u); /* 1kHz, 50% */
report(72000000u, 71u, 499u, 250u); /* 2kHz, 50% */
return 0;
}
Checking with Simulator
- Check PSC=71, ARR=999, CCR1=250, CEN=1 in CR1 on
Peripherals → Timers → TIM2. - Adding
PORTA.0to the Logic Analyzer shows a 1kHz square wave with 25% duty cycle (High 0.25ms, Low 0.75ms). - During debugging, if
TIM2->CCR1is changed to 500, the duty cycle changes to 50%. - Expected behavior: 1kHz PWM on PA0, duty proportional to CCR1.
Verification on a PC
gcc pc_test.c -o pc_test && ./pc_test
Expected output (stdout):
PSC=71 ARR=999 CCR=250 -> 1000 Hz, duty 25%
PSC=71 ARR=999 CCR=500 -> 1000 Hz, duty 50%
PSC=71 ARR=499 CCR=250 -> 2000 Hz, duty 50%
Common Mistakes
Q. PWM is not coming out of the pin.
A. Check three things: ① if PA0 is set to AF push-pull (0xB), ② if output is enabled with CC1E on CCER, ③ if counter is started with CEN on CR1.
Q. Frequency differs from the calculated Half/2-times.
A. This is because the timer clock is set incorrectly. If the APB1 prescaler is not 1, TIM2 clock is 2 times PCLK1 (72MHz). Calculating with 36MHz will result in a 2x mismatch.
Q. I changed the settings, but they don't reflect in the first cycle.
A. If you force an update event with the UG bit of EGR, PSC/ARR/CCR will load immediately. If you miss this, it will use the old value until the next update.
Q. I want to set the duty cycle to 100%.
A. If it's CCR ≥ ARR+1, CNT < CCR is always true, so the output stays high (100%). When ARR=999, CCR=1000 corresponds to 100%.
Summary
- Timer: The counter clock is set by PSC, period by ARR, and duty by CCR.
- PWM Mode 1 is High during
CNT < CCR→ duty = CCR/(ARR+1). - The timer output is sent to the AF pin (PA0=0xB).
- Setup order: Clock → Pin → Time base → Mode/Output → UG → CEN.
- Note that the APB1 timer clock is twice PCLK1 (72MHz).
Try it yourself
Solve the assignment below. The reference solution is shown below.