As an application increases in complexity, it may no longer be sufficient to use a single ISR (Interrupt Service Routine) which polls every interrupt flag each time a flag is triggered in order to discover which interrupt has triggered. This process can consume extra CPU cycles, causing your CPU to idle for too long every time an interrupt occurs. Vectored interrupts (available on some of the latest PIC18F devices) improve the response time by using hardware and a Vector Interrupt Table of pointers to trigger specific ISRs for each interrupt, without using extra clock cycles polling the other interrupts enabled on the Microcontroller.
How it Works
Imagine you have a system like the simplified version above. The flags each stand for an interrupt that could be generated if the associated peripheral reads the right event. Each sensor shows a way the microcontroller may process sensor data. Sensor 1's analog value is compared to a fixed voltage reference. The comparator triggers an interrupt if the sensor puts out a signal above the reference value. Sensor 2 needs to share data with the CPU, but only if the ADCC reads an incoming value. Finally, Sensor 3 reads a lot of data, and Timer 0 counts the sensor readings, reporting after reading a defined number of sensor outputs within a period of time. Finally, certain output peripherals may also use interrupts after certain events have occurred. The GIF below gives a visual demonstration of what happens in a vectored interrupt system:
Sensor 2 may be a capacitive touch button or some other type of analog sensor, and when the user-defined conditions are met, the ADCC interrupt flag goes HIGH. When this occurs, the interrupt controller first recognizes whether the ADCC interrupt is set to Low or High Priority (prioritization is explained below). Once the priority is determined, the controller uses the vector table to point to the first memory address where the ISR for the ADCC is located, and the ISR is executed before either processing other flagged interrupts or returning to the main program. Because this process takes place directly in hardware, it only takes three system clock cycles from the time the interrupt is triggered to the time the ISR is entered and begins executing.
Setting Vectored Interrupts in MCC
Vectored interrupts may be configured under the Interrupt Module window in MCC. This is shown below:
The green box represents the address within the vector table that holds the vector corresponding to the associated peripheral. In the above example, the vector pointing to the ISR to be triggered by the ADCC is held in the 31st position of the vector table.
The orange box must be checked if you want the corresponding peripherals to actually use interrupts in your application.
The blue box allows you to make certain interrupts a higher priority meaning that they will start their own ISR, even if a lower priority ISR has already started running. This is useful if you want to make sure some ISRs always trigger immediately, regardless of anything else that is going on in the system. Vectors of the same priority level are handled in the order of their place in the vector table, with a lower place number, such as 0, happening before a higher place number, such as 25.
The vector table is also movable, meaning that where its first address is located in memory can be changed. To change this, choose the drop down menu labeled Interrupt Vector Table Information and click in the IVT Base Address box to change the location. If you do this, make sure that wherever you move the first address, the flash memory still has enough addresses left for the rest of the table to avoid run time issues!
Setting Up the Firmware
Once you have set-up what you need in MCC, it is time to write what each individual ISR will accomplish. Generate the MCC file and return to your source files where you should see the peripherals you have set-up. Under each peripheral, a new interrupt function will appear. The syntax will follow the ADCC example below:
void __interrupt(irq(IRQ_ADT),base(IVT1_BASE_ADDRESS),low_priority) ADCC_ThresholdISR()
{
// Clear the ADCC Threshold interrupt flag
PIR1bits.ADTIF = 0;
if (ADCC_ADTI_InterruptHandler)
ADCC_ADTI_InterruptHandler();
}
The first line sets up the interrupt, while default code within the ISR simply clears the flag and completes any default operations, such as running the ADCC interrupt handler in this example. Interrupt handlers will generally appear at the end of the source code. This section is where you will write whatever you would like your ISR for each peripheral to accomplish. Because this is an interrupt service routine, it does not need to be called in your main.c file, and will instead happen automatically within the constraints of priority that we already discussed above.
Ensure that global interrupts are enabled by uncommenting the commented code generated in your main.c file as shown below:
Because this part has the option of prioritized interrupts, make sure you enable the appropriate prioritization as well as global interrupts. Parts that do not include this ability to prioritize will include a line to enable or disable peripheral interrupts instead of lines for low and high priority interrupts.
Summary
Vectored Interrupts provide improved speed over legacy interrupts when your system is using many interrupts at the same time. The CPU can process the interrupts faster and allows you to prioritize the interrupts to gain additional optimization over a single ISR approach. One note of caution is that not all parts have the capability to use vectored interrupts. At the time of this writing, the PIC18FK42 family of devices is the only PIC® line that can use vectored interrupts, although PIC18F devices can prioritize interrupts.