Interrupts are one of the most powerful features of PIC Microcontrollers, interrupts make it possible to create applications that can respond to external stimulus in real time. An interrupt is basically an event that requires the microcontroller to stop normal program execution and then to jump to execute a program code related to the event causing the interrupt. An interrupt requires immediate attention, only once the microcontroller will finish executing the interrupt code, then it can go back to continue with the main program. The interrupt code is called Interrupt Service Routine (ISR) or Interrupt Handler. Here is a simple example to understand interrupts, let say you are playing a game with your phone and Suddenly your mobile phone rings somebody is calling you. Your phone will immediately leave the game and start ringing. Only once you are done with the call, then the phone will jump back to the game. This process is similar to ISR execution. You can think the main service routine in this case as playing the game and the ringing of the mobile phone as causing an interrupt. This initiates your mobile phone conversation which is similar to executing Interrupt Service Routine (ISR).

If there were no interrupt, while playing the game, the microcontroller would time to time pause the game and monitor if there is no one trying to call you. As you can see, this is not an efficient way of programming as it consumes all its processing time for monitoring and they can be a possibility of missing a short process that can require immediate attention. The best way is to leave the microcontroller do its normal main program, and if there is nothing to do, let the microcontroller go into sleep mode and be awaken only to respond to an interrupt that occurs. This can save power and much needed processor power especially if the application if battery powered.  The processes of continuous monitoring is known as POLLING.

Interrupts can be very useful in many applications such as:

  • Fail safe applications: Applications which require the immediate attention of the microcontroller when there is a fault in the system can use interrupts. For example, in an emergency such as a power failure in an hazardous environment where a the microcontroller has to take some precise coordinated actions like switching off the system immediately in an orderly manner. In such applications an external interrupt can force the microcontroller to stop whatever it is doing and take immediate action.
  • Performing routine tasks: If an application requires the microcontroller to perform routine tasks at precise times, such as blinking a status LED, reading inputs of sensors connected to the microcontroller exactly every few millisecond. A timer interrupt scheduled with the required timing can divert the microcontroller from normal program execution to accomplish the task at the precise time required.
  • To check if a certain tasks have been completed: Some applications may need to know when a task, such as an A/D conversion, is completed, or instead of keep on listening (polling) for an incoming data from an UART or USB port for example, an interrupt could be raise immediately when an A/D conversion is done or when there is an incoming data instead of keeping the microcontroller doing nothing but waiting for this to happen.

Interrupts in PIC18F Series

Different PIC Microcontrollers have different interrupts, but most have both the core and peripheral interrupt sources. Always check your device datasheet to find out more about the interrupts. Figure 1 below shows the diagram of the PIC18F452.


Figure 1: PIC18F452

Like most PIC18F series PIC Microcontrollers, the PIC18F452 has the following interrupts:

  • External: External edge-triggered interrupt on INT0, INT1, and INT2 pins (RB0, RB1 and RB2).
  • PORTB pins change interrupts (any one of the RB4–RB7 pins changing state)
  • Timer 0 overflow interrupt
  • Timer 1 overflow interrupt
  • Timer 2 overflow interrupt
  • Timer 3 overflow interrupt
  • Parallel slave port read/write interrupt
  • A/D conversion complete interrupt
  • USART receive interrupt
  • USART transmit interrupt
  • Synchronous serial port interrupt
  • CCP1 interrupt
  • CCP2 interrupt
  • Comparator interrupt
  • EEPROM/FLASH write interrupt
  • Bus collision interrupt
  • Low-voltage detect interrupt

There are ten registers in the PIC18F452 microcontroller that control interrupt operations:

  • RCON
  • PIR1, PIR2
  • PIE1, PIE2
  • IPR1, IPR2

Interrupts in the PIC18F family can be divided into two groups: high priority and low priority. Applications that require more attention can be placed in the higher priority group. A high-priority interrupt can stop a low-priority interrupt that is in progress and gain access to the CPU. However, high-priority interrupts cannot be stopped by low-priority interrupts. If the application does not need to set priorities for interrupts, the user can choose to disable the priority scheme so all interrupts are at the same priority level.

Every interrupt source (except INT0) has three bits to control its operation:

  • A flag bit to indicate whether an interrupt has occurred regardless of whether it is enabled or not. This bit has a name ending inIF
  • An interrupt enable bit to enable or disable the interrupt source. This bit has the name ending inIE
  • A priority bit to select high or low priority. This bit has a name ending in IP.

For an interrupt to work the following conditions must be satisfied:

  • The interrupt enable bit of the interrupt source must be enabled. For example, if the interrupt source is external interrupt pin INT0, then bit INT0IE of register INTCON must be set to 1.
  • The interrupt flag of the interrupt source must be cleared. For example, if the interrupt source is external interrupt pin INT0, then bit INT0IF of register INTCON must be cleared to 0.
  • The peripheral interrupt enable/disable bit PEIE of INTCON must be set to 1 if the interrupt source is a peripheral.
  • The global interrupt enable/disable bit GIE of INTCON must be set to 1.

RCON Register

The RCON register contains the bit which is used to enable interrupt priority (IPEN). When IPEN = 0, interrupt priority levels are disabled and the microcontroller interrupt structure is similar to that of the PIC16 series which does not have priorities. When IPEN = 1, interrupt priority levels are enabled. Figure 2 below shows the bits of register RCON.

Figure 2: PIC18F452 RCON Register

INTCON Registers

The INTCON Registers are readable and writable registers, which contain various enable, priority and flag bits.

Figure 3: PIC18F452 INTON Register

For more information on other registers, please check your PIC Datasheet from Microchip website. The PIC18F452 Datasheet can be downloaded from this link.

 External INT0 interrupt on PORTB.0 pin Example

In this simple External INT0 interrupt on PORTB.0 pin Example using the PIC18F4520. Make sure the IO pin you are going to use for interrupt actually has an interrupt feature. You can’t just use any pin because it is convenient for you, check your datasheet to make sure it has interrupt. In our case, we are going to use pin RB0 of PIC18F4520 which is INT0 (External Interrupt 0 Input). A switch will be connected to this pin and pulled up to VCC. When the switch is pressed, the pin voltage becomes zero. So I will be setting the interrupt to work at the falling edge. Two LEDs are connected one to PORTB.4, this will flash normally at an interval of 1 second  until interrupt occurs (RB0 is pressed with a push button). Then the interrupt routine will be executed, the second LED on RB5 will flash rapidly 5 times at an interval of 300ms.

Watch The Video Tutorial: External Interrupt

With XC8, using interrupts is programmatically simple, since most of the code is given to you by <xc.h>. By just using the keyword “interrupt” before the function which should be called when an interrupt happens to setup the interrupt.

In order to make the pin RB0 to handle interrupt these are the only few steps to follow:

  • Set RB0 as input pin (set the TRIS as 1): TRISBbits.RB0 = 1;    //set RB0 as Input
  • Enable the interrupt bit for RB0: INTCONbits.INT0E = 1;   //enable Interrupt 0 (RB0 as interrupt)
  • Set rising or falling edge interrupt:  INTCON2bits.INTEDG0 = 0;   //cause interrupt at falling edge
  • Clear the interrupt for the pin (just to make sure):  INTCONbits.INT0F = 0;   //reset interrupt flag
  • Finally you got to enable the master switch for interrupt:  ei();     // This is like flipping the master switch to enable interrupt

When an interrupt occurs, you need a subroutine to do something. You’ll need to handle the interrupt in the following order:

  • Check if the interrupt flag to make sure the IO pin caused the interrupt (or the peripheral you are looking for)
  • Do the subroutine task
  • Reset the interrupt flag

Here is the full code, the circuit diagram is shown on figure 4. In normal operation, the LED on RB4 will be flashing at an interval of 1 second. When there is an interrupt, the main program will jump to this subroutine and Flash fast the LED connected to PORTB5.

Figure 4: External Interrupt on pin RB0

* File: Interrupt.c
* Author:
*/#include <stdio.h>
#include <stdlib.h>
#include "config.c" // File with Configuration bitsbit flag; //Declare the Flag bit//Interrupt Subroutine
void interrupt CheckSwitchPressed()
{ //check if the interrupt is caused by the pin RB0
if(INTCONbits.INT0F == 1) //Checks Receive Interrupt Flag bit
//Do your interrupt code, flash RB5 5 times
for (int flash_count = 0;flash_count <5; flash_count++) {
LATBbits.LATB5 = 1;
for (int count=0; count<20; count++) __delay_ms(15); //Generate 300ms delay
LATBbits.LATB5 = 0;
for (int count=0; count<20; count++) __delay_ms(15); //Generate 300ms delay
}INTCONbits.INT0F = 0; // Clear Interrupt Flag
}void main(void) {
TRISB = 0x0F; //Set PORTB first 4 pins as Input and last 5 as OutputINTCONbits.INT0E = 1; //enable Interrupt 0 (RB0 as interrupt)
INTCON2bits.INTEDG0 = 0; //cause interrupt at falling edge
INTCONbits.INT0F = 0; //clear interrupt flagei(); // This is like flipping the master switch to enable interrupt
while (1)
LATBbits.LATB4 = 1; //Switch ON B4
for (int count=0; count<20; count++) __delay_ms(50); //Generate 1 sec delay
LATBbits.LATB4 = 0; //Switch OFF B4
for (int count=0; count<20; count++) __delay_ms(50); //Generate 1 sec delay

External Interrupt Configuration with MPLAB Code Configurator (MCC)

The first thing we are going to select External Interrupt (EXT_INT) from the Peripherals as shown of figure 5 below:

Figure 5: External Interrupt selection with MCC

In this example we’re gonna enable only INT0 and select rising edge. And that’s all!

Select the Pin Module to open the pin manager as shown on figure 6 below:

Figure 6: External Interrupt Pin manager

In pin manager INT0 on RB0 was enable and RB4 and RB5 enabled as Output pins. In pin module, we customized RB4 and RB5 to LED_D1 and LED_D2 respectively. Click on Generate button to generate the code.



#include "mcc_generated_files/mcc.h"

                         Main application
void main(void)
    // Initialize the device

    // If using interrupts in PIC18 High/Low Priority Mode you need to enable the Global High and Low Interrupts
    // If using interrupts in PIC Mid-Range Compatibility Mode you need to enable the Global and Peripheral Interrupts
    // Use the following macros to:

    // Enable the Global Interrupts

    // Enable the Peripheral Interrupts

    // Disable the Peripheral Interrupts

    while (1)
        // Add your application code
        LED_D1_Toggle() ;    

 End of File

The first thing is to Enable the Global Interrupts by removing the comments on  INTERRUPT_GlobalInterruptEnable(); and Enable the Peripheral Interrupts by removing the comments on INTERRUPT_PeripheralInterruptEnable();

In the While(1) loop, we wrote the code to toggle LED 1 at an interval of 1 second.


#include "interrupt_manager.h"
#include "mcc.h"

void  INTERRUPT_Initialize (void)
    // Disable Interrupt Priority Vectors (16CXXX Compatibility Mode)
    RCONbits.IPEN = 0;

void interrupt INTERRUPT_InterruptManager (void)
    // interrupt handler
    if(INTCONbits.INT0IE == 1 && INTCONbits.INT0IF == 1)
        //Unhandled Interrupt
 End of File

The interrupt_manager.c file contains the Interrupt Service Routine that will run when the interrupt occurs. when an interrupt occurs, the function INT0_ISR() will be called from ext_int.c file.


  Interrupt Handler for EXT_INT0 - INT0
void INT0_ISR(void)
    //***User Area Begin->code***
    for (int count =0; count <5; count++)

    //***User Area End->code***

    // Callback function gets called everytime this ISR executes

In ext_int.c look for void INT0_ISR(void) function. This is where we’re gonna write the ISR code as shown above. In this example the LED_D2 will blink fast at an interval of 300ms 5 times.

Watch The Video Tutorial: Peripheral Interrupt

In this simple Peripheral UART Rx event interrupt Example using the PIC18F45K22 which has 2 UART modules, Whenever there is data sent to the serial port (the PIC USART), it will trigger the USART receive interrupt (interrupt on serial RX pin), if the sent character is 1, the microcontroller will execute the Interrupt Service Routine code, any other character will be ignored. In the main code, the Green LED connected to RB4 blinks at an interval of 1 seconds. The Interrupt Service Routine code will blink the Red LED connected to RB5 at an interval of 200ms 5 times

You can download the full project files (MPLAB XC8 source code and Proteus Schematic design) below here.
All the files are zipped, you will need to unzip them (Download a free version of the Winzip utility to unzip files).

MPLAB XC8 Code: Interrupt MPLAB XC8 Project

MPLAB XC8 Code: External_Interrupt_with_MCC

Proteus Schematic: Interrupt RB0 Proteus Schematic