Exception Handling on a 16-bit PIC® MCU

Last modified by Microchip on 2023/11/09 09:00


Exceptions are asynchronous, hardware-driven events that cause the MCU to divert from normal code execution. This section provides an overview of the exception processing system on 16-bit MCUs and dsPIC® Digital Signal Controllers (DSCs).

Exception Handling on 16-bit MCUs

More detailed information may be found in the interrupt controller section of the appropriate family reference manual, for example: "Section 8, Interrupts in the PIC24F Family Reference Manual".


The 16-bit MCU and DSC devices have a vector exception scheme with support for up to eight sources of non-maskable traps and up to 246 interrupt sources. In both families, each interrupt source can be assigned to one of seven priority levels.

The basic interrupt latency is four instruction cycles on entering and three cycles exiting an Interrupt Service Routine (ISR).

The combination of deterministic, low-latency exception processing with user-selectable interrupt priority makes the 16-bit interrupt management system superior to most other architectures.

Vectored Exception Scheme

Each interrupt source can trigger the execution of a unique piece of code, called an ISR.

Each ISR's start address (also called a vector) is stored in the Primary Interrupt Vector Table (IVT) as shown in the accompanying image.

Primary Interrupt Vector Table (IVT)

Simplified Exception Process

When an exception event occurs:

  • Hardware (core or peripheral) detects it.
  • If the programmed priority of the exception event is GREATER THAN the current CPU priority, current program execution is halted.
  • The exception's ISR is started.

When an ISR completes:

  • Hardware restores the previously halted program execution (at the preserved CPU priority) at the exact instruction that would have been executed had the exception event not occurred.

Back to top

Types of Exceptions

The 16-bit exception processing system recognizes two types of exceptions.

Non-Maskable Traps

Traps can be considered as non-maskable, nestable interrupts which adhere to a fixed priority structure. They are intended to detect certain hardware and software problems. The PIC24F has four implemented sources of non-maskable traps:

  • Oscillator Failure Trap
  • Stack Error Trap
  • Address Error Trap
  • Arithmetic Error Trap

Peripheral and External Interrupts

These are the regular, maskable interrupt requests that come from a variety of implemented MCU peripherals:

  • External Interrupt Pins
  • Input Capture/Output Compare
  • Communication Interfaces (UART/SPI/I²C/USB/Ethernet)
  • Parallel Master Port (PMP)
  • Analog I/O (Comparator, ADC/DAC)

Back to top

CPU Priority

One of the key features of the 16-bit exception processing system is the implementation of a user-programmable priority setting for peripheral and external interrupt sources. This requires an understanding of the concept of CPU priority.

The 16-bit CPU can operate at one of 16 priority levels (0-15). An interrupt or trap source must have a set priority level greater than the current CPU priority in order to initiate an exception process.

Priority levels for peripheral and external interrupt sources can be programmed to levels 0-7, while CPU priority levels 8-15 are reserved for trap sources and are fixed.

The CPU priority for the currently executing thread is indicated by the IPL bits in the CPU Status and Core Control registers as shown in the accompanying image.

IPL Bits in Status Register and Core Control Register Indicate CPU Priority Level For Current Execution

At a device Reset, the CPU priority is set to 0 .

Configuring CPU Priority

  • IPL bits are automatically controlled by the exception processing logic based on the exception source.
  • You can manually set the user CPU priority bits (IPL<2:0>) at any time. This is useful for temporarily masking all other interrupts to perform a CPU-intensive task.
  • IPL3 is set only by the core and indicates a trap event.

Back to top

Interrupt Nesting

Interrupts, by default, are nestable. Any ISR that is in progress may be interrupted by another interrupt source having a higher programmed priority level.

The following animation demonstrates a specific scenario involving the main() execution thread along with three ISR threads pre-programmed at varying levels of priority with interrupt nesting enabled.

  • t0: CPU priority is 0 and is running the main() execution thread.
  • t1: A Level-4 exception is triggered. Since the exception priority (4) is greater than the current CPU priority (0), the Level-4 exception thread is executed.
  • t2: A Level-7 exception is triggered. Since the exception priority (7) is greater than the current CPU priority (4), the Level-7 exception thread is executed.
  • t3: A Level-1 exception is triggered. Since the exception priority (1) is less than the current CPU priority (7), the Level-1 exception thread is queued for later execution.
  • t4: The Level-7 exception thread completes and issues a RETFIE (return from interrupt). The Level-4 thread's context is popped off the stack (including the saved CPU priority) and the Level-4 thread continues execution.
  • t5: The Level-4 exception thread completes and issues a RETFIE. The queued Level-1 exception is triggered.
  • t6: The Level-1 exception thread completes and issues a RETFIE. The Level-0 (main()) thread's context is popped off the stack (including the saved CPU priority) and the Level-0 thread continues execution.

The latency for re-entering nested interrupts is seven instruction cycles (three for exiting the prior thread + four for re-entering the next thread).

Note: Lower-priority exceptions in a nested application can experience extended and variable latencies.

Disabling Nesting

Interrupt nesting may be optionally disabled by setting the NSTDIS control bit (INTCON1<15>).

When the NSTDIS bit is set, all interrupts in progress force the CPU priority to level 7 by setting IPL<2:0>=111.

This effectively masks all other sources of interrupts until a RETFIE instruction is executed.

When interrupt nesting is disabled, the user-assigned priority levels will have no effect, except to resolve conflicts between simultaneously pending interrupt requests.

Back to top

Resolving Interrupt Conflicts

Since more than one interrupt request source may be assigned to a specific priority level, a means is provided to resolve priority conflicts within a given user-assigned level.

For example, on a device Reset, the IPCn registers are initialized such that all user interrupt sources are assigned to priority level 4. How does the system resolve such conflicts?

Each interrupt source has a Natural Order Priority based on its location in the Interrupt Vector Table. Lower-numbered vectors have higher natural priority as shown in the accompanying image.

Image Showing Interrupt Source's Natural Prority Order by Location in the Interrupt Vector Table

The overall priority level for any pending interrupt source is thus determined:

  • First, by the user-assigned priority of that source in the IPCn register.
  • Then, by the natural order priority within the Interrupt Vector Table.

Back to top

Configuring and Using Interrupts

There are three sets of control bits that need to be considered when working with interrupts:

  • Interrupt Flags
    • Indicate that an interrupt event has occurred.
    • Set by the hardware and cleared by the programmer.
  • Interrupt Enables
    • Enable or disable individual interrupt sources.
  • Interrupt Priority bits
    • Set the priority of individual interrupt sources.

The Flag, Enable and Priority control bits are used by the interrupt controller to receive/prioritize all exception requests and send a single vector and corresponding IPL to the CPU core as shown in the accompanying image.

Diagram Showing how Interrupt Controller Uses Flags, Enables, and Priority Levels to Control CPU Action During Exceptions

External Interrupt Example (INT0)

IECx Register

To enable any particular interrupt, IECx registers are to be used. By setting the corresponding bit, the particular interrupt can be enabled. The following depicts the IECx bit location for INT0 on PIC24FJ128GA010.

The following depicts the IECx bit location for INT0 on PIC24FJ128GA010

We do not recommend changing the interrupt priority while an interrupt is enabled as this can cause conflicts. Changing the priority at the same time an interrupt occurs can lead to the interrupt being missed or incorrect priority levels being used. An interrupt should be disabled before changing its priority.

IPCx Register

The IPCx registers are used to set the priority of any interrupt. A 3-bit field IP is provided whereby you can set the priority from 0 to 7. The following depicts the IPCx register INT0IP bit locations for INT0 on PIC24FJ128GA010:

The IPCx register INT0IP bit locations for INT0 on PIC24FJ128GA010

If priority is set to 0, the interrupt is disabled.

IFSx Register

On occurrence of a specific event, the interrupt's interrupt flag bit will be set in the IFSx register.

Interrupt Flag Status Bit for INT0 in Interrupt Flag Status Register 0 (IFS0)

The user program needs to acknowledge the interrupt by clearing the IFS flag in the ISR.

Declaring an Interrupt Service Routine

The ANSI-C language specification does not specify how to declare a function as an ISR.

The MPLAB® XC16 Compiler defines a special function attribute for declaring an interrupt service routine function, as shown here for the INT0 interrupt:
__attribute__((interrupt))How Declare Interrupt Service Routines for XC16 C Compiler

Requirements for ISR functions:

  • No parameters
  • Void return type
  • Must use pre-defined name (see XC16 Compiler User's Guide)
  • Must not be called from main-line code

Context Saving

Interrupt Context Saving using the Software Stack

Interrupts can occur at any time and could corrupt the variables used in your main program.

The 16-bit exception processing hardware provides a mechanism for saving and restoring CPU context from the stack memory.

The following registers are automatically saved and restored from the stack by the exception processing hardware:

  • Program Counter (PCL & PCH)
  • CPU Status Register Low Byte (SRL)

Additionally, the XC16 compiler will generate additional context-saving instructions to save and restore the following registers from the stack:

  • Repeat Count Register (RCOUNT)
  • Working Registers used in the ISR (W0-W13)
  • Program Space Visibility Page Register (PSVPAG)
  • Previous Frame Pointer (W14)

In addition to these saved registers, you can have the compiler save and restore other program variables that could be affected by the ISR via the save(x, y) attribute as shown in the accompanying image.

Protecting Program Variables During Interrupt Processing Using the Compiler's "Save" Attribute

For Fast Interrupts use Shadow Registers

Registers W0-W3 and certain CPU status registers are shadowed, meaning there is one level of dedicated backup registers for them. Context is saved by a single PUSH.S instruction while POP.S will restore all these registers in a single shot.

Shadow Registers Speed up Context Switching

Shadow register instructions may be optionally generated for your highest priority interrupt via the shadow attribute as shown in the accompanying image.

XC16 Compiler's Shadow Attribute

  • Shadow registers are only one level deep.
  • Contents can be lost if two interrupts use shadow registers and one is a higher priority than the other.

Back to top