MSP430 Interrupts and Low Power

Using interrupts makes your MSP430 respond faster and allows the CPU to spend more time processing, as well as take advantage of low power

Basics of Interrupts

If you don’t have experience programming microcontrollers, interrupts might seem like a thing of the past when IRQ switches needed to be set for cards installed in a computer. However, interrupts are still happening at the lower levels, managing DMA and other events. In microcontrollers such as the MSP430, interrupts play a key role in enabling fast response, scalability and detection of rare events. Using interrupts allows the MSP430 can detect a button press, or the arrival of a packet from a transceiver, oscillator faults and other exceptions.

Interrupts are generally any trigger that causes the CPU to deviate from executing instructions in the order set by the instructions. When an interrupt occurs, the CPU of the MSP430 saves its current state and goes to handle the interrupt handler if one exists. As we alluded to previously, interrupts can be both exception fault interrupts, which indicate an error has occurred in the MSP430, or more event interrupts such as the GPIO interrupt triggering. Given the vast number of interrupts, the MSP430 prioritizes them. Exception interrupts are typically the highest priority because they require immediate attention. Moreover, interrupts are designed hierarchically. A peripheral module may generate only one interrupt, and the interrupt service routine will need to check the actual source of the interrupt. Even though interrupts are individually set in modules, there is a Global Interrupt Enable (GIE) bit that can disable the vast majority of them at once. We “mask” the interrupts when we prevent them from triggering their handler in the CPU. The interrupts may occur, but they are masked (hidden) from the CPU and will not cause their handler to run. One way of categorizing interrupts is by the effect they have on the system. The MSP430 generally categorizes interrupts as follows:

  • System Reset Interrupts – When triggered, these interrupts cause a reset of the system
  • Non Maskable Interrupts – These interrupts cannot be masked. Typically these are fault handlers such as oscillator faults and flash access violation which indicate a critical condition
  • Maskable Interrupts – Most interrupts on the MSP430 fall into this category. GPIO, timers and peripherals all generate interrupts that can be masked by the GIE bit.

System Reset interrupts have no interrupt service routine. If you remember the JTAG pinout you might remember the RST/NMI pin. This pin is by default the MSP430’s reset pin and it generates a complete reset of the MSP430.

All Non-Maskable Interrupts share the same NMI interrupt service routine. When configured in NMI mode, the RST/NMI pin will trigger the NMI interrupt handlers. Flash access violation is another non-maskable interrupt source that must be specifically enabled by the ACCVIE bit. Oscillator fault is similar in that it also needs to be enabled specifically to detect when an oscillator has failed. In the NMI ISR you must specifically check for the flags that caused the condition, reset them, and handle them accordingly.

As we said, most interrupts generated by peripherals such as GPIO and timers fall into the maskable interrupt category in that they can be masked from the CPU by disabling the General Interrupt Enable (GIE) bit. When using the MSP430, disabling all interrupts can often result in missing important event such as receiving characters. It’s usually preferable to write the application in such a way as to make interrupts enabled as much as possible. Enabling and disabling the GIE can be done via several calls, the most common shown below:

Functions to control global MSP430 interrupts

 
__enable_interrupt(); // Enable Global Interrupts by GIE = 1
__disable_interrupt(); // Disable Global Interrupts by GIE = 0


In order for an ISR to trigger, all that’s required is that the interrupt for the module be enabled, GIE is set and the condition for the interrupt is met. Note that this can even happen inside ISRs if the GIE is enabled, causing a possibly dangerous condition called nested interrupts which we will discuss later.

Interrupt Priorities

It is possible for multiple interrupts to occur at the same time. Which interrupt will the MSP430 handle first? The interrupts in the MSP430 and all microcontrollers have priorities. In the case of the MSP430 these are fixed and you must take care that your application does not depend on an order that is contrary to the priorities. For example, Timer_A and Timer_B have different priorities, and it might be necessary to choose one or the other when doing the hardware design or software implementation. The priorities table is quite large and we won’t reproduce it here. You can easily see it on page 37 of SLAU144J for the MSP430G2553 and other devices in the family. The table itself is generic for many of the priorities. It requires the datasheet to specify the device-specific interrupt source. When multiple interrupts occur, the highest priority interrupt will be handled first. When the interrupt service routine is finished, if any other interrupts are pending, the highest of them will be handled, and so forth until all the interrupts are managed.

Anatomy of Handling an Interrupt

An interrupt is not very different than a conversation with two people. Let’s say you’re talking to a person and suddenly someone talks to you. If it’s important, you might momentarily halt your conversation with the first person and turn your attention to the second person. Once you are done, you will go back to the first person and recall the last topic you were discussing. The MSP430 and all microcontrollers do the same thing, although it is done programmatically and behind the scenes.

We know that the MSP430 checks for the interrupt and selects the highest priority that is not masked. But how does the MSP430 remember the “conversation”? The current state of the MSP430 is composed of several things. All of these need to be stored before we go handle an interrupt so we can return successfully:

  • The Program Counter (PC) holds the address of the next instruction to be executed
  • The Status Register (SR) holds the status of CPU results, interrupts, and operation

So, saving these two items allows us to go back because we’re saving the address of the instruction we would have executed had we not received an interrupt, as well as the status of the CPU and the system. Where do we save this? The stack on the MSP430 is where we will place the PC and SR (called pushing on the stack), both of which cause the Stack Pointer register to increment accordingly. When we are finished, we will “pop” the Status Register and the Program Counter from the stack to their respective registers and we can continue from our last location.

Once the interrupt with the highest priority is selected, the interrupt flag for single-source flags are reset. Multi-source flags must be explicitly cleared. The current Status Register (not the one pushed to the stack) is cleared. Because the SR controls the low power modes including whether the CPU is currently turned on, any low power mode is therefore terminated (but since we stored the Status register in the stack and it will be restored when we’re done, we will return to the Low Power mode when the interrupt handling is complete). For the CPU to process the ISR, the content of the interrupt vector is loaded to the Program Counter.

This last statement is important to note. The interrupt table contains addresses to the actual interrupt handlers. When the ISR is handled, it is this address that is loaded to the program counter. The address can vary from compile to compile and is a used specific code. Once this is done, the CPU begins executing the instructions that form part of the interrupt routine.

Once the interrupt is completed with the RETI (Return from Interrupt) instruction, the SR and the PC are popped from the stack and we begin executing the instruction pointed by the stack register. Before exiting from an interrupt it is possible to modify the Status Register that will be popped. This allows us, for example, to enter or exit low power modes, and even to disable the GIE using the following:

Functions to control MSP430 Low Power Modes

 
__bis_SR_register_on_exit(x) // Disable Global Interrupts by GIE = 0
__bic_SR_register_on_exit(x) // Enable Global Interrupts by GIE = 1


The functions above set and clear bits of the status register that was pushed to the stack in the stack itself and should only be used in interrupt routines. There are other non-ISR versions of the intrinsics above. These two functions require parameters for the bits to be changed. These bits are conveniently available as defines by Code Composer Studio:

MSP430 Low Power Modes Bits

 
LPM0_bits
LPM1_bits
LPM2_bits
LPM3_bits
LPM4_bits
GIE


We can call the intrinsics as follows:

 
__bic_SR_register_on_exit(LPM0_bits | GIE); // Exits LPM0 and disables GIE upon exit


Multiple Interrupt Sources

Some modules of the MSP430 provide only one interrupt source to the CPU, despite internally supporting multiple sources. An example of this are the GPIO Ports. A port supporting interrupts will have 8 interrupt sources. To avoid connecting such a large set of interrupts to the CPU, each port only provides one interrupt source and all interrupts for the port share the same Interrupt Service Routine. Once in the interrupt routine, however, we must check to see which bit actually caused the interrupt. For example, if the interrupt was caused by Port 1, we can use P1IFG and manually check for each bit.

Some MSP430 devices contain an Interrupt Vector (IV) register such as P1IV and RTCIV that make it easy to handle the interrupts. This registers contains a number representing the highest priority interrupt present on that port. Using this IV register and the intrinsic __even_in_range() we can handle interrupts as follows:

 
// Won't run on MSP430G2553
#include <msp430.h>
volatile int flag = 0;

void main()
{
    WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer
    P1SEL &= (~BIT3); // Set P1.3 SEL as GPIO
    P1DIR &= (~BIT3); // Set P1.3 SEL as Input
    P1IES |= (BIT3); // Falling Edge
    P1IFG &= (~BIT3); // Clear interrupt flag for P1.3
    P1IE |= (BIT3); // Enable interrupt for P1.3
    __enable_interrupt(); // Enable Global Interrupts
    while(1)
    {
        if(flag == 1)
        {
            // Do Something
            flag = 0;
        }
    }
}

// Port 1 interrupt service routine
#pragma vector=PORT1_VECTOR
__interrupt void Port_1(void)
{
    switch(__even_in_range(P1IV,16))
    {
    case 0: break; // No Interrupt
    case 2: break; // P1.0
    case 4: break; // P1.1
    case 6: break; // P1.2
    case 8: // P1.3
    flag = 1;
    break;
    case 10: break; // P1.4
    case 12: break; // P1.5
    case 14: break; // P1.6
    case 16: break; // P1.7
    }
}


Each time we access P2IV, we get the number of the highest priority interrupt. Using __even_in_range() isn’t just convenient. This intrinsic is information to the compiler that the number provided by P2IV is always even and goes up to 16. This enables the compiler to make smart decisions to optimize the service routine. In this case, the compiler instead of checking bit will add the value of P2IV to the program counter, to naturally jump to the handler. This results in faster processing of some of the interrupts as it requires fewer instructions.

This code will not run directly on the G2553 device because it lacks P1IV and P2IV.

Performance Implications

Whether you have realized it by now, saving the current state, handling an interrupt and then restoring the state takes time. In systems with very tight deadlines and many interrupts, doing his frequently for multiple interrupts can take a significant amount of time and interfere with other interrupts, especially given that typically interrupts are ignored during interrupt handling proper. The CPU is basically wasting time just saving its state, a costly overhead when done often.

The mantra of keeping interrupts short has been drilled into embedded engineers for as long as they have existed, yet still there are many who use ISRs as a kitchen sink and even place loops inside interrupts. Never do this. A fast interrupt is the best kind. Using a flag set at an interrupt and processing it in the main loop is almost always the best choice.

MSP430 Low Power Modes

The MSP430 is designed from the ground up for low power. This includes both design and process implementation. Despite this, the bulk of power savings is realized by placing the MSP430 in various power saving modes. We first have to understand what consumes the most current in the MSP430. This breaks down as follows:

  • MSP430 CPU draws the most, proportional to the frequency at which it is running.
  • Clocks and Oscillators, especially high speed clocks.
  • Modules and Peripherals

Saving power comes down to shutting off as many modules, peripherals, and clocks as we can, for as long as possible without violating the application time requirements. It is also important to reduce the speed of the CPU as it can significantly affect power consumption. The trade-off is that processing will take longer. MSP430 peripherals are also designed for low power. For example, the ADC will shut off automatically after the conversions are finished. The issue isn’t always what to turn off but when, and when to wake them up. Interrupts play a central role because they enable the MSP430 to go to a deep sleep and wake up if events have occurred, either external as detected by GPIOs or internally generated by the peripherals. To enable low power the MSP430 supports several modes, each shutting off the MSP430 more and more:

  • Active Mode (AM) – Not a low power mode but rather the mode in which everything is turned on, except perhaps for some peripherals
  • LPM0 – CPU and MCLK are shutoff. SMCLK and ACLK remain active.
  • LPM1 – CPU and MCLK are off, as in LPM1, but DCO and DC generator are disabled if the DCO is not used for SMCLK. ACLK is active.
  • LPM2 – CPU, MCLK, SMCLK and DCO are disabled, while DC generator is still enabled. ACLK is active.
  • LPM3 – CPU, MCLK, SMCLK, DCO and DC generator are disabled. ACLK is active.
  • LPM4 – CPU and all clocks disabled

These LPM modes refer to the individual bits in the Status Register. By setting and clearing the bits in the SR, one can turn off CPU and clocks resulting in certain Low Power Modes. Let’s look at the power consumption profile of the various LPMs:

Graph showing MSP430 power consumption in various power modes

At 1MHz, we go from 300uA down to less than 1uA by switching to LPM3. This is what makes the MSP430 such a good microcontroller for low power applications. It can survive for years on batteries. But, to take advantage of this, we have to build an application that takes advantage of these low power modes and turns on only what’s necessary when it’s necessary.

In general, using LPM modes comes down to the following:

  • If your application can wait until a GPIO causes an interrupt and you need no clocks active, use LPM4
  • If you need ACLK at 32kHz with a peripheral such as a timer, use LPM3
  • Use LPM0 as much as possible – basically top the CPU from processing if not needed
  • Avoid polling. Use interrupts for all low level drivers for UART, SPI and I2C as well as other modules as much as possible. Build your application to operate asynchronously.
  • Avoid using the CPU to do anything that can be done with the peripherals, including timing and other operations.

Aside from the LPM modes above, some MSP430 devices include LPM3.5 and LPM4.5 which disable the Power Management Module. The MSP430G2553 does not include a PMM and therefore does not support these modes. When LPMx.5 mode is entered, all RAM and register contents are lost, although I/O states are locked and will not change.

Low power modes allow us to conserve energy, but we do pay a penalty for using them. That penalty is the time it takes to go from a low power mode to Active Mode. The deeper we go into low power modes, the longer it takes to go to Active Mode. The primary reason is that oscillators take time to come to a stable state. In some applications, the delay in getting to active mode to handle an interrupt is too long and precludes the programmer from making the MSP430 go to sleep. Slower clocks such as 32kHz oscillator take much longer to stabilize (10ms to 100ms or more in some cases), while the fast clocks and crystal may take only a few microseconds. For this reason, it is preferable to wake up with a very fast clock, which is the default behavior.