The operating mode of the MSP430 is controlled with four different bits in the status register. These bits are called CPUOFF, OSCOFF, SCG0, and SCG1. The first two are fairly self-evident; the first of these controls the power to the CPU of the MSP430. The second controls the power to the external oscillator (ie. crystal usually). In addition, these bits turn on/off MCLK and ACLK respectively, since those are associated with the CPU and LFXTL. The other two bits add more control over SMCLK, the DCO, and the DC generator.
Page from the x2xx Family Guide describing the low power modes. |
the _BIS_SR() function we saw earlier is perhaps the easiest method of using the LPMs. The header files define a set of names called LPMx_bits to help with the bit management. To enter a low power mode, simply issue the command _BIS_SR(LPMx_bits); with x replaced by the LPM number you've chosen. Keep in mind that if you want to use interrupts, you will also have to add GIE to the argument, or at least _enable_interrupt(); before going into the LPM.
Another good thing to keep in mind is that the LPMs are distinct; you don't need to include LPM0 to use LPM1. In fact, doing so (eg. using a command such as _BIS_SR(LPM0_bits + LPM1_bits + GIE);) would have undesirable side effects by adding the binary values that the header file definitions provide together.
One of the features of the MSP430 architecture is that you don't need to tell the CPU to wake up on the interrupt, nor do you need to put it back in the LPM manually. All of this is done automatically; when an interrupt is triggered, the Status Register is saved to memory (pushed onto the stack) and reset, waking up the CPU. Once the ISR has been satisfied, the original Status Register (including the LPM setting) is popped off the stack back into its place, and the MSP430 returns to the low power state. If you require your program to wake up the chip and stay awake, you will need to include a line in your ISR to do so:
_BIC_SR(LPMx_bits); // clears the bits corresponding to LPMx and exits the low power mode
Note the different function name; _BIS_SR() is Bit Set Status Register (ie. set to 1) while _BIC_SR() is Bit Clear Status Register (ie. clear to 0).
Example using the Low Power Modes
As an example, I've coded up a simple program that plays a tone on an 8Ω speaker. The speaker I have has two pins with 0.2" separation, and so it plugs right into the headers on the LaunchPad. To accommodate the spacing, I'm using P1.0 and P1.2 to drive the speaker. If you don't have a speaker available, you can simulate the same idea using the two LEDs on the LaunchPad; just follow the instructions in the code and change SOUT to BIT6 and adjust the frequency accordingly.
The idea is simple; we start with P1.0 high, P1.2 low, and switch each at a given frequency. This method provides a square wave, not a pure tone, but it does work. Keep in mind that audible frequencies are generally in the 100's and 1000's of Hz. It might be interesting to see how low of a frequency you can hear (clicking doesn't count; listen for an actual tone) and how high you can hear. Also, you can keep one pin constantly low and switch only the other if you want to reduce the volume.
There's more to explore with the low power modes, and next time we'll start playing with the low frequency oscillator to drop into the deeper sleep modes.
Reader Exercise: This tutorial's code uses LPM0. Try out the other low power modes by using LPMx_bits in place of LPM0_bits. What happens if you try to use LPM1? LPM2? LPM3? LPM4? Can you explain why some of these work and others don't? Those that work, is there any actual difference between the modes as implemented for this program? As you answer these questions, think about what clock is sourcing the timer and what oscillator is used for the clock.
13 comments:
Nice writeup! Introduces LPMs very nicely, I might add.
Good work!
Thanks; I realized I left out mentioning how to get out of LPMs, so I added a paragraph for that.
Nice article, You could filter out the sinusoidal wave simply by adding a resistor and a capacitor on the output. See http://en.wikipedia.org/wiki/Low-pass_filter for more information.
Actually, if you look at the msp430g2231.h file in IAR, you can skip using __Bis_sr(LPMx_bits); and just use LPMx; It is already defined. :D
Actually, this lead me astray a bit and should be updated. If you are using CCS4, there is an important step you must add if you want your main routine to go back to processing again after LPM sleep. This is a common scenario where you would put the 430 in to low power and wait for an external interrupt like a button press. Then, in your ISR, after you service the interrupt, you set a state variable such that your main routine resumes in an awake state until it is finished with its duties, and then goes back to sleep.
In order to get this behavior, you must use the LMPx_EXIT intrinsic at the end of your ISR. Or else the processor will go right back to sleep again after the ISR, and your main routine will not execute. _BIC_SR(LPMx_bits) will not work.
I finally figured this out after much head banging.
Jeff, thanks for your comment on this; I haven't had much need yet to return to main after an interrupt, so I haven't used this much. I'll have to look into it and update the description so that it's accurate.
David,
There are some different ways to specify, apparently _BIC_SR_on_exit(LPMx_bits) or something like that works as well, but the key is to get the "On Exit" in so that the part does not go right back to sleep. It's nice that TI has the flexibility to go either way, as long as one knows how to do this. I was surprised to find so little in on this, even in some of TIs guides. But it is right in the include file. Here is the relevant portion from the MSP430G2231.h file:
#define LPM0 _bis_SR_register(LPM0_bits) /* Enter Low Power Mode 0 */
#define LPM0_EXIT _bic_SR_register_on_exit(LPM0_bits) /* Exit Low Power Mode 0 */
#define LPM1 _bis_SR_register(LPM1_bits) /* Enter Low Power Mode 1 */
#define LPM1_EXIT _bic_SR_register_on_exit(LPM1_bits) /* Exit Low Power Mode 1 */
#define LPM2 _bis_SR_register(LPM2_bits) /* Enter Low Power Mode 2 */
#define LPM2_EXIT _bic_SR_register_on_exit(LPM2_bits) /* Exit Low Power Mode 2 */
#define LPM3 _bis_SR_register(LPM3_bits) /* Enter Low Power Mode 3 */
#define LPM3_EXIT _bic_SR_register_on_exit(LPM3_bits) /* Exit Low Power Mode 3 */
#define LPM4 _bis_SR_register(LPM4_bits) /* Enter Low Power Mode 4 */
#define LPM4_EXIT _bic_SR_register_on_exit(LPM4_bits) /* Exit Low Power Mode 4 */
I figured the problem would be something simple like that; thanks so much for your help! I'll test it out a bit myself until I feel like I really understand the differences, then update the tutorial to reflect the right way to handle it.
Thank you for your tutorial about LPM. It really saves me a lot of time
From the thread in the comments and verifying personally, i confirmed that using
LPMx_EXIT does indeed cause CPU to be awake on returning to the ISR routine...what I want to know however is then what happens to the stored SR on the stack..does using
bic_SR(LPMx_exit) cause the SR on the stack to be ignored?
Hey man,
I didn't know there was a name option here. So this is the same anonymous that have posted twice to you lately.
Great example but I have questions if you don't mind. How is this possible?
You said SOUT was ground but both are enabled to be output at the same time.
You also said that after we want the microcontroller to wait on interruption we should have an empty infinite loop. Here while the timer counts up to throw a flag, the controller will overflow and execute whatever code there is left in the flash(that's dumped to the memory).
I am gonna go grab my buzzer from home. :)
SOUT and SPKR are both set as outputs at the same time; P1OUT controls whether the pins is an input or an output. The actual voltage at the output is set in P1OUT; so to start, SPKR is at Vcc and SOUT is at ground. When the interrupt is triggered, each bit is toggled, putting SPKR at ground and SOUT at Vcc.
One thing to keep in mind about LPM's is that interrupts don't cause you to leave the LPM unless you specifically instruct it to do so. You can get away with not putting in the infinite loop here because there is no code that will ever cause you to get past the LPM0 line.
On the other hand, I agree that it may be good coding practice to put a for(;;); trap afterward just in case... I haven't decided it's necessary yet, but I may start holding to that convention in my own code.
It's interesting to consider how waking up from a low power mode after an interrupt routine works. When an interrupt occurs, the program counter PC and the status register SR are pushed onto the stack, and then SR is cleared. As SR contains the control bits for any low-power mode, the processor enters active mode while servicing the interrupt. On interrupt routine exit, the previous values for SR and PC are popped from the stack, which will restore any previous low-power mode.
This means that in order to keep the processor awake after interrupt routine exit, the _bic_SR_register_on_exit() routine doesn't clear bits in the SR, but rather clears bits in the copy of SR that is stored on the stack!
Post a Comment