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).