Pulse Width Modulation Techniques

Introduction

Let's face it, PWM is cool! It bridges the gap between the digital and analog regimes allowing one to exchange time for voltage. Spew a stream of 1's and 0's into a filter and out comes a DC voltage!

Most people use hardware to generate PWM waveforms. I guess that's their right, but the fun lies in trying to generate them in software. So here are a few different techniques for generating Pulse Width Modulated square waves using software. It turns out that many of techniques described here may also be implemented in VHDL.

Phase Shifted Counters

In this version, there are two roll-over counters that count at the same rate, but are shifted relative to one another. You can think of them as the "rising edge" and "falling edge" counters. The operation is fairly simple: when the rising edge counter rolls over, make the PWM output High. When the falling edge counter rolls over, make it low.

In Psuedo-code:


// Initialize

PWM_output = 1;
rising_edge = 0;                         // Rising edge counter
falling_edge = MAX_COUNT - duty_cycle;   // Falling edge counter

// Loop forever:

while(1) {

  // If the rising_edge counter rolls over, make the output high

  if(rising_edge++ > MAX_COUNT) {
    rising_edge = 0;
    PWM_output = 1;
  }


  // If the falling_edge counter rolls over, make the output low

  if(falling_edge++ > MAX_COUNT) {
    falling_edge = 0;
    PWM_output = 0;
  }

}
    

In this un-optimized example, we start the PWM output in the high state and initialize the falling edge counter with the desired duty cycle (or actually MAX_COUNT - duty_cycle, since the algorithm increments the counters). When the counters reach their maximum count, they're cleared back to zero and the PWM output is updated. Perhaps a simple picture illustrates it better:



RE  |---------|---------|---------|---------|---------
    01234567890123456789012345678901234567890123456789

FE  ----|---------|---------|---------|---------|-----
    67890123456789012345678901234567890123456789012345

PWM ----______----______----______----______----______

  

RE = Rising Edge counter

FE = Falling Edge counter

PWM = PWM output

In this example, the counters roll over after 10 counts. In the beginning, the rising edge counter is cleared and the output is driven high. Also, the duty cycle is 4 so the falling edge counter is initialized to (10 - 4) = 6. When the rising edge counter counts from 9 to 10, it is "rolled over" back to zero and the output is driven high. Similarly, the falling edge counter drives the output low when it rolls over.

Perhaps one thing to notice is that the difference (modulo 10) between the two counters is always equal to the duty cycle. That's no coincidence. In fact that's why this technique is called the "Phase Shifted Counters".

A couple of optimizations

The psuedo code shown above can be simplified by taking advantage of counters that "automatically" roll over. Binary counters are certainly the most common. For example, suppose we're dealing with 8-bit bytes. These range from 0 to 255. If we were to put one of these counters into an infinite loop, it would start at 0, count up to 255, roll over to 0, count up to 255, etc. If we wanted a 5-bit wide counter, then we could logically "and" the counter with 2^5-1 = 31, each time after we increment it.

In the example psuedo code shown below, the binary counters are N-bits wide.

MAX_COUNT = 2^N;
MASK = 2^N - 1;
PWM_output = 1;
rising_edge = 0;                         // Rising edge counter
falling_edge = MAX_COUNT - duty_cycle;   // Falling edge counter

// Loop forever:

while(1) {

  // If the rising_edge counter rolls over, make the output high

  rising_edge = ++rising_edge & MASK;
  if(!rising_edge)
    PWM_output = 1;



  // If the falling_edge counter rolls over, make the output low

  falling_edge = ++falling_edge & MASK;
  if(!falling_edge)
    PWM_output = 0;

}
    

Multiple PWM outputs

So far this seems like a lot of over head to generate a single PWM output. Fortunately this technique scales up quite nicely for multiple pwm outputs. For example, all of the PWM outputs could be synchronized so that their rising edges all occur at the same time. The falling edges are of course dependent on the duty cycles.

If you want to a real example with 8 pwm outputs code on a pic then check out pwm8.asm. The psuedo code below also illustrates the point:

MAX_COUNT = 2^N;
MASK = 2^N - 1;
PWM_output = 1;
rising_edge = 0;                           // Rising edge counter
falling_edge1 = MAX_COUNT - duty_cycle1;   // Falling edge counter #1
falling_edge2 = MAX_COUNT - duty_cycle2;   // Falling edge counter #2
falling_edge3 = MAX_COUNT - duty_cycle3;   // Falling edge counter #3

// Loop forever:

while(1) {

  pwm_temp = 0;

  // If the rising_edge counter rolls over, make the output high

  rising_edge = ++rising_edge & MASK;
  if(!rising_edge)
    pwm_temp = 0xff;   // all outputs high.

  // If the falling_edge counter rolls over, make the output low

  falling_edge1 = ++falling_edge1 & MASK;
  if(!falling_edge1)
    pwm_temp &= ~1;

  falling_edge2 = ++falling_edge2 & MASK;
  if(!falling_edge2)
    pwm_temp &= ~(1<<1);

  falling_edge3 = ++falling_edge3 & MASK;
  if(!falling_edge3)
    pwm_temp &= ~(1<<2);

  PWM_OUTPUT = pwm_temp;

}
    

Phase Accumulators

Cascaded Counters

Isochonous Code

Vertical Counters


This page is maintained by Scott Dattalo. You can reach me at: scott@dattalo.com
Last modified on 10MAY01.