2.10. Interrupts

You can find the complete description of interrupts in the datasheet, in Sections “Interrupts”, “External Interrupts” and in the Subsection “Reset and Interrupt Handling” from the Section AVR CPU Core.

In computing, an interrupt is an asynchronous signal indicating the need for attention or a synchronous event in software indicating the need for a change in execution. A hardware interrupt causes the processor to save its state of execution and begin execution of an interrupt handler. Software interrupts are usually implemented as instructions in the instruction set, which cause a context switch to an interrupt handler similar to a hardware interrupt[2].

This method allows us to avoid polling for data. Instead of periodically asking each device present in the system if they have data ready to be processed it is more efficient to just run our main program normally and when data arrives the devices will send a notification to the processor so it may execute the handling routine.

In order to associate an interrupt with a certain routine, the microcontroller uses an interrupt vector table, illustrated in Figure 1. Thus, every interrupt type has associated an address where its handling routine is stored. When an interrupt occurs, the program has to halt its execution and jump to this address. The addresses are fixed and the interrupts associated with them are ordered by their priority (a lower address means that the priority is higher).

Figure 1. Interrupt vectors in ATmega328 and ATmega328P.

There are basically two types of interrupts. The first type is triggered by an event that sets the Interrupt Flag. If an interrupt condition occurs while the corresponding interrupt enable bit is cleared, the Interrupt Flag will be set and remembered until the interrupt is enabled, or the flag is cleared by software. Similarly, if one or more interrupt conditions occur while the Global Interrupt Enable bit is cleared, the corresponding Interrupt Flag(s) will be set and remembered until the Global interrupt Enable bit is set, and will then be executed by order of priority[1].

The second type of interrupts will trigger as long as the interrupt condition is present. These interrupts do not necessarily have Interrupt Flags. If the interrupt condition disappears before the interrupt is enabled, the interrupt will not be triggered.

External interrupts

The External Interrupts are triggered by the INT0 and INT1 pins or any of the PCINT23…0 pins. Observe that, if enabled, the interrupts will trigger even if the INT0 and INT1 or PCINT23…0 pins are configured as outputs. This feature provides a way of generating a software interrupt. The pin change interrupt PCI2 will trigger if any enabled PCINT[23:16] pin toggles. The pin change interrupt PCI1 will trigger if any enabled PCINT[14:8] pin toggles. The pin change interrupt PCI0 will trigger if any enabled PCINT[7:0] pin toggles. The PCMSK2, PCMSK1 and PCMSK0 Registers control which pins contribute to the pin change interrupts[1].

How do interrupts work?

When an interrupt occurs, the Global Interrupt Enable I-bit is cleared and all interrupts are disabled. This means that during the execution of the interrupt routine no other interrupts will be treated. The bit will be automatically set again when your routine ends. If you want to allow interrupts to be treated during other interrupts you can manually set the bit at the beginning of your interrupt routine, but this is not a recommended behaviour.

Figure 2. Interrupt flow.

In Figure 2 you can see the basic flow of actions. The external interrupt INT0 is received when the microcontroller is executing the

ldi R16,0xFF

instruction. The current executing context is saved on the stack; the interrupt will run in a different context and the program context will be reinstated after executing the interrupt. Before executing the next instruction, the program jumps from the current address ($123) to address $002. Here, the program finds another jump instruction, this time to address $2FF, which represents the address of your interrupt routine. It executes the instructions it finds here until it reaches the RETI instruction. This last instruction tells the program to reinstate the former context and continue executing from where it left off, at instruction

cpi R16, 22

Every interrupt routine has a RETI instruction, you do not have to manually add it.

Interrupt handling

The Global Interrupt Enable bit must be set for the interrupts to be enabled. The individual interrupt enable control is then performed in separate control registers. If the Global Interrupt Enable Register is cleared, none of the interrupts are enabled independent of the individual interrupt enable settings. The I-bit is cleared by hardware after an interrupt has occurred, and is set by the RETI instruction to enable subsequent interrupts. The I-bit can also be set and cleared by the application with the sei() and cli() instructions[1].

The interrupt routine is defined in the ISR macro (Interrupt Service Routine), as seen in the code snippet below. Notice that the header used for interrupt calls is “avr/interrupt.h”

#include <avr/interrupt.h>
 
ISR(INT0_vect)
{
    ...
}

Notice that the ISR macro takes as argument the type of the interrupt, in this case the external interrupt INT0. This argument is formed from the identifier found in the “Source” column in the table of Figure 1 in which all blank spaces are replaced with an underscore, followed by “_vect”. Example:

  • For vector number 10, the identifier is “TIMER2 OVF” so if we want to write an interrupt routine for it we will use “TIMER2_OVF_vect”.

Pseudocode

  1. Include the interrupts header:
    #include <avr/interrupt.h>
  2. For every interrupt that you have enabled in your program you need to form the interrupt vector identifier, as presented in the Interrupt handling subsection.
  3. Write an ISR routine for every interrupt that you have enabled. Each ISR will have as a parameter the interrupt vector identifier necessary for the routine you define
    ISR(interrupt_vector_identifier)
    {
        // Place your code here
    }
  4. In the main function you need to activate global interrupts:
    sei();
  5. If you don't want your program to end when it terminates the instructions in the main function (for example: if you only need to do stuff when an interrupt occurs), you should add an infinite loop at the end of the main function, before the return statement. E.g.:
    while(1);
    for(;;);
    do {}while(1);

Common mistakes

  • Confusing the activation of global interrupts with the activation of a specific interrupt for a device.
    • If you don't set the Global Interrupt Enable bit, the microcontroller will ignore all device interrupts.
    • If you only set the Global Interrupt Enable bit and you don't also activate interrupts from the device side then the device will not generate interrupts.
  • If you have activated an interrupt, but didn't define handling routine in your software then this will cause the microcontroller to reset.
    • If this happens, check the names of the interrupt vectors in the handling routines and compare them to what interrupts you have activated. Do they match?
    • Did you define a handling routine for every interrupt activated?
  • Writing a computing intensive ISR is a mistake. Whenever you enter a routine the global interrupts are disabled; your devices still send the data, but the microcontroller refuses to handle them. You need to keep the interrupt routine as simple and quick as possible or else you might not be able to handle all the arriving interrupts and you will lose data.

Bibliography

Resources

roboticsisfun/chapter2/ch2_10_interrupts.txt · Last modified: 2012/11/04 01:24 by silvia.stegaru