Exceptions are asynchronous, hardware-driven events that cause the MCU to divert from normal code execution. This section provides an overview of the 16-bit MCU/dsPIC exception processing system.
More detailed information may be found in the interrupt controller section of the appropriate family reference manual. For example: PIC24F Interrupt Controller
The 16-bit MCU and DSC devices have a vectored exception scheme with support for up to 8 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 4 instruction-cycles on entering and 3 cycles exiting an interrupt service routine (ISR).
The combination of deterministic, low-latency exception processing with user-selectable interrupt priority make the 16-bit interrupt management system superior to most other architectures.
Vectored Exception Scheme
Each interrupt source can trigger execution of a unique piece of code, called an Interrupt Service Routine (ISR).
Each ISR's start address (also called a vector) is stored in the Primary Interrupt Vector Table (IVT) as shown:
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 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 2 types of exception:
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 & 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/I2C/USB/Ethernet)
- Parallel Master Port (PMP)
- Analog I/O (Comparator, ADC/DAC)
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:
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
- The programmer 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
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 3 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. 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.
Note that the latency for re-entering nested interrupts is 7 instruction cycles (3 for exiting the prior thread + 4 for re-entering the next thread
Note that lower priority exceptions in a nested application can experience extended and variable latencies
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 interrupt until a RETFIE (return from interrupt) 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.
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:
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 & 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:
External Interrupt Example (INT0)
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:
It is not recommended to change the interrupt priority while an interrupt is enabled – this can cause conflicts if the priority is changed at the same time an interrupt occurs, causing interrupt to be missed or incorrect priority levels to be used. An interrupt should be disabled before changing its priority.
To set the priority of any interrupt, the IPCx registers are to be used. A 3-bit field "IP" is provided whereby you can set the priority from 0 to 7. The following depicts the the IPCx register "INT0IP" bit locations for INT0 on PIC24FJ128GA010:
If priority is set to 0, the interrupt is disabled
On occurrence of a specific event, the interrupt's interrupt flag bit will be set in the IFSx register.
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 "interrupt service routine".
The MPLAB® XC16 Compiler defines a special function attribute ("attribute((interrupt))") for declaring an interrupt service routine function, as shown here for the INT0 interrupt:
Requirements for ISR functions:
- No parameters
- void return type
- Must use pre-defined name (see XC16 Compiler Users Guide)
- Must not be called from main-line code
Interrupts can occur at any time and could corrupt 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/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/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/restore other program variables that could be affected by the ISR via the save(x, y) attribute as shown:
For Fast Interrupts use Shadow Registers
Registers W0-W3 and certain CPU status registers are shadowed, meaning there is 1-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:
- Shadow Registers are only one level deep
- Contents can be lost if 2 interrupts use shadow registers and one is higher priority than the other