What is PWM?
PWM (Pulse Width Modulation) is a technique where the width of a digital pulse is varied to
control power delivered to a device without changing the frequency. It is widely used for:
Motor speed control
LED brightness control
Generating analog-like signals from digital outputs
Key parameters:
Frequency (f): How often the pulse repeats per second.
Duty Cycle (%): The percentage of one period in which the signal is HIGH.
PWM in STM32F103C8T6
STM32F103C8T6 has general-purpose timers (TIM1–TIM4) capable of generating PWM
signals.
Timer features for PWM:
16-bit or 32-bit counters: TIM1 & TIM8 are advanced timers, TIM2 is 32-bit, TIM3
& TIM4 are 16-bit.
Multiple channels per timer: Each channel can generate independent PWM signals.
Prescaler & Auto-Reload Register (ARR): Used to control frequency.
How PWM Works in STM32
PWM generation uses the Output Compare (OC) mode of timers:
Types of PWM Modes in STM32
1. Edge-aligned PWM (Most Common)
o Timer counts from 0 to ARR.
o Output goes HIGH when counter < CCR, LOW when counter ≥ CCR.
2. Center-aligned PWM
o Timer counts up and down.
o Produces symmetrical PWM waveform (useful for motor control).
Steps to Configure TIM1 for PWM using HAL
1. Open STM32CubeIDE → Create project for STM32F103C8T6.
2. Configure Timer 1 in PWM mode:
o Go to Timers → TIM1 → Mode → PWM Generation CH1
o Set Prescaler and Counter Period (ARR)
o Set PWM Mode 1 (default)
3. Configure GPIO for TIM1_CH1 output (usually PA8) as Alternate Function
Push-Pull.
4. Generate code → HAL Library will create MX_TIM1_Init() function.
HAL Example Code
Full Example: Generate 1 kHz PWM with 50% duty cycle on TIM1 CH1 (PA8)
#include "main.h"
TIM_HandleTypeDef htim1;
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM1_Init(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM1_Init();
// Start PWM on TIM1 Channel 1
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
// Set duty cycle to 50%
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, [Link] / 2);
while(1)
{
// Optional: change duty cycle dynamically
for(uint32_t i=0; i<=[Link]; i+=100)
{
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, i);
HAL_Delay(100);
}
}
}
void MX_TIM1_Init(void)
{
TIM_OC_InitTypeDef sConfigOC = {0};
[Link] = TIM1;
[Link] = 71; // Timer clk = 72MHz / (PSC+1) = 1 MHz
[Link] = TIM_COUNTERMODE_UP;
[Link] = 999; // PWM freq = 1MHz / (ARR+1) = 1 kHz
[Link] = TIM_CLOCKDIVISION_DIV1;
[Link] = 0;
[Link] = TIM_AUTORELOAD_PRELOAD_DISABLE;
HAL_TIM_PWM_Init(&htim1);
[Link] = TIM_OCMODE_PWM1;
[Link] = 500; // 50% duty cycle (500/999)
[Link] = TIM_OCPOLARITY_HIGH;
[Link] = TIM_OCNPOLARITY_HIGH;
[Link] = TIM_OCFAST_DISABLE;
[Link] = TIM_OCIDLESTATE_RESET;
[Link] = TIM_OCNIDLESTATE_RESET;
HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1);
}
4. Explanation
PSC = 71, ARR = 999 → Timer clock: 72 MHz / (71+1) = 1 MHz → PWM freq = 1
MHz / (999+1) = 1 kHz
CCR = 500 → Duty cycle = 500/999 ≈ 50%
GPIO PA8 → TIM1_CH1 output in Alternate Function Push-Pull
HAL_TIM_PWM_Start() → starts PWM generation
Dynamic duty cycle: You can change __HAL_TIM_SET_COMPARE() in a loop to vary
brightness or motor speed.