PWM (Pulse Width Modulation) is one of the most common microcontroller outputs — used in motor control, LED dimming, audio, power regulation, etc. Let’s go step by step.
1. What is PWM?
PWM is a digital signal with a fixed frequency but a variable duty cycle (the proportion of HIGH vs LOW in one cycle).
- Duty cycle (%) = (Ton / Tperiod) × 100
- Example: at 1 kHz, 50% duty = 0.5 ms HIGH, 0.5 ms LOW.
2. Methods of Generating PWM
A) Software (bit-banging / timer interrupt method)
- Use a timer interrupt to toggle a GPIO pin ON/OFF at the desired duty ratio.
- Steps:
- Configure a timer to tick at high resolution (e.g., 1 µs).
- Inside ISR, compare a counter with duty value.
- Set GPIO HIGH when counter < duty, else LOW.
- Pros: Simple, works on any MCU.
- Cons: CPU overhead, limited frequency, jitter if ISR latency is high.
Example (pseudo-code):
volatile uint16_t counter = 0;
volatile uint16_t duty = 50; // duty out of 100
void TIM_IRQHandler() {
counter++;
if (counter >= 100) counter = 0; // 100 steps per cycle
if (counter < duty)
GPIO_SetPin(PWM_PIN);
else
GPIO_ClearPin(PWM_PIN);
}
B) Using a Dedicated Timer/Counter Peripheral (preferred)
Most MCUs (8051, STM32, AVR, PIC, ARM Cortex, etc.) have hardware timers with PWM mode.
Principle:
- Timer counts up (or up/down) with a prescaler.
- A compare register (CCR) defines the duty cycle.
- When the timer counter < CCR → output pin = HIGH, else = LOW.
- The ARR (auto-reload register) sets the PWM frequency.
Steps (generic):
- Choose a timer (e.g., Timer1).
- Configure ARR = (fclk / (Prescaler × fPWM)) – 1.
- Set CCRx = (Duty% × ARR).
- Enable PWM mode on that timer channel.
- Enable pin’s alternate function (e.g., TIMx_CHy on STM32).
Example on STM32 (HAL code):
TIM_HandleTypeDef htim2;
void PWM_Init(void) {
// TIM2 @ 1 MHz clock
htim2.Instance = TIM2;
htim2.Init.Prescaler = 84 - 1; // APB1=84 MHz → 1 MHz
htim2.Init.Period = 1000 - 1; // 1 kHz PWM
HAL_TIM_PWM_Init(&htim2);
TIM_OC_InitTypeDef sConfig;
sConfig.OCMode = TIM_OCMODE_PWM1;
sConfig.Pulse = 500; // 50% duty
sConfig.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfig.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&htim2, &sConfig, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); // Start PWM on pin
}
C) Using Dedicated PWM Peripheral
Some MCUs (esp. dsPIC, STM32 advanced timers, ESP32, etc.) have enhanced PWM modules with:
- Dead-time insertion (for motor/half-bridge).
- Center-aligned PWM.
- Complementary outputs.
- Synchronization with ADC triggers.
- Hardware fault shutdown.
These are hardware-controlled, so the CPU only updates duty values occasionally.
3. Frequency and Resolution
PWM frequency (fPWM):
Resolution (bits):
Example: ARR = 255 → 8-bit PWM resolution.
4. Applications
- LED dimming → 500 Hz–1 kHz (avoid flicker).
- Motors (DC/servo) → 1 kHz–20 kHz (above audible range).
- Switching power supplies / inverters → 50–200 kHz.
- Audio output → > 40 kHz PWM, then filtered.
Summary:
- Software PWM = timer interrupt + GPIO toggle (simple but CPU heavy).
- Hardware PWM (recommended) = use timer/capture-compare registers for precise, low-CPU PWM.
- Dedicated PWM peripherals = for motor drives / advanced control.