This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

Having trouble exiting low-power mode with MSP430 and Timer_A

Other Parts Discussed in Thread: ENERGIA, MSP430F2013

Hi all,

I've been looking around and I haven't been able to find any good help on exiting low power mode, in particular in use with Timer_A. The following is my C code to turn on the LED for a split second, by implementing a delay in between turning the LED on and off:

int main(void)
{
WDTCTL = WDTPW + WDTHOLD; // Stop WDT
P1DIR |= 0x01; // P1.0 output
P1OUT |= 0x01; // LED ON
delay_us(65535); // DELAY
P1OUT ^= 0x01; // LED OFF

}

void delay_us(unsigned int us) {
TACCTL0 = CCIE;
TACCR0 = us;
TACTL = TASSEL_2 + MC_1; // UP MODE
_bis_SR_register(LPM0_bits);
}

#pragma vector=TIMERA0_VECTOR
__interrupt void Timer_A (void)
{
_bis_SR_register_on_exit(LPM0_bits);
}

Note that LMP0_bits is defined as CPUOFF. Most of this code came from TI's example projects page (msp430x20x3_ta_02, Timer_A, Toggle P1.0, CCR0 Up Mode ISR, DCO SMCLK).

With this code above, the LED simply stays turned on forever. Could anyone tell me if I am entering and exiting low-power mode correctly?

Thanks,

Matt

  • A couple of things spring to mind

    1. you don't enable interrupts so TImer_A ISR will never fire                 _bis_SR_register(LPM0_bits + GIE);

    2. Even if it did fire you don't loop anything within main() so if Timer_A did work it would toggle the LED off and main would terminate.

    In the example, TI uses Timer_A ISR to do all the work as that is the only thing that will execute without a loop-forever in main().

  • hi matt, i found few mistakes in ur code,i have modified those, i hope it will work now.

    void delay_us(unsigned int ); 

    void main(void)
    {
    WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer

    P1DIR= 0xFF; // Configure Port pin

    TACCTL0 = CCIE; // Capture compare interrupt enable 

    TACTL = TASSEL_2;

    TACTL |= MC_1; // Up mode, TA start

     P1OUT |= 0x01; //LED ON

    delay_us(65535); // DELAY

    P1OUT &= ~(BIT0); // LED OFF

    }

    void delay_us(unsigned int us) {

    TACCR0 = us;

     __bis_SR_register(LPM0_bits + GIE); 
    }

    #pragma vector=TIMERA0_VECTOR
    __interrupt void TIMER_ISR(void)
    {
    TACTL &=~(MC_1); // Stop Timer_A
    __bic_SR_register_on_exit(LPM0_bits+GIE); // Clear LPM bits upon ISR Exit
    }





  • Thanks for the reply bharathi,

    It does work as you have shown above. However, my intent is to create a timer function that can be used multiple times at any length I choose (I am just running it using a blinking LED to make sure that it works). Do you know which variables I would have to reset, and where to do that, in order for my delay_us function can be used multiple times, for example:

    LED_ON

    delay_us(65535);

    LED_OFF

    delay_us(65535);

    LED_ON

    delay_us(65535);

    delay_us(65535);

    LED_OFF

    Right now, if I call delay_us() a second time, it seems to get stuck in an infinite loop.

    Thanks,

    Matt

  • UPDATE:

    Below is the code for essentially what I want to accomplish:

    void delay_us(unsigned int full, unsigned int remainder);

    int main(void)
    {
    WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer
    P1DIR= 0xFF; // Configure Port pin
    //TACCTL0 = CCIE; // Capture compare interrupt enable
    //TACTL = TASSEL_2;
    //TACTL |= MC_1; // Up mode, TA start
    P1OUT |= 0x01; //LED ON
    delay_us(15, 16975); // ONE SECOND
    P1OUT ^= 0x01; // LED OFF
    delay_us(15, 16975); // ONE SECOND
    P1OUT ^= 0x01; // LED ON
    delay_us(30, 33950); // TWO SECONDS
    P1OUT ^= 0x01; // LED OFF
    delay_us(30, 33950); // TWO SECONDS
    P1OUT ^= 0x01; // LED ON
    delay_us(45, 50925); // THREE SECONDS
    P1OUT ^= 0x01; // LED OFF
    delay_us(45, 50925); // THREE SECONDS

    }

    void delay_us(unsigned int full, unsigned int remainder) {
    //unsigned long numCycles = remainder*TA_CLK_FREQ_IN_MHZ;
    unsigned int i;
    for(i = 0; i < full; i++) {
    TACCTL0 = CCIE; // Capture compare interrupt enable
    TACTL = TASSEL_2; // SMCLK
    TACTL |= MC_1; // Up mode, TA start
    TACCR0 = MAX_TA - 5; // ELIMINATE CLOCK CYCLES ALREADY ACCOUNTED FOR BY C CODE INSTRUCTIONS.
    __bis_SR_register(LPM0_bits + GIE);
    }
    TACCTL0 = CCIE; // Capture compare interrupt enable
    TACTL = TASSEL_2;
    TACTL |= MC_1; // Up mode, TA start
    TACCR0 = remainder; // TACCR0 CAN ONLY HOLD 16 BITS WHICH IS WHY MAX_TA IS 2^16 - 1 = 65535.
    __bis_SR_register(LPM0_bits + GIE);
    }

    #pragma vector=TIMERA0_VECTOR
    __interrupt void TIMER_ISR(void)
    {
    TACTL &= ~(MC_1); // Stop Timer_A
    __bic_SR_register_on_exit(LPM0_bits+GIE); // Clear LPM bits upon ISR Exit
    }

    1. Does anyone know what SMCLK is set to so that I can use that to accurately determine how many cycles to wait (I think it's like 1 MHz or 1.1 Mhz)?

    2. Are there any unnecessary repeated lines in my delay_us function (in particular in the for loop), that I can take out to keep my program from slowing down?

    3. How can I determine how many cycles each of the instructions in my delay function take up (both the for loop and the code after the loop), so that I can remove them from the timer in order to get an even more accurate delay? I just used the number 5 in the for loop at random as an example of what I'm trying to do.

    Thanks,

    Matt 

  • Hi Matt,

    Some tips:

    What MSP430 are you using? I was wondering if you might want to look into something like Energia http://energia.nu/?

    There are some different options to set up a delay, depending on your goal.

    • If you just need an accurate delay function:
      • You can use __delay_cycles(x); where x is the number of MCLK cycles that you will wait - this will give you a nice precise number of cycles, but your part will be in active mode during the whole delay. You will know your SMCLK speed based on how you have set it up - if you use one of the value line parts, I'd recommend using the DCO calibration constants to set up your frequency (you can find code examples of this).
    • If you need low power and you want to use a timer for the delay with a function like this:
      • I'd just have the function get passed the number of clock cycles rather than the number of us. This will be more efficient (won't be trying to calculate the # of cycles to make up 1 us at run time) and easier to write a function for. If you want it to be easy for the user and you have particular delays like 1 second that you know you need, you could just make some #define values or a lookup table for the # of cycles to make up 1 second of delay.
      • In addition, I wouldn't set the timer up for 1 us ticks if I need to have a long 1 second delay - I'd use the clock and timer dividers to divide my clock frequency down, or even better use a different clock source like a watch crystal 32768Hz for my timer instead of my fast SMCLK freq. Then you don't have to do that looping that you have in your delay function - you can just set the timer up for the entire 1 sec delay because the # of ticks will be small enough to be less than 0xFFFF.

    Regards,

    Katie

  • Hi Katie,

    Thanks for the reply. I am not entirely sure how I will be implementing this timer delay function, but I was told that it needs to be able to take in a variable, which I do not think the __delay_cycles(x); function allows. Otherwise, I would definitely use it.

    I will also definitely consider passing in clk cycles rather than us, once I start getting more into the project and what it actually entails.

    As for dividing the clock frequency down, first off, I only used 1 second delays because it was easy to see the led blink and recognize the time that was delayed. I was told that I would be waiting around magnitudes of 50 us, 100 us, or even 500 us, maybe up to 1 ms, but thats about the max. Furthermore, I was asked to get accurate delays of around a few us. If that is the case, I would need a high frequency (1 MHz) to keep everything accurate, but at the same time, that frequency shouldn't exceed the kinds of delays that I would expect. Is this a reasonable approach to my task?

    Matt

  • Matthew Wasko said:

    3. How can I determine how many cycles each of the instructions in my delay function take up (both the for loop and the code after the loop), so that I can remove them from the timer in order to get an even more accurate delay? I just used the number 5 in the for loop at random as an example of what I'm trying to do.

    'View > Disassembly ' 

    That will display a window with the assembly code of your 'C' program.

  • Hey matt,

    can you tell what MSP430 are you using?

  • Joseph: Thanks for the tip on reading the assembly.

    bharathi: I am using an MSP430F2013.

  • One last thing before I close this post:

    When I enter LPM, will the next line ever be executed before I exit LPM? For example:

    void enable_test()
    {
    P1DIR = 0x00;
    P1IE = EN_0 | EN_1 | EN_2;
    __bis_SR_register(LPM0_bits + GIE);
    unsigned int i = 1;
    }

    Will 'i' ever be assigned before the interrupt handler is called on and it is subsequently exited? I think that is the case, but when I put a breakpoint on the last line in the function call, it hits that breakpoint before the interrupt handler actually goes off (it goes off on a GPIO pin low-to-high transition). If my assumption above is true, why does the breakpoint hit and point to the last line already?

    Thanks,

    Matt

  • Matthew Wasko said:
    When I enter LPM, will the next line ever be executed before I exit LPM? For example:

    No. When you enter any LPM, MCLK is stopped.

    However, the same MCLK cycle the LPM bits are set in the status register, causing MCLK to stop, the CPU will fetch the next instruction. So if you set a breakpoint on the instruction directly after an LPM entry, the breakpoint will be triggered before (or rather exactly at the moment) the LPM is entered. As Breakpoints are triggered at the instruction fetch from the memory location, not at command execution.
    This is why usually an LPM entry is followed by a NOP. The comment 'for debugging' in many demo codes doesn't mean this is where you should place the breakpoint (you could put it on any instruction, not just the NOP, so this would be nonsense) but rather that you should NOT put it there and the additional NOP allows you to put it on the next maningful instruction fetched after LPM exit.

  • matt, just try this code , and let me know whether its working or not..

    #include "msp430 x20x3.h"

    #include <intrinsics.h>

    void delay(unsigned int);

    void main(void)

    {
    WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer
    P1DIR= 0xFF; // Configure Port pin

        BCSCTL1= CALBC1_12MHZ;    // Configure Clock system

        DCOCTL = CALDCO_12MHZ;     // Set DCO = 12MHz

        BCSCTL3 = LFXT1S_2;                     // ACLK = VLO

        TACTL= TASSEL0;                         // TACLK = ACLK=VLO

        TACCTL0 = CCIE;                         // Capture compare interrupt enable 

        TACTL |= MC_1;                          // Up mode, TA start

    while(1)

        {
    P1OUT |= BIT0; //LED ON

    delay(11999); //delay for 1sec


    P1OUT &= ~(BIT0); // LED OFF
    delay(11999);  //delay for 1sec
    P1OUT |= BIT0; // LED ON
    delay(23999); //delay for 2sec


    P1OUT &= ~(BIT0); // LED OFF
    delay(23999); //delay for 2sec

    }

    }

    void delay(unsigned int count)

    {

        TACCR0 = count;                    

    __bis_SR_register(LPM3_bits + GIE);  // LPM3, Global Interrupts Enable

    }

     

    // Timer_A0 Interrupt Service Routine

    #pragma vector=TIMERA0_VECTOR

    __interrupt void TIMER_ISR(void)

    {

      

            TACTL &=~(MC_1);                    // Stop Timer_A   

        __bic_SR_register_on_exit(LPM3_bits+GIE); // Clear LPM bits upon ISR Exit

    }

    here i used AUXILORY CLOCK for TIMERA ,

    as per my knowledge the following statements should be with in the main

     (TACTL= TASSEL0;                         // TACLK = ACLK=VLO

        TACCTL0 = CCIE;                         // Capture compare interrupt enable 

        TACTL |= MC_1;                          // Up mode, TA start)

    and delay(11999) gives 1 second delay i.e

    for 1 tick----->1/12000HZ (since ACLK connected to VLO which works at 12000HZ by default)

          ? <------- 1second

    then we will get 12000 as count, but TIMERA starts from '0' we need to upload (12000-1=11999) value in TACCR0 register

  • Thanks Jens-Michael.

    bharathi: I already have an implementation of my timer working. Just curious though, why/how did you decide to use LPM3 instead of LPM0. All of the code I use or have read just use LMP0. What would be a good reason for picking one LPM over the other (I know that different clocks are turned off, but not much else)?

    Matt

  • actually in LPM3

    – CPU is disabled
    – MCLK and SMCLK are disabled
    – DCO's dc-generator is disabled
    – ACLK remains active

    and in LPM0

    – CPU is disabled
    – ACLK and SMCLK remain active
    – MCLK is disabled

    so if you want to give a delay,LPM3 is enough, no need to go for LPM0.

    anyway both will work but i chose simple one.

  • Ok, so LPM3 goes into a "deeper" LPM correct? What do you mean by LPM3 is enough, and I do not need to go for LPM0? Are you saying its better to go as deep into LPM as possible as long as everything still functions, to save more power I guess?

    Matt

  • yeah,in order to save power LPM3 is one of the best choices.

    actually we require different LOW POWER MODES in different situations.so we have to choose corresponding LOW POWER MODE according to our requirement in order  to save the power

    as i said before  in LPM3 everything is disabled except ACLK,

    here TIMERA is connected to  ACLK which is connected to VLO (Internal low frequency oscillator with 12-kHz nominal frequency) i.e

    TIMERA <--ACLK <--VLO

    obviously it is enough to give a simple delay as well as to save the power......

  • Thanks! Makes perfect sense.

  • bharathi kala said:
    as i said before  in LPM3 everything is disabled except ACLK,

    That's not entirely true. Depending on MSP family, some modules may make an unconditional clock request that maybe keeps SMCLK active even in LPM3.
    Also, a DMA transfer will reactivate MCLK and therefore DCO (if source for MCLK) even in if any LPM.

    bharathi kala said:
    VLO (Internal low frequency oscillator with 12-kHz nominal frequency)

    with quite a large variation across MSPs and a large temperature drift and drift with VCC. So timings by VLO may be off by +-66%
    Well, for a simple delay this might be still acceptable.

**Attention** This is a public forum