Implementing Interrupts Using MPLAB® Code Configurator

Last modified by Microchip on 2024/01/23 16:43

​This page discusses how to efficiently code interrupts in applications configured with MPLAB® Code Configurator (MCC).

PIC® microcontrollers (MCUs) service interrupt requests through interrupt vector addresses. When an interrupt occurs, the MCU saves the current program's context data, and then 'jumps' to a predetermined address. This predetermined address is referred to as the interrupt vector. (The amount of context data saved and the interrupt vector address(es) used are device- and peripheral-specific. Please refer to your device's datasheet for details.)

interrupt vectors

The application developer is responsible for writing an Interrupt Service Routine (ISR) to respond to the interrupt condition. In non-MCC applications, the developer inserts a compiler directive into the source, ensuring the compiler places a jump instruction at the interrupt vector address, whose destination is the application's ISR. At run-time, an interrupt causes non-MCC applications to jump indirectly to the ISR.

non-MCC ISR

When using MCC, an extra jump occurs when servicing interrupts. The code generated by MCC for each interrupt source creates a callback function and places a jump to callback at the interrupt vector. MCC inserts a directive in the code making the interrupt jump to the callback function. The callback function will then jump to the application's developer-written ISR. MCC-based projects utilize two jumps before the application's ISR executes.

MCC-generated ISR

Back to Top

Why Does MCC Interrupt Want to Encourage This Extra Jump?

The MCC's callback mechanism provides a level of abstraction between the code written by the developer and the code generated by MCC.

When a developer uses MCC to configure an interrupt, MCC places the source code defining the callback into one of the MCC-generated project files. With the exception of user-supplied files and main.c, all the MCC files are overwritten each time the programmer makes any change to an MCC configuration and pushes the Generate button. If the user were to input their code into the MCC-supplied file, the code would be lost each time the MCC configuration changes. To avoid the consequences of having to re-enter application code and to provide the ability to change an ISR at runtime, MCC utilizes a callback mechanism for the ISR.

abstracted ISR

Back to Top

How to Use the Callback Function

To use the callback function, the user-supplied ISR must be registered with the callback function. For each interrupt defined, MCC supplies a function to register the user-generated ISR.

Perform the following steps to utilize the callback ISR in MCC:

Specify the peripheral interrupt using MCC.

Click the Generate button to generate the MCC code.

Write a function in a file not located in the MCC-generated section of the project.

Insert the code into the application startup process to register the ISR with the callback function.

Back to Top

Example of Registering a Timer1 interrupt on an 8-bit PIC MCU

When MCC is used to generate an interrupt for Timer1, MCC creates tmr1.c containing all the function calls needed to set up and control interrupts for Timer1. Among the functions created there are two we will talk about:

  • TMR_ISR(void) - the callback function
  • TMR1_SetInterruptHandler(void* InterruptHandler) - the function to register the application ISR with the callback

Back to Top

Timer1.c

void TMR1_ISR(void)
{
    // Clear the TMR1 interrupt flag
    PIR1bits.TMR1IF = 0;

    TMR1H = (timer1ReloadVal >> 8); //Reset the 8-bit Timer1 overflow value
    TMR1L = timer1ReloadVal;

   if(TMR1_InterruptHandler)
    {
        TMR1_InterruptHandler();
    }
}

void TMR1_SetInterruptHandler(void* InterruptHandler)
{
   TMR1_InterruptHandler = InterruptHandler;
}

The user supplies their own application-specific ISR. This ISR function must be located in a non-MCC supplied file such as main.c.

An example of an application ISR could be a simple toggling an I/O pin:

void myTimerISR(void);

void myTimerISR(void)
{
   IO_RA2_Toggle();
}

The application's ISR is registered with the callback at run-time. TMR1_SetInterruptHandler must be called with the address of MyTimerISR as the input parameter.

TMR1_SetInterruptHandler (myTimerISR);     //Register the ISR

Back to Top

Completed main.c

Here is the final main.c code with all the Timer1 interrupt additions to toggle an I/O pin when a Timer1 interrupt occurs:

#include "mcc_generated_files/mcc.h"

void myTimerISR(void);

/*
                         Main application
 */

void main(void)
{
   // initialize the device
   SYSTEM_Initialize();

   TMR1_SetInterruptHandler (myTimerISR);     //Register the interrupt Handler

   // When using interrupts,set the Interrupt Enable bits
   // Use the following macros to:

   // Enable the Global Interrupts
   INTERRUPT_GlobalInterruptEnable();

   // Enable the Peripheral Interrupts
   INTERRUPT_PeripheralInterruptEnable();

   while (1)
    {
       // Add your application code
   }
}

void myTimerISR(void){
    IO_RA2_Toggle();
}
/**
 End of File
*/

Back to Top