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
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 PWMNow 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.
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 WorkIn 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?