25 April 2013

Tutorial 21: Timer Tricks (PWM)

Since I've done some big, multi-part tutorials lately, I thought I'd do a quick one this time. This tutorial will deal with an extremely versatile use of the Timer module: pulse width modulation.

Pulse width modulation, or PWM, is really simple to do, but it allows you to do so many different things! You can change the brightness of a bulb, or set the speed of a motor. You can generate an analog voltage, or even synthesize sounds, all from a simple use of the timer with two interrupts.

The Basic PWM Signal

Three examples of PWM signals with different
duty cycles. Each is also marked with its
average level.
A PWM signal is a natural extension of a square wave--much like the signals we've used in clocks. For a square wave, the signal turns on and off at regular intervals. When you look at a square wave, you see that the signal is high for the same amount of time it's low. The amount of time the line is high--the pulse width--compared to the period, or spacing between pulses, is called the duty cycle. For a square wave, the length of the pulse is half the length of one cycle, so it has a 50% duty cycle. A PWM signal simply adjusts the duty cycle while maintaining the same frequency of pulses.

We create a PWM signal using two interrupts on a timer. If necessary, we could use the same interrupt and adjust when the interrupt occurs in software, but the timer modules in the MSP430 have at least two interrupts that we can use! One interrupt will set the overall frequency of our signal, while the other will set the duty cycle. The idea is simple: using the compare mode of the Timer while in continuous mode, the smallest Timer_A module has three interrupts. Whenever the timer rolls over after 65536 (or 216) clock cycles, an interrupt is driven which we'll use to set the output of our PWM. Using CCR0, we can set the duty cycle by resetting the output after a specified number of clock cycles. If the CCR0 interrupt resets when the timer count register TAR reaches 32768 (half of 65536), we have a 50% duty cycle; the PWM is set for half the period, and reset for the other half. If we set CCR0 to 6554, we have approximately a 10% duty cycle. Since the smallest Timer_A module has two capture-compare registers, using this method you can have at least two PWM signals from any MSP430 device running independently. Unfortunately, often in practice we're going to loose one of these.

As a first example, consider this first example: LEDPWM_G2231.c. If you run this on your LaunchPad, you'll see both LEDs light up, starting dim and growing in brightness, then decreasing from a maximum brightness, and repeating the cycle. The PWM signals being shown by the LEDs are set to increase their duty cycle from 0 to 100%, then back to 0, and repeat. You'll probably also notice a not very desirable feature: the transitions are not very smooth. In fact, you can detect the LEDs flashing nearly the entire time. Once you've run the code as-is, try uncommenting the lines setting the DCO to a faster frequency and note the effect it has on your perception of the LEDs.

Reader Exercise: Why doesn't my code let the PWM go all the way to 0? Try setting the comparison for switching directions upward to else if (TACCR0 == 0) and see what happens. Can you explain it?

Better PWM

Now that you've seen it in action, consider the output frequency of this PWM scheme. If we let the timer count all the way to 216, the PWM signal is set at a frequency of f/216, where f is the Timer_A operation frequency. At the default DCO frequency of ~1.1 MHz, the frequency between pulses is only about 17 Hz-- easily detectable by the human eye! If we use its maximum speed of 16 MHz, using a 16 MHz DCO, then the maximum frequency for a PWM signal using this method will be 244 Hz. For some applications, that may be fast enough, but for many others you may be shooting for a frequency of about 1 kHz or more! Since we have no way to increase the Timer_A frequency any further, we need to have the timer count to a lower number; we can do this by using up mode instead of continuous mode, at the cost of requiring CCR0 to serve in that capacity. While this means we loose an output, we do gain the flexibility of faster PWM signals.

Counting to a lower number also means that we loose some resolution. If we set CCR0 to 10, for example, there are only 11 possible duty cycles: CCR1 can only be set to an integer value less than (or equal to) 10. That provides 11 duty cycles from 0% to 100%, spaced by 10% intervals. Using CCR0 set to 100, we can select by 1% intervals; set to 1000, we can select 0.1% intervals, and so on. While it may have been nice to have such high resolution using all 16 bits of the Timer_A register, most applications aren't benefited by it. Those that really require high resolution output will have to compromise by operating at a lower frequency.

Try the example code in this second example: LEDPWM2_G2231.c, and note the differences in how it's set up. Before, we had to manually set the PWM lines on an overflow interrupt. The MSP430 provides timer output modes that make use of both TACCRx and TACCR0, so the setting of the PWM line is now automatic. No interrupts are needed! This feature makes for much cleaner code, and saves a lot of memory. Of course, if other functions are needed in conjunction with the PWM signal, an interrupt can still be triggered to handle that. In general, it won't be needed.

You might also notice that one characteristic of the schemes we have looked at is that the pulses are all left-edge aligned, since each PWM line is set high at the same moment (when the timer rolls over to 0). In some cases, it may be desirable to have the pulses center-aligned, so that the center of the pulses occur at the same moment. This setup is done by using the timer in up-down mode, and setting the timer outputs to toggle at their respective CCRx interrupts; if the line starts low (which can be assured by resetting at TAR = 0 if necessary), then the output will go high when CCRx is reached while counting up, then go low when reached again while counting back down. This technique centers the pulse about the high point, allowing multiple PWM signals to be centered on the same moment in time. Since this mode requires TACCR0 to set the value to which the timer will count, you cannot use TA0.0 for PWM in this setup either.

Reader Exercise: You'll note that this time, the PWM does go all the way to 0%. If you couldn't figure out the answer to the first exercise, consider how this code is different. What order do the actions happen with the interrupt method? What order do they happen with the reset/set output method?

Why PWM?

The second code example has the same simple function as before: the PWM increases its duty cycle from 0% to a full 100%, then decreases back to 0% before repeating. When the pulse width is small, on average the LED puts out less light. A 10% duty cycle means the LED puts out light for only 10% of the time as usual, and is therefore perceived as being about 10% as bright. By increasing then decreasing the duty cycle, you create a cool, breathing-like effect from the LED, which is used in many popular electronics devices today. For a more practical application, the technique can be used as a "dimmer" for an LED light source when desired.

Since LEDs are nearly digital in their behavior (they're either on or off, nothing in between), why do we perceive the light as being dimmer rather than observing the blinking they're actually doing? The human eye has a relatively slow response to changes in light-- as long as the blinking is faster than the human eye can perceive, we don't detect the rapid transitions and only perceive less total light accumulation in our eyes. This is the same trick that makes movies possible--videos with a frame rate of about 25 frames per second or more seem smooth and seamless to the human eye. But this doesn't mean that ideally you should be flickering the LED as fast as possible; since LEDs aren't perfectly digital, you can probably anticipate that by having the PWM frequency set too high you might artificially lower the brightness even further, as the LED itself (or at least the resistance and capacitance of the circuitry connecting the LED to the signal) can't react quickly enough. In practice, 1-10 kHz is usually sufficient for PWM, though some applications do benefit from higher frequencies. Those situations will require careful design to ensure the stray resistance and capacitance of the circuit don't slow down the transitions too much.

Putting PWM to Work

In reality, that extra resistance and capacitance is sometimes a benefit, however. For example, by intentionally putting a large enough resistor-capacitor pair on the PWM output, you can prevent the signal from ever dropping significantly. Each pulse of the PWM recharges the capacitor a little, and each dead time lets the capacitor discharge slowly. The effect keeps a relatively constant voltage between pulses. By choosing the RC values carefully, you can successfully maintain a voltage that is proportional to the duty cycle: you've successfully created a digital to analog converter!

That's it for this tutorial. Hopefully this answers some of your questions about PWM, and gives you some ideas on how to use it.

Reader Exercise: Try making a 10-bit DAC. You'll want an RC combination that gives a time constant longer than the period between pulses; so if you use a 1 kHz PWM frequency (such as that from the default DCO, or the 1 MHz calibrated DCO for that matter, and setting TACCR0 to 1000) shoot for an RC value greater than 0.001. Write your code in a way that lets you put out a few different duty cycles, cycling either by a button press, a UART command, or automatically with a long delay between changes. Measure the output with a multimeter; the output should be close to the duty cycle multiplied by the voltage of your LaunchPad. (If you're running on USB, that's 3.3 V.) If you have an oscilloscope, look at how clean your DC voltage is. How much ripple do you get? Try raising or lowering the RC time constant. How does that affect the output?

6 comments:

renasuprema said...

sweet tutorial!
...great for precise motor control.
Much obliged!

Dan said...

Keep up the outstanding writing. You're on your way to creating one of the most comprehensive one-stop shops of MSP430 tutorials I've ever seen.

newsha said...

This has been really helpful.
Thank you.

Amanda said...

This is cool!

echepelev said...

translation into Russian

David Olson said...

Very nice work on the translation, thank you! Feel free to translate any of the other tutorials. Just be certain to link back to this site since I own the copyright on all of them.