5.9.3 Specifying the Interrupt Vector

For devices that do not have the VIC module, the process of populating the interrupt vector locations is fully automatic. The compiler links the interrupt code entry point to the fixed vector locations. Typically the entry point code will be all or part of the code that performs the interrupt context switch and the body of the interrupt function will be located elsewhere.

The location of these interrupt vectors cannot be changed at runtime, nor can you change the code linked to the vector. That is, you cannot have alternate interrupt functions and select which will be active during program execution. An error will result if there are more interrupt functions than interrupt vectors in a program.

For devices that have the VIC module, you have more freedom in how interrupt functions can be executed at runtime. When the IVT is enabled, these devices employ a table of interrupt vectors. Each table entry can hold an address, which is read when the corresponding interrupt is triggered, and the device will jump to that address. The vector table entry corresponding to an interrupt function is automatically completed by the compiler, based on the information in the irq() (or __irq()) and base() (or __base()) arguments to __interrupt(), see 5.9.1 Writing an Interrupt Service Routine.

Although the addresses in the vector table cannot be changed at runtime, it is possible to construct more than one table and have the device swap from one table to another. Changing the active vector table is performed by changing the vector table base address, which is stored in the IVTBASE registers. Since these registers cannot be modified atomically, you must disable all interrupts before changing their content. The following example shows how this might be performed in C code.

di();    // disable all interrupts
IVTBASEU = 0x0;
IVTBASEH = 0x2;
IVTBASEL = 0x0;
ei();    // re-enable interrupts

You can also preload this register by using the -mivt option (see 4.6.1.12 Ivt Option).

For devices with the VIC module operating in legacy mode, the vector table is disabled and the dual-priority vectors employed by regular PIC18 devices are used. These vector locations will then hold an instruction, not an address, but unlike regular PIC18 devices, the program can use the IVTBASE register to map the vector locations to any address and you can define two interrupt functions for each base address.

Do not confuse the function of the IVTBASE register with the -mcodeoffset option (see 4.6.1.3 Codeoffset Option). The option moves the code associated with reset but does not change the address to which the device will vector on reset. For devices without the VIC, the option also moves the code associated with the interrupts, but again does not change the interrupt vector addresses. For devices with the VIC, even if they are running in legacy mode, the code associated with interrupts is not affected by this option. Instead, for those devices, the IVTBASE registers controls the address at which the vector table is assumed to occupy (or the address of the interrupt vectors when devices are running in legacy mode).

If you are writing an application that is loaded by a bootloader for devices with the VIC, you will typically need to use both the -mcodeoffset option and the IVTBASE registers to ensure that the reset and interrupt entry points are shifted. The option shifts the reset location and, provided the interrupt functions have used the appropriate base() address argument, changing the register will ensure that the shifted vector table is correctly accessed. This requirement is relevant for devices operating in legacy mode.

Interrupt vectors that have not been specified explicitly in the project can be assigned a default function address by defining an interrupt function that uses default as its irq() interrupt source, or assigned a default instruction by using the -mundefints option (see 4.6.1.26 Undefints Option).