Friday, November 23, 2012

Interrupts and timers

Interrupts stops executing main program and jumps to interrupt service routine. ISR is just function which address is stored in interrupt vector ( it is memory located in  flash at address 0xfffe ).
This is how is done in msp430gcc:
void Port1Int( void ) __attribute__( ( interrupt( PORT1_VECTOR ) ) );
void Port1Int( void )
{
   //our isr
}
We can use any name function, the thing that determines which interrupt will be served in this routine is vector name "PORT1_VECTOR". In this example we have PORT1 interrupt which fires when logic level is changed on any pin on port1 in the processor, we can decide which pin and what transition is working.

On lauchpad we have one press button that we can use for this task, it's P1.3.
P1DIR &= ~BIT3;  // make this pin input
P1REN |= BIT3;   // turn on internal pull resistor
P1OUT |= BIT3;   // make pulling up to VCC
P1IES |= BIT3;   // high to low transition fires interrupt
P1IFG &= ~BIT3;  // clear interrupt

We have to use internal pull resistor because in newer launchpads there is no resistor for push button. There is a place for one but they removed it probably to lower power consumption.
Let's use timer A0 for some led blinking, here is our setup:
TA0CTL =  TASSEL_2 | ID_2 | MC_1;
TA0CCR0 = 250000;
TA0CCTL0 = CCIE;
TASSEL_2 - set clock source for timer to SMCLK which is ~1MHz ID_3 - divide our source clock by /4 MC_1 - counting until we reach value in TACCR0 CCIE - enable compare interrupt
We want to have times around 1s so we divide clock to 1MHz/4 = 250 KHz, so our timer will count 250 000 times per second, it's still pretty fast. So, next we use compare register which will compare our timer value to it and when it's equal it's fire interrupt (CCIE). We compare to 25 000 value so our timer count to this value every 1/10 of second, so we have interrupt 10 times per second.
Let's use all this to make program which starts blinking after pressing push button.
#include 
#include 

uint16_t counter = 0;
uint8_t second = 0;

int main()
{
    WDTCTL = WDTPW + WDTHOLD;   
    
    BCSCTL1 = CALBC1_1MHZ;
    DCOCTL = CALDCO_1MHZ;
    
    TA0CCR0 = 25000;
    TA0CCTL0 = CCIE ;
    
    P1DIR &= ~BIT3;
    P1REN |= BIT3;
    P1OUT |= BIT3;
    P1IES |= BIT3;  
    P1IFG &= ~BIT3; 
    P1IE |= BIT3;   
 
    P1DIR |= BIT0 | BIT6;
    P1OUT &= ~( BIT0 | BIT6 );

    __bis_SR_register( GIE );
 
    while(1) {
 }

    return 0;
}
void Port1Int( void ) __attribute__( ( interrupt( PORT1_VECTOR ) ) );
void Port1Int( void )
{
 switch(P1IFG&BIT3) {
  case BIT3:
   P1IFG &= ~BIT3;
   TA0CTL = TASSEL_2 | ID_2 | MC_1; //run timer
   break;
 } 
}

void TimerA0Int( void ) __attribute__( ( interrupt( TIMER0_A0_VECTOR ) ) );
void TimerA0Int( void ) 
{
 P1OUT ^= BIT6;
 if ( counter == 5 ) {
  counter = 0;
  second++;
  P1OUT ^= BIT0;
  if ( second > 7 ) {
   TA0CTL = 0; //stop timer
   second = 0;
  }
 } 
 counter++;
}

11,12 - setting calibrated values to have more accurate 1MHz clock
17 - 22 - setting BIT3 PORT1 interrupt
24,25 - setting pins to drive led
26 - this is msp430gcc macro which handle Special Register, here we enable General interrupt enable. When set, enable maskable interrupts ( all except nmi interrupt )
28 - just do nothing and wait for interrupts.
36 - we use switch so we can handle more pins in future
38 - this is important we must clear this bit by software, so we won't renter interrupt again. Normally when there is one interrupt source it's cleared automatically but here we want to check which pin invoke interrupt.
39 - We start our timer so leds will start blinking
44 - It is isr for our timer compare interrupt. One led blinks fast another slower, we blink 7 second and then stopping timer.

Uff, I never thought writing tech blog is so time-consuming and hard. I think my explanation of interrupts and timers is very superficial so don't hesitate and ask question so i can explain more.

2 comments:

  1. Hi, thanks for new post.

    I have 2 questions. First coming with simple example:
    ===
    #define LED_0 BIT0
    #define LED_1 BIT6
    #define LED_OUT P1OUT
    #define LED_DIR P1DIR
    unsigned int timerCount = 0;
    int main(void)
    {
    WDTCTL = WDTPW + WDTHOLD; // Stop watchdog timer
    LED_DIR |= (LED_0 + LED_1); // Set P1.0 and P1.6 to output direction
    LED_OUT &= ~(LED_0 ); //+ LED_1); // Set the LEDs off
    LED_OUT |= LED_1;

    CCTL0 = CCIE;
    TACTL = TASSEL_2 + MC_2; // Set the timer A to SMCLCK, Continuous
    // Clear the timer and enable timer interrupt

    __enable_interrupt();

    __bis_SR_register(LPM0_bits + GIE); // LPM0 with interrupts enabled

    return 0;
    }
    void timerA0( void ) __attribute__( ( interrupt( TIMER0_A0_VECTOR ) ) );
    void timerA0( void )
    {
    P1OUT ^= (LED_0 + LED_1);
    }
    ===
    Question - here I do not have endless loop at the end of main() and it does work. In your example removing endless lop at the end causes program not to work. Why ?

    2. question:
    I tried to find documentation for tool-chain and C library. What I found is: http://mspgcc.sourceforge.net/manual/ but writing interrupt handler as described there causes compilation errors and is obviously different that in example above. Where can I find documentation for tool-chain that you are using ?

    BTW thanks for this blog, I got interested in launchpad thanks to you! :)

    ReplyDelete
    Replies
    1. ad 1. You put you mcu in low power mode and never wake up, so it doing only what is in interrupt. If you want to wake up mcu you have to use __bic_SR_register( LPMO_bits ) in your interrupt ( bic means bit is cleared ) so after exiting form isr mcu is awaken.

      ad 2. Look at the date of this document is very old and I'm afraid that there is no actual one. I find solutions in header files and in mspgcc mailing list archive and of course google:D

      Delete