5.9.1.4 Writing Hardware Interrupt Vector Table ISRs

If your project is using a device with the VIC module and you intended to use one or more hardware interrupt vector tables (hardware IVTs), observe the following guidelines when writing interrupt service routines (ISRs).

Write as many ISRs as required using the __interrupt() specifier, using void as the ISR return type and specifying a parameter list of either void or one char parameter if you need to identify the interrupt source.

Devices that have the VIC module identify each interrupt with a number. This number can be specified with the irq() argument to __interrupt() if the vector table is enabled, or you can use a compiler-defined symbol that equates to that number. You can see a list of all interrupt numbers, symbols and descriptions by opening the files pic_chipinfo.html or pic18_chipinfo.html in your favorite web browser, and selecting your target device. Both of these files are located in the docs directory under your compiler’s installation directory.

Assign the interrupt priority to the function’s source, using either low_priority or __low_priority, or high_priority or __high_priority. If an ISR does not specify a priority, it will default to being high priority. It is recommended that you always specify the ISR priority to ensure your code is readable.

If the ISR processes more than one source, determine the source of the interrupt from the function’s parameter, if specified, or by checking the interrupt flag and the interrupt enable for each source that is to be processed.

At the appropriate point in your code, assign the required priority to each interrupt source by writing the appropriate bits in the SFRs. Additionally, enable the interrupt sources required and the global interrupt enable.

The following example shows two interrupt sources processed by two ISRs. Since only one interrupt source is associated with each ISR, the interrupt code does not need to determine the source and is therefore faster. The compiler-defined symbols TMR0 and TMR1 were used as the interrupt numbers.
void __interrupt(__irq(TMR0), __high_priority) tc0Int(void) {
    TMR0IF=0;
    ++tick_count;
    return;
}

void __interrupt(__irq(TMR1), __high_priority) tc1Int(void) {
    TMR1IF=0;
    tick_count += 100;
    return;
}
If you prefer to process multiple interrupt sources in one function, that can be done by specifying more than one interrupt source in the irq() argument and using a function parameter to hold the source number, such as in the following example.
void __interrupt(__irq(TMR0,TMR1), __high_priority) tInt(unsigned char src) {
    switch(src) {
    case IRQ_TMR0:
        TMR0IF=0;
        ++tick_count;
        break;
    case IRQ_TMR1:
        TMR1IF=0;
        tick_count += 100;
        break;
    }
    return;
}
The VIC module will load the parameter, in this example, src, with the interrupt source number when the interrupt occurs.

The special interrupt source symbol default or __default can be used to indicate that the ISR will be linked to any interrupt vector not already explicitly specified using irq(). You can also populate unused vector locations by using the -mundefints option (see Undefints Option).

By default, the interrupt vector table will be located at an address equal to the reset value of the IVTBASE register, which is the legacy address of 0x8. The base() or __base() argument to __interrupt() can be used to specify a different table base address for that function. This argument can take one or more comma-separated addresses. The base address cannot be set to an address lower than the reset value of the IVTBASE register.

By default, and if it is required, the compiler will initialize the IVTBASE register in the runtime startup code. You can disable this functionality by turning off the -mivt option (see Ivt Option). This option also allows you to specify an initial address for this register, which will position the vector table ready for mainline code. If vectored interrupts are enabled but you do not specify an address using this option, the vector table location will be set to the lowest table address used in the program, as specified by the __base() arguments to __interrupt().

If you use the __base() argument to implement more than one table of interrupt vectors, you must ensure that you allocate sufficient memory for each table. The compiler will emit an error message if it detects an overlap of any interrupt vectors.

The following examples show empty interrupt functions that handle the timer 0 and 1 interrupt sources. These are configured to reside in independent vector tables whose base addresses are 0x100 and 0x200. All other interrupt sources are handled by a low-priority ISR, defIsr(), which appears in both vector tables. For these ISRs to become active, the IVTBASE register must first be loaded with either 0x100 or 0x200. Changing the address in this register at runtime allows you to select which vector table is active.
void __interrupt(__irq(TMR0,TMR1), __base(0x100)) timerIsr(void) {
    // process timer interrupts here
}

void __interrupt(__irq(TMR0,TMR1), __base(0x200)) altTimerIsr(void) {
    // process timer interrupts here
}

void __interrupt(__irq(default), __base(0x100,0x200), low_priority) defIsr(void) {
    // process extraneous low priority interrupts here
}