Introduction
PWM bridges the gap between the digital and analog regimes — spew a stream of 1's and 0's into a filter and out comes a DC voltage! This article covers several software techniques for generating PWM waveforms on a microcontroller, all of which may also be implemented in VHDL.
Phase-Shifted Counters
Two roll-over counters count at the same rate but are shifted relative to one another — a "rising edge" counter and a "falling edge" counter. When the rising edge counter rolls over, the output goes high; when the falling edge counter rolls over, the output goes low.
Pseudo-code for single PWM output:
// Initialize
PWM_output = 1;
rising_edge = 0;
falling_edge = MAX_COUNT - duty_cycle;
while(1) {
if(rising_edge++ > MAX_COUNT) {
rising_edge = 0;
PWM_output = 1; // Rising edge: set high
}
if(falling_edge++ > MAX_COUNT) {
falling_edge = 0;
PWM_output = 0; // Falling edge: set low
}
}
Optimization with Binary Roll-over
Using N-bit binary counters that automatically roll over (no explicit comparison needed):
rising_edge = 0;
falling_edge = MAX_COUNT - duty_cycle;
while(1) {
rising_edge = ++rising_edge & MASK;
if(!rising_edge) PWM_output = 1;
falling_edge = ++falling_edge & MASK;
if(!falling_edge) PWM_output = 0;
}
Multiple PWM Outputs
This technique scales efficiently to multiple simultaneous PWM outputs. All rising edges are synchronized; only the falling edges differ per channel:
falling_edge1 = MAX_COUNT - duty_cycle1;
falling_edge2 = MAX_COUNT - duty_cycle2;
falling_edge3 = MAX_COUNT - duty_cycle3;
while(1) {
pwm_temp = 0;
rising_edge = ++rising_edge & MASK;
if(!rising_edge)
pwm_temp = 0xFF; // All outputs high simultaneously
falling_edge1 = ++falling_edge1 & MASK;
if(!falling_edge1) pwm_temp &= ~(1 << 0); // Ch1 low
falling_edge2 = ++falling_edge2 & MASK;
if(!falling_edge2) pwm_temp &= ~(1 << 1); // Ch2 low
falling_edge3 = ++falling_edge3 & MASK;
if(!falling_edge3) pwm_temp &= ~(1 << 2); // Ch3 low
PWM_OUTPUT = pwm_temp;
}
Phase Accumulators
A phase accumulator generates a sawtooth waveform by repeatedly adding a fixed increment \(\Delta\phi\) to an accumulator. The PWM output is derived by comparing the accumulator value to the desired duty cycle:
The output frequency is: