4.8.1.1 Writing ISRs for a Full Vector Table

If your project is using the full vector table, observe the following guidelines when writing an ordinary ISR.

Enable the CCI (Ext Option). Write as many ISRs as required using the __interrupt() specifier, using void as the ISR return type and as the parameter list. Consider implementing default interrupt functions to handle accidental triggering of unused interrupts.

The __interrupt() specifier takes a numerical argument to indicate the interrupt number, hence the interrupt source. Macros whose names ends with vect_num are available once you have included <xc.h> in your program and can be used to indicate the interrupt number in a more readable way.

At the appropriate point in your code, enable the interrupt sources required and the global interrupt enable.

An example of an interrupt function is shown below. Ensure the CCI is enabled using the -mext=cci option.
#include <xc.h>
void __interrupt(SPI_STC_vect_num) spi_Isr(void) {
  process(SPI_ClientReceive());
  return;
}

More complex interrupt function definitions can be created using macros and attributes defined by <avr/interrupt.h>, which are shown in the following examples.

If there is no code to be executed for an interrupt source but you want to ensure that the program will continue normal operation should the interrupt unexpectedly trigger, then you can create an empty ISR using the EMPTY_INTERRUPT() macro and an interrupt source argument.
#include <avr/interrupt.h>
EMPTY_INTERRUPT(INT2_vect);
The special interrupt source symbol, BADISR_vect, can be used with the ISR() macro to define a function that can process any otherwise undefined interrupts. Without this function defined, an undefined interrupt will trigger a device reset. For example:
#include <avr/interrupt.h>
ISR(BADISR_vect) {
  // place code to process undefined interrupts here
  return;
}
If you wish to allow nested interrupts you can manually add an in-line sei instruction to your ISR to re-enable the global interrupt flag; however, there is an argument you can use with the ISR() macro to have this instruction added by the compiler to the beginning of the interrupt routine. For example:
#include <avr/interrupt.h>
ISR(IO_PINS_vect, ISR_NOBLOCK)
{ ... }
The ISR_NOBLOCK expands to __attribute__((interrupt)), which can be used instead.
If one ISR is to be used with more than one interrupt vector, then you can define that ISR in the usual way (using __interrupt() and enabling the CCI) for one vector then reuse that ISR for other vector definitions using the ISR_ALIASOF() argument.
#include <xc.h>
void __interrupt(PCINT0_vect_num)
{ ... }
ISR(PCINT1_vect, ISR_ALIASOF(PCINT0_vect));
In some circumstances, the compiler-generated context switch code executed by the ISR might not be optimal. In such situations, you can request that the compiler omit this context switch code and supply this yourself. This can be done using the ISR_NAKED argument, as shown in this example.
#include <avr/interrupt.h>
ISR(TIMER1_OVF_vect, ISR_NAKED)
{
  PORTB |= _BV(0); // results in SBI which does not affect SREG
  reti();
}

Note that the compiler will not generate any context switch code, including the final return from interrupt instruction, so you must write any relevant switching code and the reti instruction. The SREG register must be manually saved if it is modified by the ISR, and the compiler-implied assumption of __zero_reg__ always being 0 could be wrong, for example, when an interrupt occurs right after a mul instruction.