MSP430 Launchpad Tutorial - Part 2 - Interrupts and timers

Enrico GaranteJune 17, 201335 comments

What is an "interrupt"? It is a signal that informs our MCU that a certain event has happened, causing the interruption of the normal flow of the main program and the execution of an "interrupt routine", that handles the event and takes a specified action.

Quick Links

Interrupts are essential to avoid wasting the processor's valuable time in polling loops, waiting for external events (in fact they are used in Real-Time Operating Systems, RTOS).

This article is available in PDF format for easy printing

In the MSP430 architecture, there are several types of interrupts: timer interrupts, port interrupts, ADC interrupts and so on. Each one of them needs to be enabled and configured to work, and there is a separate "service routine" for every interrupt.

In this tutorial we'll see how to use timer and port interrupts to flash some leds, we'll keep the ADC interrupt for the next tutorial. So, let's write some code!

#include "msp430g2231.h" 
void main(void)
{
  WDTCTL = WDTPW + WDTHOLD;                 // Stop WDT  

You should recognize those lines,we used them in the last tutorial to add the definition file for our MCU, declare the main function and stop the watchdog timer.

  CCTL0 = CCIE;                             // CCR0 interrupt enabled
  TACTL = TASSEL_2 + MC_1 + ID_3;           // SMCLK/8, upmode  
  CCR0 =  10000;                           // 12.5 Hz   

Here's some interesting stuff. These lines configure the timer interrupt. We first enable it by setting the CCIE bit in the CCTL0 register.

Then we set the clock for the timer module in the TimerA control register.

If you have a look at the msp430g2231.h file, you can see that:

  • TASSEL_2 selects the SMCLK (supplied by an internal DCO which runs at about 1 MHz);
  • MC_1 selects the "UP mode", the timer counts up to the number stored in the CCR0 register;
  • ID_3 selects an internal 8x divider for the supplied clock (in our case we have SMCLK/8).

Finally, we set the CCR0 register. We configured the TimerA module to count up to the number stored in this register before overflowing and triggering the interrupt.

By setting it at 10000, we get an overflow-frequency of 12,5 Hz. In fact we have (SMCLK/8)/10000 = 12,5 .

You may obtain several frequencies by changing this number (remember that the MSP430 has a 16-bit timer, so the value stored in the CCR0 register must not be higher than 65535), changing the dividers or adding an if-else block with a counter in the interrupt routine. Let's go ahead.

  
  P1OUT &= 0x00;               // Shut down everything
  P1DIR &= 0x00;               
  P1DIR |= BIT0 + BIT6;       // P1.0 and P1.6 pins output the rest are input 
  P1REN |= BIT3;                 // Enable internal pull-up/down resistors
  P1OUT |= BIT3;                 //Select pull-up mode for P1.3

These lines should be familiar too, but there are some additions: firstly, we clear the PORT1 output and direction registers. Then we set the P1.0 and P1.6 pins as outputs and the rest as inputs. The last two lines enable the pull-up resistor on the switch (BIT3) so that the normal state (button not pressed) will be "1".

  P1IE |= BIT3;                    // P1.3 interrupt enabled
  P1IES |= BIT3;                  // P1.3 Hi/lo edge
  P1IFG &= ~BIT3;               // P1.3 IFG cleared

With these lines of code, we first tell the MCU to listen to the P1.3 pin for logic-state changes (effectively enabling the interrupt on that particular pin).

Then we select the edge when the interrupt is raised (from High to Low or Low to High); remember that the button on the LaunchPad connects the input pin to GND when pushed and to VCC when not. For this reason we seletct Hi/Lo edge.

Finally we clear the interrupt flag for that pin. The interrput flag register P1IFG reports when an interrupt is raised, and it should be cleared at the end of the interrupt service routine.

  _BIS_SR(CPUOFF + GIE);        // Enter LPM0 w/ interrupt   
  while(1)                      //Loop forever, we do  everything with interrupts!
  {}
}

With this line, as you can remember, we shut down the CPU to spare some power while keeping the interrupts enabled. Then we enter a loop to be sure the MCU does nothing else, as we do our job with interrupts.

// Timer A0 interrupt service routine
#pragma vector=TIMERA0_VECTOR
__interrupt void Timer_A (void)
{
  P1OUT ^= BIT0;                            // Toggle P1.0
}

This is the TimerA interrupt service routine. Every time the TimerA overflows, the code inserted in this routine (note the special declaration) is executed. As you can see we only toggle the P1.0 pin (red led on LaunchPad), then we return to normal execution.

// Port 1 interrupt service routine
#pragma vector=PORT1_VECTOR
__interrupt void Port_1(void)
{    
   P1OUT ^= BIT6;                        // Toggle P1.6   
   P1IFG &=~BIT3;                        // P1.3 IFG cleared    
} 

This is the Port1 interrupt service routine. Every time the we push the P1.3 button, the code inserted in this routine (note the special declaration) is executed. We toggle the P1.6 pin (greenled on LaunchPad), clear the P1.3 interrupt flag (very important) and then we return to normal execution.

Compile and program the LaunchPad, you should see the red led blink, and the green led toggle when you press the P1.3 button.

Here's the full code, enjoy!

#include "msp430g2231.h"  
  
void main(void)
{
  WDTCTL = WDTPW + WDTHOLD;     // Stop WDT  
  CCTL0 = CCIE;                             // CCR0 interrupt enabled
  TACTL = TASSEL_2 + MC_1 + ID_3;           // SMCLK/8, upmode
  CCR0 =  10000;                     // 12.5 Hz   
  P1OUT &= 0x00;               // Shut down everything
  P1DIR &= 0x00;               
  P1DIR |= BIT0 + BIT6;            // P1.0 and P1.6 pins output the rest are input 
  P1REN |= BIT3;                   // Enable internal pull-up/down resistors
  P1OUT |= BIT3;                   //Select pull-up mode for P1.3
  P1IE |= BIT3;                       // P1.3 interrupt enabled
  P1IES |= BIT3;                     // P1.3 Hi/lo edge
  P1IFG &= ~BIT3;                  // P1.3 IFG cleared
  _BIS_SR(CPUOFF + GIE);          // Enter LPM0 w/ interrupt 
  while(1)                          //Loop forever, we work with interrupts!
  {}
} 
  
// Timer A0 interrupt service routine 
#pragma vector=TIMERA0_VECTOR 
__interrupt void Timer_A (void) 
{   
   P1OUT ^= BIT0;                          // Toggle P1.0 
} 
// Port 1 interrupt service routine
#pragma vector=PORT1_VECTOR
__interrupt void Port_1(void)
{    
   P1OUT ^= BIT6;                      // Toggle P1.6
   P1IFG &= ~BIT3;                     // P1.3 IFG cleared 
}

Previous post by Enrico Garante:
   MSP430 Launchpad Tutorial - Part 1 - Basics
Next post by Enrico Garante:
   MSP430 LaunchPad Tutorial - Part 3 - ADC


Comments:

[ - ]
Comment by Naresh14May 11, 2015
Hi,

Nice piece of tutorial. May I know where you got the material from? I mean, the instruction set. Thanks!
[ - ]
Comment by emihackr97May 23, 2014
Hi, I am using your code with the Energia IDE, and it compiles and uploads perfectly, but only the pushbutton interrupt is working, not the timer interrupt, what could be wrong? BTW, I am using an MSP430G2553.

Thanks a lot!
[ - ]
Comment by Julio SouzaJuly 16, 2014
Just look at the previous commentaries
[ - ]
Comment by chintan_gaurav8August 31, 2016
Have you got the solution. When I run the same program, TAR register is not increment, so never reach to desire value and execution remains on while(1) loop. Is there clock issue or some other issue?
[ - ]
Comment by Tomi89July 23, 2015
Hi!
I translated the example to MSP-EXP430F5529LP I hope it helped to some of you guys!

#include "msp430F5529.h" //Contains definitions for registers and built-in functions
#include

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

TA0CCTL0 = CCIE; // CCR0 interrupt enabled
TA0CTL = TASSEL_2 | MC_1 | ID_3; // SMCLK/8, upmode
TA0CCR0 = 10000; // I have to calculate it, but I was lazy Hz

P1OUT &= 0x00; // Shut down pins on P1
P1DIR &= 0x00; // Set P1 pins as output
P1DIR |= BIT0; // P1.0 pin set as output the rest are input

P4OUT &= 0x00; // Shut down pins on P4
P4DIR &= 0x00; // Set P4 pins as output
P4DIR |= BIT7; // P4.7 pin set as output the rest are input

P2REN |= BIT1; // Enable internal pull-up/down resistors for P2
P2OUT |= BIT1; //Select pull-up mode for P2.1

P2IE |= BIT1; // P2.1 interrupt enabled
P2IES |= BIT1; // P2.1 Hi/lo edge
P2IFG &= ~BIT1; // P2.1 IFG cleared

_BIS_SR(CPUOFF + GIE); // Enter LPM0 w/ interrupt
while(1) //Loop forever, we work with interrupts!
{}
}

// Timer A0 interrupt service routine
#pragma vector=TIMER0_A0_VECTOR
__interrupt void Timer_A0 (void)
{
P1OUT ^= BIT0; // Toggle P1.0

}

// Port 2 interrupt service routine
#pragma vector=PORT2_VECTOR
__interrupt void Port_2(void)
{
static uint8_t debounce = 0;

while(debounce <= 100)
{
if (~P2IN & BIT1) debounce++;
else debounce = 0;
}
P4OUT ^= BIT7; // Toggle P4.7
debounce = 0;

P2IFG &= ~BIT1; // P2.1 IFG cleared
}
[ - ]
Comment by Dennis the peasantDecember 30, 2015
Basically the same concept but for Switch "S2"

#include

/*
* main.c
*/
int main(void) {
WDTCTL = WDTPW | WDTHOLD; // Stop watchdog timer

TA0CCTL0 = CCIE; /* TA0CCTL0 Register: If you plan to use a Timer, the first register that you need to set is CCTL0. It is a 16 bit register and settings of this register effects how we use the register. For our purpose we just tell it to enable the interrupt using */
TA0CTL = TASSEL_2 + MC_1 + ID_3; /* TASSEL_2: use SMCLK MC_1: upcount ID_3: divide by 8*/
TA0CCR0 = 10000; // count up to 10000

P1OUT = 0;
P1DIR= BIT0; //P0 as output
P1REN |= BIT1; // enable pull up/down for P1
P1OUT |= BIT1; // pull up res for P1

P4OUT = 0;
P4DIR = 0;
P4DIR= BIT7;

P1IE |= BIT1; //P1.0 interrupt enable
P1IES |= BIT1; // High/low edge
P1IFG &= ~BIT1; // clear flag

_BIS_SR(CPUOFF + GIE); // sleep untill interupt

while(1) {}

return 0;
}

// Timer A interrupt
#pragma vector=TIMER0_A0_VECTOR
__interrupt void TIMER0_A0_ISR(void)
{
P4OUT ^= BIT7;
}

#pragma vector=PORT1_VECTOR
__interrupt void Port_1(void)
{
P1OUT ^= BIT0;
P1IFG &= ~BIT1;
}
[ - ]
Comment by HK65July 22, 2013
can you tell me why you have P1OUT |= BIT3 in your initialization code? I don't understand why it's there, but the code doesn't work without it.
[ - ]
Comment by egaranteAugust 1, 2013
As stated in the "MSP430x2xx Family User's Guide":

"Each bit in each PxREN register enables or disables the pullup/pulldown resistor of the corresponding I/O pin.
The corresponding bit in the PxOUT register selects if the pin is pulled up (1) or pulled down (0)."


I made that clearer with a comment in the code, thanks for reporting it!
[ - ]
Comment by DJCAugust 14, 2013
Can you declare more than one interrupt pin on each port and have separate code run for each interrupt signal?
[ - ]
Comment by egaranteAugust 16, 2013
Of course!
You just have to enable the pin interrupt with the P1IE and P1IES registers. Then in the PORT1 interrupt routine you can execute different code for each pin with an if statement on the P1IFG register, that will let you know which pin has been pressed.
[ - ]
Comment by vishwavardhanSeptember 8, 2013
Thanks for the article it is very helpful
[ - ]
Comment by PrasadgannaSeptember 17, 2013
Hi Guys,

I am working on MPS430g2553 controller,I want set time from a keypad where it has 5 keys,What is flow for it?

[ - ]
Comment by Carlos80October 1, 2013
great again, but doesnt work with 2553, i cant fix it !
[ - ]
Comment by jipiOctober 16, 2013
For the MSP430G2553, you must use
#pragma vector=TIMER0_A0_VECTOR
instead of
#pragma vector=TIMERA0_VECTOR
for the timer interrupt service routine and it should work. I tested it 10 minutes ago!
[ - ]
Comment by sanjay kaglaNovember 19, 2013
please tell me about phasor calculation using msp430
[ - ]
Comment by NIKHIL_EMBEDDEDFebruary 28, 2014
sir i want to interface a key with P1.0. and as a output i want to increment % duty cycle of PWM wave from 1% to 99%in step of 1% with 1 second time interval.
[ - ]
Comment by Mohamed92May 25, 2014
why didn't you take into consideration the 20ms for debounce of the button?
[ - ]
Comment by H2SO4August 30, 2014
Great guide, explained perfectly! Thanks a lot.
[ - ]
Comment by aravindhNovember 7, 2014
Hi,
Thanks for your tutorial.. ur way of explanation on the code is really nice and easy to breakdown the code into simpler way to understand, thanks a lot.

i changed the line " P1IES |= BIT3; " into " P1IES &= ~BIT3; " ie,. from rising edge to falling edge.
it worked as expected,..

at the same time i tried this too
I changed the line " P1OUT |= BIT3; ", into "P1OUT &= ~BIT3; ".
if im correct which is clearing the bit ie,. setting the bit as pull down resistor mode.
there is no change in the output.!
It should not work, right ,..???!
[ - ]
Comment by Akash1April 2, 2015
Initially,P1.3 is pulled up...that is,it is high.
Now,when the button is pressed,it becomes low,in polling,it is checked with( ~BIT2 & P1IN). How is this done using interrupts coding?
[ - ]
Comment by SL12April 3, 2015
when u define this (process of pressing the button) as an interrupt , then while being in waiting program also keeps looking for interrupts. Now when you press the button an interrupt flag gets set , and a subroutine is gets called......
[ - ]
Comment by SL12April 3, 2015
Hi,
I am using this port and timer interrupt program on g2553.
It is showing error as : "Port" is not recognized as an internal or external command.
I have also changed interrupt names in accordance with g2553.h , but still the same error......
For reference.....here is the code :::

#include "msp430g2553.h"

void main(void)
{
WDTCTL = WDTPW + WDTHOLD; // Stop WDT
CCTL0 = CCIE; // CCR0 interrupt enabled
TACTL = TASSEL_2 + MC_1 + ID_3; // SMCLK/8, upmode
CCR0 = 10000; // 12.5 Hz
P1OUT &= 0x00; // Shut down everything
P1DIR &= 0x00;
P1DIR |= BIT0 + BIT6; // P1.0 and P1.6 pins output the rest are input
P1REN |= BIT3; // Enable internal pull-up/down resistors
P1OUT |= BIT3; //Select pull-up mode for P1.3
P1IE |= BIT3; // P1.3 interrupt enabled
P1IES |= BIT3; // P1.3 Hi/lo edge
P1IFG &= ~BIT3; // P1.3 IFG cleared
_BIS_SR(CPUOFF + GIE); // Enter LPM0 w/ interrupt
while(1) //Loop forever, we work with interrupts!
{}
}

// Timer A0 interrupt service routine
#pragma vector=TIMER0_A0_VECTOR
__interrupt void Timer_A (void)
{
P1OUT ^= BIT0; // Toggle P1.0
}
// Port 1 interrupt service routine
#pragma vector=PORT1_VECTOR
__interrupt void PORT1_ISR(void)
{
P1OUT ^= BIT6; // Toggle P1.6
P1IFG &= ~BIT3; // P1.3 IFG cleared
}
[ - ]
Comment by greenthumb79April 12, 2015
Perhaps the issue is the ISR function name - You use void PORT1_ISR(void), but more commonly used is void Port_1(void).
[ - ]
Comment by goldspiceJune 17, 2015
For the G2553, u must use:
TA0CTL=TASSEL_2+MC_1+ID_3;
TA0CCR0=10000;
TA0CCTL0=CCIE
#pragma vector= TIMER0_A0_VECTOR
[ - ]
Comment by Vic86June 29, 2015
Hi I´m doing a interruption with timer but i need to change SMCLK with a frequency at 8MHz can you help me?
[ - ]
Comment by Tomi89July 23, 2015
the second # include is stdint.h between <>
I donnow why it doesen't show.
[ - ]
Comment by Yara22November 1, 2015
Your tutorials are amazing! Thanks a lot.
[ - ]
Comment by Jed64February 24, 2016
Thanks for your sharing!
I have a problem about clearing interrupt flag.
Why we don't need to clear interrupt flag in Timer's interrupt service routine, but need to clear interrupt flag in Port1 interrupt service routine?
[ - ]
Comment by tonyp12February 25, 2016
MSP families that have IV on port IRQs, you don't manually have to clear the flag as just reading the registers clears the flag that causes it and only one flag will ever be set at one time so switch/case can be used in it as if two IRQ happened they will be in queue.
430x2xxx don't have IV and if many IFG flags can be set at one time and if all flags was cleared by just reading it you could not have multiple if bittest of the IFG registers, so you have clear that bit manually.
[ - ]
Comment by nikemensMarch 16, 2016


i am trying get an external interrupt and blink an LED. but i get error like 'pragma vector= accepts numeric arguments or "unused_interrupts" but not PORT_VECTOR'

int main(){
// Stop Watch Dog Timer
WDTCTL = WDTPW + WDTHOLD;
P1DIR &= ~BIT4; //configure as an input
P1OUT |= BIT4;
P3DIR |= BIT4;
P3OUT |= BIT4;
P1IES = 0x01;
P1IE = 0x01; //1b = Corresponding port interrupt enabled
P1IFG = 0x01; //PxIFG flag is set with a high-to-low transition
P1REN = 0x01; //Pullup or pulldown enabled
}

#pragma vector = PORT_VECTOR
__interrupt void Port_1 (void){

if( (P1IN & BIT4)==0 ){
// read input as GND
P3OUT |= BIT4;
}
else if( (P1IN & BIT4)==1 ){
P3OUT &= ~BIT4;

}

}
[ - ]
Comment by dipankarpandaJune 20, 2016
Hi, I am using MSP430F1491. While compiling, It gives an error on enable resistor pull up/down line ( P1REN |= BIT3; ). Shows that it is invalid. But while compiling with MSP430F2419, it is compiling perfectly. Whats the reason behind that?
[ - ]
Comment by chintan_gaurav8August 31, 2016
When I run the same program, TAR register is not increment, so never reach to desire value and execution remains on while(1) loop. Is there clock issue or some other issue?
[ - ]
Comment by chintan_gaurav8August 31, 2016
When I run the same program, TAR register is not increment, so never reach to desire value and execution remains on while(1) loop. Is there clock issue or some other issue?
[ - ]
Comment by dipankarpanda50September 13, 2016
Hi,
The above example works pretty well. Now, if I want to blink 2 LED's at different frequency using timer_A what I have to do?
I have taken TACCRO = 10000 and TACCR1 = 60000
Accordingly enabling interrupt by TACCTLO = CCIE and TACCTL1 = CCIE

Also made two ISR:
#pragma vector=TIMERA0_VECTOR
__interrupt void ISR1 (void)
{ }
and #pragma vector=TIMERA1_VECTOR
__interrupt void ISR2 (void)
{ }

but it not working properly. One LED blinks perfectly but anotherone not blinking. Can anybody please suggest. Thanks in advance.
[ - ]
Comment by Ordorica_AbrahamSeptember 27, 2016
I have a question about the end of ISR in the first tutorial, blink a LED we had:
#pragma vector=PORT1_VECTOR
__interrupt void Port_1(void)
{
P1OUT ^= BIT6; // Toggle P1.6
P1IFG &= ~BIT3; // P1.3 IFG cleared
}
If we pressed the button 1.3 we went to the ISR because P1IE and P1IES was configured for that, in te routine P1OUT XOR BIT6 and then P1IFG and then the program back to the "main ()"? I mean, the ISR ends by itself and back automatically? If that's rigth I imagine if we press the button again, the program goes again to ISR. I mean.

But whit TimerA, who tells to MCU "go to ISR_TimerA Routine?_TimerA begin to count and at the end of the count, the program goes to ISR_TimerA? and if that's right, what line begin the count of timerA. And how we coud stop the ISR_TimerA for example... after 5 times the same interruption.

I spect my questions don´t be so ridiculous.

To post reply to a comment, click on the 'reply' button attached to each comment. To post a new comment (not a reply to a comment) check out the 'Write a Comment' tab at the top of the comments.

Registering will allow you to participate to the forums on ALL the related sites and give you access to all pdf downloads.

Sign up
or Sign in