Channels ▼

Al Williams

Dr. Dobb's Bloggers

Doing Your Duty (Cycle)

January 03, 2012

A certain part's end-of-life announcement has me rewriting some old pulse width modulation (PWM) code that I haven't touched in quite a few years. PWM is a fundamental technique used to control things like lights and motors and even generate analog voltages (with a little external help).

Imagine you are in a room with a single light and a switch. Turn the switch on and you can see because the light is on. Turn the light off and the room goes dark. What if you could switch the light on and off very fast? Suppose you turned it on for a hundredth of a second and then turned it off for another hundredth and just kept going like that. You'd see the light on, but it would appear to be only half as bright as before because your eye averages the light that hits it over a longer time than the switching rate. If you turned the light on 90% of the time, you'd see nearly the full brightness; and if you kept the light off 90% of the time, you'd see very dim light. That percentage, by the way, is called the "duty cycle."

This is a powerful concept because it allows apparent analog control using nothing but a digital signal. The analog part occurs in the physical actuator (in this case, your eye). A motor will work the same way, and it is actually better to control a motor with a pulse than with an analog voltage. Motors often react differently to different voltages, and may produce lower torque at a reduced voltage. But if the motor is turned on full and then turned off, it can operate normally — but the mechanical averaging will produce the desired speed.

There are two common ways to generate PWM. The first thing you'd probably think of is just to pick a maximum cycle time and then switch things on and then off over that cycle. To make things simple, suppose you had a total cycle time of 100 milliseconds. Then a 40% duty cycle would be a true output for 40 milliseconds followed by 60 milliseconds of a zero output.

Here's a simple C language simulation of this algorithm:

#include <stdio.h>

int pwm(unsigned t, unsigned  dc)
{
  unsigned cycle=t%256;
  return cycle<dc;
}

      



int main(int argc, char *argv[])
{
  unsigned t;
  unsigned dc=128;
  if (argc==1)
    {
      fprintf(stderr,"equal PWM demo by Al Williams\n"
        "Next time, specify duty cycle (0-255)\n"
              "This time, using 128");
    }
  else
    dc=(unsigned)atoi(argv[1]);
  dc&=0xFF;  // force to byte
  for (t=0;t<512;t++)
    {
      printf("%c",pwm(t,dc)?'-':'_');
    }
  putchar('\n');
  return 0;
}

In the code, the cycle time is 256 "ticks" (which can be anything you want them to be since it is only simulated). The duty cycle ranges from 0 to 255, so 50% is 128, for example. You can't quite get to 100%, although you can go to zero percent (try it — just run the program with a duty cycle from 0 to 255 on the command line). The output is sent to stdout (catch it in a file and open in an editor that won't wrap long lines).

This is simple, which is good since you want to go fast. However, it leads to a fixed frequency signals. If the tick time in the example code is 1 millisecond, the frequency output will always be just under 4Hz. The only question is how long is it off/on, but the total time is always 1/256 of a second.

Obviously, the frequency plays an important part in PWM designs. If you turned the light on and off only once per minute, your eye is fast enough to not be fooled. Other physical systems will also have some maximum time you can use as a cycle. In some cases, a motor may have some resonance with a certain frequency and that can lead to unwanted noise or other effects. So in many cases, it is actually good to have a fixed frequency.

On the other hand, what happens if you change the PWM value? Unless you don't mind transients that look like a higher or lower duty cycle, you only want to change the duty cycle at the start of a new cycle. Using the aforementioned scheme, that means you can't change your output any faster than every 256 ticks.

However, that time cycle is the worst case. Consider a 50% duty cycle (128 as the program input). The fastest way to generate that would be to simply turn the output on, then off, and so on for every cycle. If each tick in the previous example was 1 millisecond, a 50% duty cycle signal could have a period of 2 milliseconds or 500Hz. Much faster than 4Hz.

But hat is the best case. If you want to produce a 1/255, you still need 256 cycles. An easy way to calculate this is to simply add the duty cycle to an accumulator and output the overflow bit. Assuming byte-wide quantities, you wind up with the minimum period (maximum frequency) possible for that duty cycle.

Consider 128 (50%) again. You'd see something like this:

T=0 Acc=128 Output=0
T=1 Acc=0 (carry) Output=1
T=2 Acc=128 Output=0
T=4 Acc=0 (carry) Output=1

For 64 (25%) it would look like:

T=0 Acc=64 Output=0
T=1 Acc=128 Output=0
T=2 Acc=192 Output=0
T=3 Acc=0 (carry) Output=1

I picked numbers that evenly divide 256 so the sequence always repeats, but it works even if you don't. Here's the C code:

#include <stdio.h>

int pwm(unsigned t, unsigned  dc)
{
  static unsigned acc=0;
  int retval=0;
  acc+=dc;
  // set output to carry
  if (acc>=256) retval=1;
  // simulate byte operation here
  acc=acc&0xFF;
  return retval;
  
}

      



int main(int argc, char *argv[])
{
  unsigned t;
  unsigned dc=128;
  if (argc==1)
    {
      fprintf(stderr,"prop PWM demo by Al Williams\n"
        "Next time, specify duty cycle (0-255)\n"
              "This time, using 128");
    }
  else
    dc=(unsigned)atoi(argv[1]);
  dc&=0xFF;  // force to byte
  for (t=0;t<512;t++)
    {
      printf("%c",pwm(t,dc)?'-':'_');
    }
  putchar('\n');
  return 0;
}

Try a few examples and you'll see that it works. Of course, in real life, you'd want to use assembly language interrupt service routines driven by timer interrupts. In some ways, that's actually easier because the timing is done for you and byte-wide arithmetic is natural (or whatever word size you choose).

In addition to motors and lights, you can use a simple RC (resistor/capacitor) integrator to generate an analog voltage. Frequency counts there, too. A faster signal will change the output voltage faster, but will be more sensitive to loads on the integrator output. Of course, a buffer amplifier on the integrator can easily take care of that.

PWM is a handy trick to have around. Many CPUs today have specialized PWM hardware that do all the work for you, but the effect is the same. You might wonder why I'm not using one of those. Well, I do have my reasons, but that's an issue for another day...

Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 


Video