Interrupts

AVR derivates acknowledges an interrupt request by executing a hardware generated CALL to the appropriate servicing routine ISRs.
ISRs are organized in IVT. ISR is defined as a standard function but with the iv directive afterwards which connects the function with specific interrupt vector.

For example, 0x0008 is IVT address of Timer/Counter 2 Overflow interrupt source of the ATMEGA16, and the symbolic name for this interrupt vector in mikroC PRO for AVR is IVT_ADDR_TIMER2_OVF.
Symbolic interrupt name list can be accessed by using Code Assistant, i.e. by typing the IVT and pressing Ctrl+Space in the Code Editor.

For more information on interrupts and IVT refer to the specific data sheet.

Function Calls from Interrupt

Calling functions from within the interrupt routine is allowed. The compiler takes care about the registers being used, both in "interrupt" and in "main" thread, and performs "smart" context-switching between them two, saving only the registers that have been used in both threads. It is not recommended to use function call from interrupt. In case of doing that take care of stack depth.

Use the #pragma disablecontextsaving to instruct the compiler not to automatically perform context-switching. This means that no regiser will be saved/restored by the compiler on entrance/exit from interrupt service routine. This enables the user to manually write code for saving registers upon entrance and to restore them before exit from interrupt.

Interrupt Handling

For the sake of interrupt handling convenience, new keyword, iv, is introduced. It is used to declare Interrupt Vector Table (IVT) address for a defined interrupt routine :

void Timer1_Interrupt() iv IVT_ADDR_TIMER1_OVF {  // Dummy Timer1 interrupt routine
   asm nop;
}

Now it is possible to explicitly declare interrupt routine address :

void Timer1_Interrupt() org 0x600 iv IVT_ADDR_TIMER1_OVF {  // Interrupt routine will be placed on the address 0x600
   asm nop;
}  

For the sake of backward compatibility, user may write also :

void Timer1_Interrupt() org IVT_ADDR_TIMER1_OVF {
   asm nop;
}

which is equivalent to :

void Timer1_Interrupt() iv IVT_ADDR_TIMER1_OVF {
   asm nop;
}

Is is recommended that interrupts are handled in this way for the sake of better readability of the user projects.

Interrupt Example

Here is a simple example of handling the interrupts from Timer1 (if no other interrupts are allowed) :

const char _THRESHOLD = 10;
char counter;

void Timer1Overflow_ISR() org IVT_ADDR_TIMER1_OVF {

  if (counter >= _THRESHOLD) {
    PORTB = ~PORTB;             // toggle PORTB
    counter = 0;                // reset counter
  }
  else
    counter++;                  // increment counter

}

void main() {

  DDRB   = 0xFF;                // set PORTB as output
  PORTB  = 0;                   // clear PORTB

  SREG_I_bit = 1;               // Interrupt enable
  TOIE1_bit  = 1;               // Timer1 overflow interrupt enable
  TCCR1B = 2;                   // Start timer with 8 prescaler

  while (1)                     // Endless loop, port is changed inside Interrupt Service Routine (ISR)
    ;
}

Most of the MCUs can access interrupt service routines directly, but some can not reach interrupt service routines if they are allocated on addresses greater than 2K from the IVT. In this case, compiler automatically creates Goto table, in order to jump to such interrupt service routines.

These principles can be explained on the picture below :

Accessing interrupt service routines

Direct accessing interrupt service routine and accessing interrupt service routine via Goto table.

Copyright (c) 2002-2012 mikroElektronika. All rights reserved.
What do you think about this topic ? Send us feedback!
Want more examples and libraries? 
Find them on LibStock - A place for the code