1
\$\begingroup\$

I am using an MSP430FR2433 with the development board from TI and want to use its low power mode 3.5 so that I can wake it up from a GPIO pin or the RTC. I am using one of the LPM3.5 example codes in the resource explorer in CodeComposer studio as a reference. My goal is:

  1. To keep the microcontroller in a low power mode until a button is pushed or the RTC wakes it up (GPIO or RTC interrupt).
  2. To activate the associated interrupt service routing (ISR)
  3. Set a variable "interrupt_type" to "1" or "2" depending on which ISR is activated
  4. Return to main() after the ISR and perform specific functions depending on the value of "interrupt_type".

I am able to do 1,2, and 3, but I am not able to change the value of "interrupt_type" in the ISR. It is always the initialized value that was declared before main(). I have read that declaring it as a global (volatile) variable should make it work but it doesnt. Here is the relatively simple code in question:

#include <msp430.h>
void initGpio(void);
void initCrystalclk(void);
void GPIOfunction(void);
void RTCfunction(void);
volatile int interrupt_type = 0;
void main(void)
{
    WDTCTL = WDTPW | WDTHOLD;               // stop watchdog timer
    initGpio();                             // Configure GPIO
    initCrystalclk();                       // Initialize XT1 32kHz crystal
    P1OUT |= BIT1;
    if (SYSRSTIV == SYSRSTIV_LPM5WU)        // If MCU wakes up from LPM3.5.
    {
           __enable_interrupt();               // The RTC interrupt should trigger now...
           __no_operation();         
          
           if(interrupt_type==1){
            GPIOfunction();
            }
           if(interrupt_type==2){
            RTCfunction();
           }
    }
    else {                  //waking up from cold start
        static int const Offtime=30;        //time in seconds to stay in LPM
        RTCMOD = 32*Offtime-1;              //forcing LPM3.5 (i.e. run RTC module)
        RTCCTL = RTCSS__XT1CLK | RTCSR |RTCPS__1024| RTCIE; //clock control bits
    }
    P2IES |= BIT3;                          // P2.3 low/high interrupt edge select for button
    P2IE |= BIT3;                           // P2.2 Interrupt enable  P1IE |= BIT6;  for MP
    P2IFG &=~BIT3;                          // in case PxIES set the interrupt flag
    PMMCTL0_H = PMMPW_H;                    // Open PMM Registers for write
    PMMCTL0_L |= PMMREGOFF;                 // and set PMMREGOFF
    __bis_SR_register(LPM3_bits | GIE); // enter LPM 3.5 with interrupts enabled
    __no_operation();
}

#pragma vector=PORT2_VECTOR                 //Interrupt routine when switch closes
__interrupt void Port_2()
{
    __disable_interrupt();          //this is to prevent RTC from interrupting (not tested)
    interrupt_type=1;
    P2IFG &=~BIT3;
}

#pragma vector = RTC_VECTOR
__interrupt void RTC_ISR(void)
{
    switch(__even_in_range(RTCIV, RTCIV_RTCIF))
    {
        case RTCIV_NONE : break;            // No interrupt pending
        case RTCIV_RTCIF:                   // RTC Overflow
            interrupt_type=2;
            break;
        default:          break;
    }
}

void GPIOfunction(void)
{
 //do something eg. blink led
}

void RTCfunction()
{
    //do something else
}

void initGpio(void)                         //initialize ports
{
    P1DIR = 0xFF; P2DIR = 0xFF; P3DIR = 0xFF;
    P2DIR &= ~BIT3;                         //Set P2.3 as input
    P2REN |= BIT3;                          //Enable Resistor
    P2OUT |= BIT3;                          //Configure P2.3 as pulled-up for debug purpose using button
    PM5CTL0 &= ~LOCKLPM5;                   //Turn ON digital I/O 
}
void initCrystalclk(void)                   //note: crystal clock needs to be connected on Dev board
{
    P2SEL0 |= BIT0 | BIT1;                  // set XT1 pin as second function (for crystal as clock)
       do
       {
           CSCTL7 &= ~(XT1OFFG | DCOFFG);   // Clear XT1 and DCO fault flag
           SFRIFG1 &= ~OFIFG;
       } while (SFRIFG1 & OFIFG);           // Test oscillator fault flag
}

If I put the GPIOfunction() or RTCfunction() inside the ISR, it works, but they are quite lengthy and have their own interrupts, which apparently is not good practice. Please let me know if there is a way to modify the variable value inside the ISR and have it readable by the main() after the ISR. Essentially I am hoping to set a flag inside the ISR that indicates which ISR was called so that I can run a specific subroutine in main(). Thanks in advance!

UPDATE: I can confirm now that the RTCfunction() does get called and the ISR for the RTC is able to modify the interrupt_type variable. THe GPIO ISR however cannot.

\$\endgroup\$
12
  • 1
    \$\begingroup\$ typo in the interrupt code: _interrupt_type=1; should be: interrupt_type=1; ? \$\endgroup\$ Commented Mar 19, 2021 at 18:48
  • \$\begingroup\$ Of course, declare a global variable that can be set in ISR, then you check it in main() and you do something and reset the variable. \$\endgroup\$ Commented Mar 19, 2021 at 20:00
  • \$\begingroup\$ @ChrisKnudsen yes, corrected thanks. But not part of the issue. \$\endgroup\$
    – vague1
    Commented Mar 19, 2021 at 21:11
  • 1
    \$\begingroup\$ Are you sure that the interrupt handlers actually get executed? \$\endgroup\$
    – CL.
    Commented Mar 19, 2021 at 21:14
  • \$\begingroup\$ @CL. yes, I've tried putting blinking LED codes in the ISRs to check. The flow after wakeup does seem to be as expected: enter main()-->enter the if statement-->enter ISR-->exit ISR-->continue the main code until it reaches the sleep mode again. Only the variable does not seem to get modified. \$\endgroup\$
    – vague1
    Commented Mar 19, 2021 at 21:20

3 Answers 3

3
\$\begingroup\$

EDIT: The below explanation is not the solution to OPs problem. He is using LPM3.5. LPMx.5 is fundamentally different to the other LPMs. LPMx.5 first resets and restarts the processor upon an interrupt and only after initializing runs the actual ISR to the interrupt.

(I will leave the answer here in case anybody else comes here in search for a solution for their standard LPM problem.)

The problem is that your main function gets executed once and then exits. As soon as your main function exits, the msp430 gets reset. This means the whole program will be restarted and all the variables will be reinitialized. This in turn means that also the line volatile int interrupt_type = 0; will be executed again and your functions in question will not be called.

We analyze what happens from the start of the program:

  1. main() gets called
  2. watchdog timer, GPIO and crystal get initialized
  3. As the processor has freshly been started, the program will enter the else clause
  4. configure & enable interrupts
  5. go into sleep state

Now when an interrupt occurs following happens:

  1. interrupt occurs
  2. processor wakes up from sleep
  3. ISR is executed
  4. the main program picks up where it left in main() after being put to sleep (i.e. on the line below __bis_SR_register(LPM3_bits | GIE);
  5. __no_operation() is called
  6. main() terminates!
  7. processor resets

In a typical program the main function should not exit. A typical program could look something like this:

/* global variables */
volatile bool interrupt_flag = 0;
// etc...

/* main function */
void main(){
  /* SETUP (will be run ONCE) */
  setup_function_1();
  setup_function_2();
  // etc...

  /* INFINITE PROGRAM LOOP */
  while(1){
    gotoSleep();  // go to low power mode and wait for interrupt
    
    // at this point an interrupt has occurred and interrupt_flag may have been set
    if(interrupt_flag){
      some_function();
      interrupt_flag = 0;  // clear our flag
    }
  }
}

/* ISR for interrupt XYZ */
__interrupt void XYZ_ISR(void){
  // code to handle interrupt
  // and set interrupt_flag when desired
}
\$\endgroup\$
3
  • 1
    \$\begingroup\$ Thank you for your post. Typically (i.e. for most low power modes-LPMs) as you say, the ISR is executed first after the interrupt occurs. However, for LPMs 3.5 and 4.5, this is not the case. this program goes into LPM 3.5. Upon wakeup from these modes, the device starts executing from main(), not the ISR (I believe it's called a brownout reset). The SYSRSTIV_LPM5WU can be used to indicate whether the wakeup was from an interrupt or a reset. So upon wakeup, the program goes from main()-->if{true}-->ISR-->end of if{true}-->rest of the main() code-->back to sleep. \$\endgroup\$
    – vague1
    Commented Mar 20, 2021 at 2:32
  • \$\begingroup\$ The LPMx.5 modes cut power from the CPU and RAM; wakeup implies a reset and complete reinitialization. \$\endgroup\$
    – CL.
    Commented Mar 20, 2021 at 5:04
  • \$\begingroup\$ @vague1 Yes you are right. I did not realize that my answer does not apply to your problem. \$\endgroup\$
    – Lukas
    Commented Mar 20, 2021 at 20:09
1
\$\begingroup\$

Update: I found a possible workaround, though the inability to change the variable in the ISR still remains. I read the port interrupt flag vector P2IFG to determine whether the interrupt is from GPIO and on which pin the interrupt occurred. The following code at the start of the first if{} in main() before __enable-interrupt() seems to work:

if(P2IFG==0x08){interrupt_type=1;} //if button P2.3 is pressed, set variable

Then the ISR is entered, where the just the interrupt flag is cleared. Then it enters the

 if(interrupt_type==1)

and then enters the GPIOfunction() as needed.

This this is not the solution to the original issue and more of a workaround, but I hope this is still a good practice to use IFG vectors. Please do let me know if there is another way to do it.

\$\endgroup\$
0
\$\begingroup\$

This may be an old, stale issue. But, just in case...

Isn't the global variable, interrupt_type, initialized to 0 on any "reset"? So, from certain low power modes or POR, the value of interrupt_type will be reset to 0.

I believe if you want a global to be left alone (after program load), you need to mark it as "noinit" or "persistent". In that case, the compiler and linker will put the variable in a RAM section/location that will NOT be initialized on reset prior to reaching "main". The compiler-linker supplied code that runs before main usually clears RAM and sets any initialized globals to their specified values... not the behavior you want. Good luck!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.