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.

Phase-shifted counters — timing diagram (duty cycle = 4/10)
RE 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 FE 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 PWM d=4 T=10
RE = Rising Edge counter. FE = Falling Edge counter. The difference (mod T) between the two counters always equals the duty cycle.

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):

\[ \text{MAX\_COUNT} = 2^N, \qquad \text{MASK} = 2^N - 1 \]
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;
}
Efficiency insightWith 8-bit microcontrollers, writing all 8 PWM output bits simultaneously (PWM_OUTPUT = pwm_temp) is a single instruction — far more efficient than setting each bit individually. This makes the phase-shifted counter technique ideal for PIC and AVR microcontrollers.

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:

\[ \phi[n] = (\phi[n-1] + \Delta\phi) \bmod 2^N \]
\[ \text{PWM}[n] = \begin{cases}1 & \phi[n] < d \cdot 2^N \\ 0 & \text{otherwise}\end{cases} \]

The output frequency is:

\[ f_{\text{out}} = \frac{\Delta\phi \cdot f_{\text{clk}}}{2^N} \]
Phase accumulator — sawtooth ramp and PWM output
φ[n] d·2ᴺ PWM
The sawtooth ramp (purple) rises linearly until the threshold (yellow dashed line), producing a PWM pulse (green). Duty cycle is controlled by adjusting the threshold.
Key advantage of phase accumulatorsFractional duty cycles and frequencies are achievable with arbitrary precision — limited only by the accumulator word length N. This is the basis of Direct Digital Synthesis (DDS).