Exception Handling on a 16-bit PIC® MCU
Exceptions
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).
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".
Introduction
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.
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.
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)
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.
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.
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.
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.
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.
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.
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.
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.
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.
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:
IFSx Register
On occurrence of a specific event, the interrupt's interrupt flag bit will be set in the IFSx register.
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))
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
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.
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 register instructions may be optionally generated for your highest priority interrupt via the shadow attribute as shown in the accompanying image.