Interrupts and Signals(3)

NAME
Interrupts and Signals -

Detailed Description

Note:
    This discussion of interrupts and signals was taken from Rich Neswold's document. See Acknowledgments.

It's nearly impossible to find compilers that agree on how to handle interrupt code. Since the C language tries to stay away from machine dependent details, each compiler writer is forced to design their method of support.

In the AVR-GCC environment, the vector table is predefined to point to interrupt routines with predetermined names. By using the appropriate name, your routine will be called when the corresponding interrupt occurs. The device library provides a set of default interrupt routines, which will get used if you don't define your own.

Patching into the vector table is only one part of the problem. The compiler uses, by convention, a set of registers when it's normally executing compiler-generated code. It's important that these registers, as well as the status register, get saved and restored. The extra code needed to do this is enabled by tagging the interrupt function with __attribute__((interrupt)).

These details seem to make interrupt routines a little messy, but all these details are handled by the Interrupt API. An interrupt routine is defined with one of two macros, INTERRUPT() and SIGNAL(). These macros register and mark the routine as an interrupt handler for the specified peripheral. The following is an example definition of a handler for the ADC interrupt.

#include <avr/signal.h>
INTERRUPT(SIG_ADC)
{
    // user code here
}

Refer to the chapter explaining assembler programming for an explanation about interrupt routines written solely in assembler language.

If an unexpected interrupt occurs (interrupt is enabled and no handler is installed, which usually indicates a bug), then the default action is to reset the device by jumping to the reset vector. You can override this by supplying a function named __vector_default which should be defined with either SIGNAL() or INTERRUPT() as such.

#include <avr/signal.h>
SIGNAL(__vector_default)
{
    // user code here
}

The interrupt is chosen by supplying one of the symbols in following table. Note that every AVR device has a different interrupt vector table so some signals might not be available. Check the data sheet for the device you are using.

[FIXME: Fill in the blanks! Gotta read those durn data sheets ;-)]

Note:
    The SIGNAL() and INTERRUPT() macros currently cannot spell-check the argument passed to them. Thus, by misspelling one of the names below in a call to SIGNAL() or INTERRUPT(), a function will be created that, while possibly being usable as an interrupt function, is not actually wired into the interrupt vector table. No warning will be given about this situation.

  Signal Name Description SIG_2WIRE_SERIAL 2-wire serial interface (aka. I²C [tm]) SIG_ADC ADC Conversion complete SIG_COMPARATOR Analog Comparator Interrupt SIG_EEPROM_READY Eeprom ready SIG_FPGA_INTERRUPT0 SIG_FPGA_INTERRUPT1 SIG_FPGA_INTERRUPT2 SIG_FPGA_INTERRUPT3 SIG_FPGA_INTERRUPT4 SIG_FPGA_INTERRUPT5 SIG_FPGA_INTERRUPT6 SIG_FPGA_INTERRUPT7 SIG_FPGA_INTERRUPT8 SIG_FPGA_INTERRUPT9 SIG_FPGA_INTERRUPT10 SIG_FPGA_INTERRUPT11 SIG_FPGA_INTERRUPT12 SIG_FPGA_INTERRUPT13 SIG_FPGA_INTERRUPT14 SIG_FPGA_INTERRUPT15 SIG_INPUT_CAPTURE1 Input Capture1 Interrupt SIG_INPUT_CAPTURE3 Input Capture3 Interrupt SIG_INTERRUPT0 External Interrupt0 SIG_INTERRUPT1 External Interrupt1 SIG_INTERRUPT2 External Interrupt2 SIG_INTERRUPT3 External Interrupt3 SIG_INTERRUPT4 External Interrupt4 SIG_INTERRUPT5 External Interrupt5 SIG_INTERRUPT6 External Interrupt6 SIG_INTERRUPT7 External Interrupt7 SIG_OUTPUT_COMPARE0 Output Compare0 Interrupt SIG_OUTPUT_COMPARE1A Output Compare1(A) Interrupt SIG_OUTPUT_COMPARE1B Output Compare1(B) Interrupt SIG_OUTPUT_COMPARE1C Output Compare1(C) Interrupt SIG_OUTPUT_COMPARE2 Output Compare2 Interrupt SIG_OUTPUT_COMPARE3A Output Compare3(A) Interrupt SIG_OUTPUT_COMPARE3B Output Compare3(B) Interrupt SIG_OUTPUT_COMPARE3C Output Compare3(C) Interrupt SIG_OVERFLOW0 Overflow0 Interrupt SIG_OVERFLOW1 Overflow1 Interrupt SIG_OVERFLOW2 Overflow2 Interrupt SIG_OVERFLOW3 Overflow3 Interrupt SIG_PIN SIG_PIN_CHANGE0 SIG_PIN_CHANGE1 SIG_RDMAC SIG_SPI SPI Interrupt SIG_SPM_READY Store program memory ready SIG_SUSPEND_RESUME SIG_TDMAC SIG_UART0 SIG_UART0_DATA UART(0) Data Register Empty Interrupt SIG_UART0_RECV UART(0) Receive Complete Interrupt SIG_UART0_TRANS UART(0) Transmit Complete Interrupt SIG_UART1 SIG_UART1_DATA UART(1) Data Register Empty Interrupt SIG_UART1_RECV UART(1) Receive Complete Interrupt SIG_UART1_TRANS UART(1) Transmit Complete Interrupt SIG_UART_DATA UART Data Register Empty Interrupt SIG_UART_RECV UART Receive Complete Interrupt SIG_UART_TRANS UART Transmit Complete Interrupt SIG_USART0_DATA USART(0) Data Register Empty Interrupt SIG_USART0_RECV USART(0) Receive Complete Interrupt SIG_USART0_TRANS USART(0) Transmit Complete Interrupt SIG_USART1_DATA USART(1) Data Register Empty Interrupt SIG_USART1_RECV USART(1) Receive Complete Interrupt SIG_USART1_TRANS USART(1) Transmit Complete Interrupt SIG_USB_HW

  Global manipulation of the interrupt flag
The global interrupt flag is maintained in the I bit of the status register (SREG).
#define sei() __asm__ __volatile__ ('sei' ::)
#define cli() __asm__ __volatile__ ('cli' ::)

  Allowing specific system-wide interrupts
In addition to globally enabling interrupts, each device's particular interrupt needs to be enabled separately if interrupts for this device are desired. While some devices maintain their interrupt enable bit inside the device's register set, external and timer interrupts have system-wide configuration registers.

Example:

    // Enable timer 1 overflow interrupts.
    timer_enable_int(_BV(TOIE1));
    // Do some work...
    // Disable all timer interrupts.
    timer_enable_int(0);

Note:
    Be careful when you use these functions. If you already have a different interrupt enabled, you could inadvertantly disable it by enabling another intterupt.

#define enable_external_int(mask) (__EICR = mask)
static __inline__ void timer_enable_int (unsigned char ints)

  Macros for writing interrupt handler functions
#define SIGNAL(signame)
#define INTERRUPT(signame)
#define EMPTY_INTERRUPT(signame)

Define Documentation

  #define cli() __asm__ __volatile__ ('cli' ::)

#include <avr/interrupt.h>

Disables all interrupts by clearing the global interrupt mask. This function actually compiles into a single line of assembly, so there is no function call overhead.

  #define EMPTY_INTERRUPT(signame)

Value:

void signame (void) __attribute__ ((naked));    void signame (void) { __asm__ __volatile__ ('reti' ::); }

#include <avr/signal.h>

Defines an empty interrupt handler function. This will not generate any prolog or epilog code and will only return from the ISR. Do not define a function body as this will define it for you. Example:

 EMPTY_INTERRUPT(SIG_ADC);

  #define enable_external_int(mask) (__EICR = mask)

#include <avr/interrupt.h>

This macro gives access to the GIMSK register (or EIMSK register if using an AVR Mega device or GICR register for others). Although this macro is essentially the same as assigning to the register, it does adapt slightly to the type of device being used. This macro is unavailable if none of the registers listed above are defined.

  #define INTERRUPT(signame)

Value:

void signame (void) __attribute__ ((interrupt));      void signame (void)

#include <avr/signal.h>

Introduces an interrupt handler function that runs with global interrupts initially enabled. This allows interrupt handlers to be interrupted.

  #define sei() __asm__ __volatile__ ('sei' ::)

#include <avr/interrupt.h>

Enables interrupts by clearing the global interrupt mask. This function actually compiles into a single line of assembly, so there is no function call overhead.

  #define SIGNAL(signame)

Value:

void signame (void) __attribute__ ((signal));            void signame (void)

#include <avr/signal.h>

Introduces an interrupt handler function that runs with global interrupts initially disabled.

Function Documentation

  static __inline__ void timer_enable_int (unsigned char ints) [static]

#include <avr/interrupt.h>

This function modifies the timsk register. The value you pass via ints is device specific.