grahamf72 169 Posted January 25, 2013 Share Posted January 25, 2013 I had a bit of spare time on my hands today, so had a bit of a play with activating low power modes. I had a basic clock sketch that I had been using to test the RTC library that I posted on the RTC thread, and modified it a bit to see if I could get it to still work in low power mode. I discovered a few things about Energia in the process, so it was quite a productive day of play. Here are a few of my notes which hopefully will be of benefit to some others. 1. I was originally relying the Energia function millis() to do software switch debouncing. The function millis() derives its count from the WDT timer triggering an interrupt at 31.25khz (on 16MHz devices such as MSP430G2553). The timing is derived from SMCLK which is in turn derived from the DCO clock. In LPM3 the DCO is disabled, so the interrupt is not called, meaning millis() no longer works. I did consider changing the WDT to get its signal from ACLK but because the smallest divider is 64, the WDT would only be called 512 times/second instead of 31250 times/second, making millis() and delay() inaccurate by a factor of ~60. Instead I changed my logic so that once a switch was pressed it stayed out of low power mode until the user had finished. Alternatively, because the 430 has Schmitt inputs, I could do hardware debouncing quite easily with just a capacitor & resistor. 2. Inside an ISR, further interrupts are disabled, so although the DCO & SMCLK are running inside the ISR, millis() still won't work, because the interrupt will not be called. In theory delay() should work as it re-enables interrupts, however I found it to be unreliable, possibly because I was getting re-entrant problems. delayMicroseconds() works for short delays, as it is a simple loop that relies on the time instructions take to execute. 3. According to the mspgcc documentation, when an interrupt is called during low power mode, it should return to low power mode when the ISR finishes. My experiments seem to indicate that this behaviour isn't reliable. My interrupt running off the A0 timer seemed to return to the LPM about 3 times in 4. The pin interrupts seemed to never return to LPM. I have looked over the code in wiring.c & winterrupts.c and cannot see why this should be the case. In any case, it is a positive, because after a pin interrupt triggers, the loop() continues to run, allowing it to do other useful functions. 4. According to the mspgcc documentation you can override the Low Power Mode when an ISR exits by using the pseudo-function _BIC_SR_IRQ(). This function can only be called from an ISR. This works from within the ISR that I created for the RTC library, however it does not work if you use AttachInterrupt() to attach an interrupt to one of the 430's input pins. The reason being that the ISR is buried in WInterrupts.c, which then calls the user function. Although I cannot see any code to cause it, the ISR that handles input pin interrupts leaves the system at full power when it exits. This is fortunate, as otherwise there would be no way to resume the system to full power after a pin interrupt. So putting it together, the basic guidelines that I have worked out for using low power mode are: In your setup() function, configure the interrupts that you need - eg Attach interrupts to your pushbuttons If you want a timed interrupt, configure it to run off ACLK & configure ACLK. Set your Low-Power-Mode in loop() as per the below - this way after an interrupt runs, your loop() will continue. void loop() { //put code here that will run on the first pass of loop, and every subsequent loop //... _BIS_SR(LPMx_bits | GIE); //Turn on low power mode // Execution will stop at the _BIS_SR line above. // Any code here will not run until AFTER an Interrupt has triggered and // the ISR has finished running. // If an Interrupt is never triggered, this code will never run. // ... } The low power modes are as follows: LPM0_bits - Low power mode 0 - the CPU & MCLK are disabled, SMCLK & ACLK still run. This means the WDT will still run and generate interrupts at 31.25kHz LPM1_bits - Low power mode 1 - the CPU & MCLK are disabled. The DCO will also be turned off if SMCLK isn't derived from it. Because Energia derives the SMCLK from DCO, DCO will still run, as will the WDT interrupts. With the standard Energia clock setup, this becomes identical to LPM0 LPM2_bits - Low power mode 2 - CPU, MCLK, SMCLK & DCO disabled, DC generator still runs (The DC generator is used for the DCO). ACLK still runs. LPM3_bits - Low power mode 3 - CPU, MCLK, SMCLK, DCO & DC Generator disabled. ACLK still runs. The difference between this and LPM2 is that with the DC generator disabled, the DCO takes a little longer to start back up. LPM4_bits - Low Power mode 4 - all clocks (including ACLK) are disabled. In this mode no timing functions will operate - the CPU can only be woken up by a pin interrupt. Attached is my clock example that uses my RTCplus library (attached to one of my posts in the RTC thread) to display date and time on a 16x2 LCD and allow date and time to be set with a pair of buttons. My earlier version of this that didn't switch to low power mode was using 5mA as measured with a DMM across the VCC jumper on the launchpad (not including the LCD display current). By converting this to low power mode, consumption dropped to 2uA measured on my DMM. Each second as it ticks, my DMM spikes up to about 300uA. The actual consumption during the tick is probably closer to 5mA but for such a short time my DMM couldn't catch it. If you want to test this example, you will need to amend the RTCplus header so that the line "#define RTCSUBSECONDS" is commented out. Using LPM requires a bit of thought about what functions will run when, but for a task that only needs to do something infrequently or responding to user input, it can make a huge difference to the power consumption. sketch_Low_Power_LCD_Clock.zip calinp, xv4y and tripwire 3 Quote Link to post Share on other sites
xv4y 46 Posted January 25, 2013 Share Posted January 25, 2013 Good work. I will have to dig in to LPM soon for my remote Temp/Hum. sensor soon... Yan. Quote Link to post Share on other sites
roadrunner84 466 Posted January 25, 2013 Share Posted January 25, 2013 It is true that _BIC_SR_IRQ() can be called only from the ISR (interrupt service routine) but not from any functions called from within that ISR. As the ISR are part of the Energia library (as you state, in winterrupts.c) you cannot influence SR with _BIC_SR_IRQ() from within the attached function. As a suggestion to the energia team, how about having attached functions returning true or false (or sleep or wakeup when using an enum), where either of these will wake from LPM and the other doesn't? Returning to sleep from an interrupt should be reliable behaviour, it is part of the MSP430 processor architecture and cannot be overridden by the IDE, library or compiler. If you don't have reliable wakeup behaviour, you probably have wakeup behaviour inside your library (winterrupts.c probably). I am using LPM extensively in my watch firmware, even stronger; I do not have any "main loop"! After initialisation, the main() function will go in low power mode and none of my interrupts will ever wake it; all code is inside the interrupts. tripwire 1 Quote Link to post Share on other sites
Rickta59 589 Posted January 25, 2013 Share Posted January 25, 2013 As a suggestion to the energia team, how about having attached functions returning true or false (or sleep or wakeup when using an enum), where either of these will wake from LPM and the other doesn't? There is actually more than sleeping or not sleeping. You might want to sleep in LPM4 until an external interrupt is detected, at that point you might just want to switch to LPM3 to run a clock, and then switch back to LPM4. In a previous post, http://forum.43oh.com/topic/2759-problem-with-interrupts-on-p1-and-a-timer/#entry23367 , I outlined some replacement code for the underlying interrupt structure for that posters specific problem. I created an attachInterrupt2() function that allows you to adjust the power mode from user functions called from the ISR. Energia is an msp430 implementation of the Arduino API. The API has no concept of low power mode. They way it works by default is not low power friendly as you have discovered with the millis() implementation. Energia attempts to stay close to the API and take advantage of low power where it can, see the way the ADC sleeps until the reading is complete. To fully embrace all the low power features of the msp430 you are going to have to abandon a lot of the Arduino API. Energia is just a framework. At some point you will probably outgrow it. If so, then I guess we have done a good job of getting you interested enough in the msp430 to dive deeper into its inner workings. -rick xv4y 1 Quote Link to post Share on other sites
roadrunner84 466 Posted January 25, 2013 Share Posted January 25, 2013 Yes, there are a lot of low power modes, yes all are low power, but one: in active mode and only in active mode the body of the loop() function will be executed. Even the ISR are only executed in active mode. However, changing from LP4 to LPM3 will enable the ACLK, and thus may enable more interrupt sources (like timers). Quote Link to post Share on other sites
grahamf72 169 Posted January 25, 2013 Author Share Posted January 25, 2013 I would think a suitable workaround for the problem of not being able to issue _BIC_SR_IRQ in a user function for AttachInterrupt(), and still maintain Arduino compatibility, would simply be for the underlying ISR to clear all LPM_bits so the ISR exits in active mode. The user code (most likely in loop) would then continue to run from where LPM was issued, and the programmer can then code to switch back to their choice of LPM. This seems to be the behaviour it currently exhibits, although I cannot work out why, as there is no _BIC_SR_IRQ in the current WInterrupts.c. Quote Link to post Share on other sites
Rickta59 589 Posted January 25, 2013 Share Posted January 25, 2013 although I cannot work out why, as there is no _BIC_SR_IRQ in the current WInterrupts.c. https://github.com/energia/Energia/blob/master/hardware/msp430/cores/msp430/wiring.c line 222 ... /* Exit from LMP3 on reti (this includes LMP0) */ __bic_status_register_on_exit(LPM3_bits); which is why you need to disable the watchdog isr to even do anything with low power and Energia. Quote Link to post Share on other sites
Rickta59 589 Posted January 25, 2013 Share Posted January 25, 2013 Yes, there are a lot of low power modes, yes all are low power, but one: in active mode and only in active mode the body of the loop() function will be executed. Even the ISR are only executed in active mode. However, changing from LP4 to LPM3 will enable the ACLK, and thus may enable more interrupt sources (like timers). The code I shared does all its work in the ISR routines, flipping between LPM4 and LPM3. Once loop() calls LPM4; .. it never gets control again. -rick Quote Link to post Share on other sites
pabigot 355 Posted January 25, 2013 Share Posted January 25, 2013 Energia is just a framework. At some point you will probably outgrow it. If so, then I guess we have done a good job of getting you interested enough in the msp430 to dive deeper into its inner workings. If and when you do outgrow Energia, please take a look at BSP430, which is designed for complete control of low power mode, a couple types of application architecture, and a bunch of other stuff in a framework that works on ten different boards across the 2xx, 4xx, 5xx, and FRAM families of MSP430. (I don't have any 3xx MCUs, and don't see the point in making it work on the F1611 tmotes I have, but there's no technical reason it couldn't work on those too.) Quote Link to post Share on other sites
RichardVowles 12 Posted January 26, 2013 Share Posted January 26, 2013 Is this new? Because this looks awesome! Quote Link to post Share on other sites
roadrunner84 466 Posted January 26, 2013 Share Posted January 26, 2013 So where's the real advantage on using this bsp? I mean, chips are either compatible in pinout or they are not. When their pinout is not compatible you'd have to rewrite at least part of your software anyways Quote Link to post Share on other sites
arheops 0 Posted January 26, 2013 Share Posted January 26, 2013 lowpower sleep can be done by following: void mdelay(uint32_t milliseconds) { WDTCTL = WDT_ADLY_1_9; // WDT triggered from ACLK after ~6ms, millis() will be skewed BTW uint32_t wakeTime = milliseconds/5; while(wakeTime>0){ wakeTime--; /* Wait for WDT interrupt in LMP3 */ __bis_status_register(LPM3_bits+GIE); } WDTCTL = WDT_MDLY_0_5; // WDT back to original Energia setting, SMCLK 0.5ms/IRQ } it not very accurate, but enought for most cases. watchdog interrupts supplied by energia ide. p.s code is tested and workign on launchpad. Quote Link to post Share on other sites
grahamf72 169 Posted January 26, 2013 Author Share Posted January 26, 2013 https://github.com/energia/Energia/blob/master/hardware/msp430/cores/msp430/wiring.c line 222 ... /* Exit from LMP3 on reti (this includes LMP0) */ __bic_status_register_on_exit(LPM3_bits); which is why you need to disable the watchdog isr to even do anything with low power and Energia. Yes I am aware of that in the WDT ISR. However in LPM3 the SMCLK is disabled, so the WDT will never trigger while in LPM3. This makes clearing LPM3 after the WDT ISR runs a bit pointless. LPM1 is the lowest mode in which the WDT can still operate. If the WDT triggers while in another ISR, it will save the SR at the time, which will be Active Mode, because all ISR's run in Active Mode. When the WDT ISR exits, it will only change the SR that was pushed onto the stack when the WDT was called. Control then returns back to the user's ISR. When that ISR exits, it will restore the SR that it saved at the time it was called, putting the system back into LPM. Consequently the call to __bic_status_register_on_exit in the WDT cannot restore the system to active mode. As I said previously, I am not sure why the call to the Port1 & Port2 ISR's bring the system out of LPM3, because they don't call __bic_status_register_on_exit. Perhaps a bug in the compiler? Personally, I think the Port_1 & Port_2 functions should have a call to __bic_status_register_on_exit(LPM4_bits). Otherwise if whatever is currently causing the return to active mode gets fixed, there is no way to ensure a return to active mode from a pin interrupt. __attribute__((interrupt(PORT1_VECTOR))) void Port_1(void) { uint8_t i; for(i = 0; i < 8; i++) { if((P1IFG & BV(i)) && intFuncP1[i]) { intFuncP1[i](); P1IFG &= ~BV(i); } } //The following line isn't actually in WInterrupts.c, but I believe it should be, in both Port1 & Port2 functions. __bic_status_register_on_exit(LPM4_bits); } Quote Link to post Share on other sites
roadrunner84 466 Posted January 26, 2013 Share Posted January 26, 2013 Lpm1 is the lowest in which smclk can still operate, so if you run wdt from smclk you can't user it in a lower lpm. In lpm3 the aclk is still active, so if you source wdt from aclk you can go as low as lpm3. Quote Link to post Share on other sites
pabigot 355 Posted January 26, 2013 Share Posted January 26, 2013 So where's the real advantage on using this bsp? I mean, chips are either compatible in pinout or they are not. When their pinout is not compatible you'd have to rewrite at least part of your software anyways Is this referring to BSP430? In short, it's as explained on the project home page, and motivated because I want to run the same code on multiple boards, often with incompatible peripheral implementations (USCI/USCI5/eUSCI) or resource assignments (T0A3 or T1A5), without duplicating code. Adding two files to support dozens of applications without changing anything else is pretty useful to me. I'm not trying to hijack this thread or compete with Energia (completely different target audiences), so if you want to know more there's mention in some other threads where you could follow-up, or start a new one. Quote Link to post Share on other sites
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.