The prototype and content of an ISR will vary based on the target device and the project being compiled. Observe the following guidelines when writing an ISR.
For devices that do not have the VIC module:
__interrupt()
specifier.void
as the return type and for the parameter
specification.low_priority
(or __low_priority
) or
high_priority
(or __high_priority
) arguments to
__interrupt()
.For devices operating in legacy mode:
__interrupt()
specifier.void
as the return type and specify a parameter
list of either void
or one char
argument if you need
to identify the interrupt source. It is recommended that the parameter list be set to
void if you want to ensure portability with devices that do not have the VIC
module. __interrupt()
specifier in the
ISR prototype, specify the interrupt priority assigned to the function’s source, using
low_priority
(or __low_priority
) or
high_priority
(or __high_priority
); and optionally,
specify the base address of the IVT in which to place the function’s address, using
base()
(or __base()
). It is recommended that the
base address be left as the default if you want to ensure portability with devices that
do not have the VIC module.For devices which are using the VIC module:
__interrupt()
specifier.void
as the return type and specify a parameter
list of either void
or one char
argument if you need
to identify the interrupt source.__interrupt()
specifier in the
ISR prototype, specify which sources each interrupt function should handle, using either
irq()
or __irq()
; specify the interrupt priority
assigned to the function’s source, using either low_priority
(or __low_priority
) or high_priority
(or
__high_priority
); and optionally, specify the base address of the
IVT in which to place the function’s address, using either base()
(or
__base()
).For all devices:
If interrupt priorities are being used but 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 you supply an irq()
or base()
argument to the __interrupt()
specifier with a device that does not have
the VIC module, an error will be issued by the compiler. If you use this specifier with a
device that is configured for legacy mode, supplying an irq()
argument
will result in an error from the compiler; however, you may continue to use the
base()
argument if required.
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 these files are located in the docs directory
under your compiler’s installation directory.
Interrupt functions always use the non-reentrant function model. These functions ignore any option or function specifier that might otherwise specify reentrancy.
The compiler processes interrupt functions differently to other functions, generating code to save and restore any registers used by the function and a special return instruction.
An example of an interrupt function written for code not using the IVT is
shown below. Notice that the interrupt function checks for the source of the interrupt by
looking at the interrupt enable bit (e.g., TMR0IE
) and the interrupt flag
bit (e.g., TMR0IF
). Checking the interrupt enable flag is required since
interrupt flags associated with a peripheral can be asserted even if the peripheral is not
configured to generate an interrupt.
int tick_count;
void __interrupt(high_priority) tcInt(void)
{
if (TMR0IE && TMR0IF) { // any timer 0 interrupts?
TMR0IF=0;
++tick_count;
}
if (TMR1IE && TMR1IF) { // any timer 1 interrupts?
TMR1IF=0;
tick_count += 100;
}
// process other interrupt sources here, if required
return;
}
Here is the same function code, split and modified for a device using vector tables. Since only one interrupt source is associated with each ISR, the interrupt code does not need to determine the source and is therefore faster.
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
, 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()
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 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, for the initial
vector table that will be used. 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 the interrupt function prototypes for two ISRs
which 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
either 0x100 or 0x200. Changing the address in this register allows you to select which
vector table is active.
void __interrupt(irq(TMR0,TMR1),base(0x100)) timerIsr(void)
{...}
void __interrupt(irq(TMR0,TMR1),base(0x200)) altTimerIsr(void)
{...}
void __interrupt(irq(default),base(0x100,0x200),low_priority) defIsr(void)
{...}