Mastering STM32 PWM timer configuration and code is essential for motor control, LED dimming, servo positioning, and audio generation. STM32’s advanced timer system with hardware dead-time insertion, complementary outputs, and DMA triggers makes it far more capable than Arduino’s analogWrite for professional PWM generation.
Table of Contents
- STM32 Timer Architecture
- Calculating PWM Frequency
- Basic PWM Output Code
- Multi-Channel PWM
- Complementary PWM for H-Bridge
- Servo Motor Control
- Frequently Asked Questions
STM32 Timer Architecture
STM32F4 series has up to 14 timers. For PWM:
- TIM1, TIM8: Advanced-control timers — complementary outputs, dead-time, break input (motor control)
- TIM2–TIM5: General-purpose 32-bit timers — most flexible for PWM applications
- TIM9–TIM14: Basic timers — limited channel count
Each timer channel can be mapped to specific GPIO pins (alternate functions). STM32CubeMX shows which pins correspond to which timer channel — critical knowledge for PCB design in Indian hardware products.
Calculating PWM Frequency
PWM frequency formula: f_PWM = f_TIMclk / ((PSC + 1) × (ARR + 1))
Example: 20 kHz PWM on STM32F411 (APB1 clock = 50 MHz for TIM2):
// Target: 20 kHz PWM
// f_TIMclk = 100 MHz (TIM2 on APB1 × 2 on STM32F411)
// PSC = 0 (no prescaling)
// ARR = f_TIMclk / f_PWM - 1 = 100,000,000 / 20,000 - 1 = 4999
// Resolution: 5000 steps (0.02% duty cycle steps)
// In CubeMX:
// TIM2 → Clock source: Internal Clock
// PSC: 0
// Counter Period (ARR): 4999
// PWM Channel → Pulse (CCR): 0 to 4999 = 0% to 100% duty
Basic PWM Output Code
/* STM32 HAL PWM on TIM2 Channel 1 (PA0) */
#include "stm32f4xx_hal.h"
TIM_HandleTypeDef htim2;
TIM_OC_InitTypeDef sConfigOC = {0};
void MX_TIM2_Init(void) {
htim2.Instance = TIM2;
htim2.Init.Prescaler = 0;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 4999; // 20 kHz at 100 MHz
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_PWM_Init(&htim2);
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 2500; // 50% duty cycle
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);
}
// Start PWM
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
// Change duty cycle dynamically
void setPWMDutyCycle(uint32_t duty_percent) {
uint32_t pulse = (htim2.Init.Period + 1) * duty_percent / 100;
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, pulse);
}
Multi-Channel PWM
TIM2 on STM32F4 has four channels (CH1–CH4), each with independent duty cycle but sharing the same frequency. This is ideal for multi-servo control or RGB LED dimming with synchronised timing:
// Configure and start 3 PWM channels on TIM2 for RGB LED
HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1); // Red
HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_2); // Green
HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_3); // Blue
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3);
// Set RGB colour
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, r * 5000 / 255);
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, g * 5000 / 255);
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_3, b * 5000 / 255);
Complementary PWM for H-Bridge
TIM1’s advanced feature for motor H-bridge control: complementary outputs on CH1 and CH1N with configurable dead time to prevent shoot-through.
/* TIM1 Complementary PWM with dead-time */
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_ENABLE;
sBreakDeadTimeConfig.DeadTime = 20; // ~150 ns dead time at 168 MHz
HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig);
// Start complementary channels
HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1); // PA8 (CH1) and PB13 (CH1N)
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
Servo Motor Control
/* Servo control on TIM3 CH1 at 50Hz (20ms period) */
// At 1 MHz timer clock (PSC=99 for 100 MHz): ARR = 19999
// 1ms pulse = 1000 counts (0 degrees)
// 2ms pulse = 2000 counts (180 degrees)
void setServoAngle(uint8_t angle) {
uint32_t pulse = 1000 + (angle * 1000 / 180); // 1000-2000
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, pulse);
}
Frequently Asked Questions
What PWM frequency is best for motor speed control in Indian robotics?
For DC motors with L298N or DRV8833: 10–25 kHz is ideal (above audible frequency, reduces motor heating). For servo motors: 50 Hz is standard (20ms period). For brushless motors (ESC): 50 Hz standard pulse or 400 Hz fast protocol.
Can I change PWM frequency dynamically on STM32?
Yes. Modify ARR (auto-reload register) while timer runs. Use __HAL_TIM_SET_AUTORELOAD() to change period, but adjust pulse values proportionally to maintain duty cycle.
How many simultaneous PWM channels can STM32F103 generate?
STM32F103 has seven timers with up to 4 channels each. In practice, 10–12 independent PWM channels are achievable simultaneously — far more than Arduino Uno’s 6.
Add comment