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.