PIC32MX Interrupt and Exception Usage

Last modified by Microchip on 2023/11/15 11:39

Summary

PIC32MX Interrupt and Exception operation must be carefully initialized by the application developer. This page summarizes the key initialization and usage steps required for both interrupt exceptions and general exceptions.

Further information regarding interrupt and exception usage is provided by the MPLAB® XC32 User's Guide (DS50001686)

Overview

PIC32MX CP0 and Interrupt Controller registers are initialized by hardware and MPLAB XC32 Compiler start-up code placing the Central Processing Unit (CPU) in the following state upon entry to your main() function:

  • Ebase = _ebase_address (= 0x9FC01000 for PIC32MX795F512L)
  • IntCtlVS<4:0> = 0x01 (Vector spacing is initialized to 32 (0x20) bytes (8 words) between entry points)
  • StatusBEV = 0 (Exception vector entry points changed from the "bootstrap" location to the "normal" location)
    • General Exceptions: Ebase (= 0x9FC01000) + 0x180
    • Interrupt Exceptions: Ebase + 0x200 + (VectNumber * IntCtlVS)
  • StatusIE = 0 (Interrupt Exceptions Disabled)
  • StatusIPL<2:0> = 0 (CPU running @ priority level 0)
  • INTCONMVEC = 0 (Interrupt controller configured for single-vector mode)
  • Prefetch Cache is disabled
  • PFM Wait States = 7 (max. setting)
  • Data Ram Wait States = 1 (max. setting)

Back to Top

The following steps must be taken in the application code for proper interrupt exception initialization and usage.

Procedure

#include Standard Headers

The application must include header files xc.h and attribs.h as shown.

1
2
#include <xc.h>            
#include
<sys/attribs.h> 

The <sys/attribs.h> header file provides macros intended to simplify the application of attributes to interrupt functions:

  • _ISR(V, IPL)
  • _ISR_AT_VECTOR(V, IPL)

There are also Vector Number macros defined in the processor header files (included via <xc.h>) - see the appropriate header file in the compiler’s /pic32mx/include/proc directory.


Provide Interrupt Service Routine (ISR)

 An interrupt handler function is different from an ordinary function in that it handles the context save and restore to ensure that upon return from interrupt, the program context is maintained. A different code sequence is used to return from these functions as well.

There are several actions that the compiler needs to take to generate an interrupt service routine:

  • The compiler has to be told to use an alternate form of return code.
  • The compiler has to be told whether to use a shadow register set for context save/restore.
  • The function needs to be linked to the interrupt vector.

Several function _attributes_ are provided to allow the application developer to define ISR handlers so that the compiler generates the correct code for an ISR (__ISR() macros are provided so that this is easier to accomplish).

For all interrupt vectors without specific handlers, a default interrupt handler will be installed. The default interrupt handler will reset the device.

An application may override the default handler and provide an application-specific default interrupt handler by declaring an interrupt function with the name _DefaultInterrupt.

Interrupt Service Routine Definition using the _ISR() Macro

The following code example shows how to use the _ISR() macro to define and interrupt function:

1
2
3
4
5
6
void __ISR(_INT_VECTOR_NUMBER, IPLx[SRS|SOFT|AUTO]) isrName(void)
{
   /* Clear the cause of the interrupt (if required) */32bit:mx-code-examples-general-exception-usage
   /* Clear the interrupt flag */
   /* ISR-specific processing */
}        

The various parameters will now be more fully described.

_INT_VECTOR_NUMBER

  • Unique Vector Number Identifier, as shown in Table 7-1 in the device datasheet:

Unique Vector Number Identifier

  • Macros are provided in the device header file to simplify/clarify usage. The PIC32MX795F512L header file is located here:
    • /xc32/<version>/pic32mx/include/proc/p32mx795f512l.h

Macros provided in the device header file

IPLx[SRS|SOFT|AUTO]

  • Assign Interrupt priority level (0-7) to the ISR, and
  • Define how context is preserved
    • The generated code preserves context by either using the shadow register set (SRS) or using generated software instructions (SOFT) to push context onto the stack.
    • AUTO setting tells the compiler to use the run-time value in CP0 register SRSCtl to determine whether it should use SRS or SOFT context-saving mode.

The IPLx setting must match the interrupt controller setting (IPCx) for the specific interrupt source.

isrName

  • Unique name you give the ISR.

NO parameters or return values are allowed on an ISR.

void __ISR(_INT_VECTOR_NUMBER, IPLx[SRS|SOFT|AUTO]) isrName(void)

Example

In this example, we define a Timer 2 ISR function T2Interrupt() that is configured as a IPL-level-7 process, and to which is assigned the shadow-register set (as defined by the DEVCFG3FSRSSEL<2:0> (Device Configuration Word 3) bit settings shown):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <xc.h>
#include
<sys/attribs.h>

/* Config bits (not all shown) */

#pragma config FSRSSEL = PRIORITY_7     /* Assign the SRS to level 7 priority handlers */

void __ISR(_TIMER_2_VECTOR, IPL7SRS) T2Interrupt(void)
{
   // Toggle LED LD1
   LATGINV = _LATG_LATG6_MASK;

   // Reset interrupt flag
   IFS0bits.T2IF = 0;
}

int main(void)
{
   // Initialization

    IPC2bits.T2IP = 7;                /* Set Timer 2 interrupt priority to 7 */

   while(1);                        /* main application loop */

}

Configure Peripheral

Next, you must configure the peripheral to generate interrupt request events.

For example, the PIC32MX contains several Timer peripherals modules. Each module has a period register (PRx) that, when properly initialized, will periodically trigger a Timer interrupt request signal in an IFSx register as shown:

Timer interrupt request signal in an IFSx register

In this example, we will initialize Timer 2 to generate interrupt requests every 100mS, given a peripheral bus clock (PBCLK) of 10 MHz (code not shown):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <xc.h>
#include
<sys/attribs.h>

/* Config bits (not all shown) */

#pragma config FSRSSEL = PRIORITY_7     /* Assign the SRS to level 7 priority handlers */

void __ISR(_TIMER_2_VECTOR, IPL7SRS) T2Interrupt(void)
{
   // Toggle LED LD1
   LATGINV = _LATG_LATG6_MASK;

   // Reset interrupt flag
   IFS0bits.T2IF = 0;
}

int main(void)
{
   // Initialization
   T2CONbits.TON = 0;                /* turn off Timer 2 */
    T2CONbits.TCKPS = 7;                /* pre-scale = 1:256 (T2CLKIN = 39062.5 Hz) */
    PR2 = 3906;                    /* T2 period ~ 100mS */
    TMR2 = 0;                        /* clear Timer 2 counter */

    IPC2bits.T2IP = 7;                /* Set Timer 2 interrupt priority to 7 */

   while(1);                        /* main application loop */

}

Configure Interrupt Controller

The next step is to configure the associated Interrupt Controller Flag, Enable, and Priority settings for the specific interrupt. In this case, those registers/bits associated with Timer 2 interrupt events:

  • IPC2 Register
    • Contains Timer 2 interrupt priority bits
  • IFS0 Register
    • Contains Timer 2 interrupt request bit
  • IEC0 Register
    • Contains the Timer 2 interrupt enable bit

Refer to the Interrupt Controller section in the device datasheet for a complete description of IPCx/IFSx/IECx registers for all interrupt sources.

In the following example, we will configure these registers; setting the initial priority level of the Timer 2 interrupt event to 4, matching the IPL setting shown in the ISR function definition.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <xc.h>
#include
<sys/attribs.h>

/* Config bits (not all shown) */

#pragma config FSRSSEL = PRIORITY_7     /* Assign the SRS to level 7 priority handlers */

void __ISR(_TIMER_2_VECTOR, IPL7SRS) T2Interrupt(void)
{
   // Toggle LED LD1
   LATGINV = _LATG_LATG6_MASK;

   // Reset interrupt flag
   IFS0bits.T2IF = 0;
}

int main(void)
{
   // Initialization
   T2CONbits.TON = 0;                /* turn off Timer 2 */
    T2CONbits.TCKPS = 7;                /* pre-scale = 1:256 (T2CLKIN = 39062.5 Hz) */
    PR2 = 3906;                    /* T2 period ~ 100mS */
    TMR2 = 0;                        /* clear Timer 2 counter */

    IPC2bits.T2IP = 7;                /* Set Timer 2 interrupt priority to 7 */
    IFS0bits.T2IF = 0;                /* Reset Timer 2 interrupt flag */
    IEC0bits.T2IE = 1;                /* Enable interrupts from Timer 2 */

   while(1);                        /* main application loop */

}

Assign The Shadow Register Set to an IPL

The PIC32MX has 1 shadow register set that can be used for any priority level, eliminating the software context switch and reducing interrupt latency.

If you assign a shadow register set to an interrupt handler function (as in our example), you must also initialize the DEVCFG3FSRSSEL<2:0> (Device Configuration Word 3) bit settings.

The following example initializes DEVCFG3FSRSSEL<2:0> so that the shadow register sets are assigned to interrupt priority level 7.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <xc.h>
#include
<sys/attribs.h>

/* Config bits */
#pragma config FSRSSEL = PRIORITY_7     /* Assign the SRS to level 7 priority handlers */

void __ISR (_TIMER_2_VECTOR, IPL7SRS) T2Interrupt(void)
{
   // Toggle LED LD1
   LATGINV = _LATG_LATG6_MASK;

   // Reset interrupt flag
   IFS0bits.T2IF = 0;
}

int main(void)
{
   // Initialization
   T2CONbits.TON = 0;                /* turn off Timer 2 */
    T2CONbits.TCKPS = 7;                /* pre-scale = 1:256 (T2CLKIN = 39062.5 Hz) */
    PR2 = 3906;                    /* T2 period ~ 100mS */
    TMR2 = 0;                        /* clear Timer 2 counter */

    IPC2bits.T2IP = 7;                /* Set Timer 2 interrupt priority to 7 */
    IFS0bits.T2IF = 0;                /* Reset the Timer 2 interrupt flag */
    IEC0bits.T2IE = 1;                /* Enable interrupts from Timer 2 */

   while(1);                        /* main application loop */

}

All 3 steps must be followed to properly configure shadow register operation:

  • Device configuration bits
  • ISR Definition
  • Peripheral IPCx register setting

This page provides more details on shadow register set operation on PIC32MX. 


Set Interrupt Controller Mode

Next, we need to configure the interrupt controller to operate in MULTI-VECTOR MODE, whereby each interrupt source is assigned to its own interrupt vector.

This setting is controlled by the INTCONMVEC bit.

MPLAB XC32 provides a nice macro that can be applied with the INTCONSET register as shown in the following example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <xc.h>
#include
<sys/attribs.h>

/* Config bits */
#pragma config FSRSSEL = PRIORITY_7     /* Assign the SRS to level 7 priority handlers */

void __ISR (_TIMER_2_VECTOR, IPL7SRS) T2Interrupt(void)
{
   // Toggle LED LD1
   LATGINV = _LATG_LATG6_MASK;

   // Reset interrupt flag
   IFS0bits.T2IF = 0;
}

int main(void)
{
   // Initialization
   T2CONbits.TON = 0;                /* turn off Timer 2 */
    T2CONbits.TCKPS = 7;                /* pre-scale = 1:256 (T2CLKIN = 39062.5 Hz) */
    PR2 = 3906;                    /* T2 period ~ 100mS */
    TMR2 = 0;                        /* clear Timer 2 counter */

    IPC2bits.T2IP = 7;                /* Set Timer 2 interrupt priority to 7 */
    IFS0bits.T2IF = 0;                /* Reset the Timer 2 interrupt flag */
    IEC0bits.T2IE = 1;                /* Enable interrupts from Timer 2 */

    INTCONSET = _INTCON_MVEC_MASK;    /* Set the interrupt controller for multi-vector mode */

   while(1);                        /* main application loop */

}

Enable Interrupt Exceptions

Next, we need to globally enable interrupts by setting the CP0 StatusIE bit.

MPLAB XC32 provides a handy __builtin() function for this task:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <xc.h>
#include
<sys/attribs.h>

/* Config bits */
#pragma config FSRSSEL = PRIORITY_7     /* Assign the SRS to level 7 priority handlers */

void __ISR (_TIMER_2_VECTOR, IPL7SRS) T2Interrupt(void)
{
   // Toggle LED LD1
   LATGINV = _LATG_LATG6_MASK;

   // Reset interrupt flag
   IFS0bits.T2IF = 0;
}

int main(void)
{
   // Initialization
   T2CONbits.TON = 0;                /* turn off Timer 2 */
    T2CONbits.TCKPS = 7;                /* pre-scale = 1:256 (T2CLKIN = 39062.5 Hz) */
    PR2 = 3906;                    /* T2 period ~ 100mS */
    TMR2 = 0;                        /* clear Timer 2 counter */

    IPC2bits.T2IP = 7;                /* Set Timer 2 interrupt priority to 7 */
    IFS0bits.T2IF = 0;                /* Reset the Timer 2 interrupt flag */
    IEC0bits.T2IE = 1;                /* Enable interrupts from Timer 2 */

    INTCONSET = _INTCON_MVEC_MASK;    /* Set the interrupt controller for multi-vector mode */

    __builtin_enable_interrupts();    /* Set the CP0 Status IE bit to turn on interrupts globally */

   while(1);                        /* main application loop */

}

Enable Peripheral

Finally, we trigger the generation of interrupt exceptions by enabling the peripheral(s):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <xc.h>
#include
<sys/attribs.h>

/* Config bits */
#pragma config FSRSSEL = PRIORITY_7     /* Assign the SRS to level 7 priority handlers */

void __ISR (_TIMER_2_VECTOR, IPL7SRS) T2Interrupt(void)
{
   // Toggle LED LD1
   LATGINV = _LATG_LATG6_MASK;

   // Reset interrupt flag
   IFS0bits.T2IF = 0;
}

int main(void)
{
   // Initialization
   T2CONbits.TON = 0;                /* turn off Timer 2 */
    T2CONbits.TCKPS = 7;                /* pre-scale = 1:256 (T2CLKIN = 39062.5 Hz) */
    PR2 = 3906;                    /* T2 period ~ 100mS */
    TMR2 = 0;                        /* clear Timer 2 counter */

    IPC2bits.T2IP = 7;                /* Set Timer 2 interrupt priority to 7 */
    IFS0bits.T2IF = 0;                /* Reset the Timer 2 interrupt flag */
    IEC0bits.T2IE = 1;                /* Enable interrupts from Timer 2 */

    INTCONSET = _INTCON_MVEC_MASK;    /* Set the interrupt controller for multi-vector mode */

    __builtin_enable_interrupts();    /* Set the CP0 Status IE bit to turn on interrupts globally */

    T2CONbits.TON = 1;                /* Enable Timer 2 peripheral */

   while(1);                        /* main application loop */

}

Back to Top


Code Example - Interrupt Usage

All preceding steps for interrupt exception usage are combined in a complete working MPLAB® X IDE project:

  • Interrupt Code Example (in C)

Back to Top

General Exception Usage

PIC32 devices also have exception vectors for non-interrupt exceptions. These exceptions are grouped into bootstrap exceptions and general exceptions.

Back to Top

Bootstrap (or "Reset") Exception

A Reset exception is any exception which occurs while bootstrap code is running (StatusBEV=1).

All Reset exceptions are vectored to 0xBFC00380.

At this location, the 32-bit toolchain places a branch instruction targeting a function named _bootstrap_exception_handler(). In the standard library, a default weak version of this function is provided, which merely causes a software Reset.

If the user application provides an implementation of _bootstrap_exception_handler(), that implementation will be used instead.

The handler must be attributed with the "nomips16" attribute [e.g., _attribute_((nomips16))], since the start-up code jumps to this function.

1
2
3
4
5
6
7
void __attribute__((nomips16)) _bootstrap_exception_handler(void)
{
   /* Mask off ExcCode field from the Cause Register */
   /* Disable interrupts */
   /* Examine EPC and ExcCode */
   /* Provide a useful indication of the Exception as well as a recovery mechanism */
}        

Back to Top

General Exception

A general exception is any non-interrupt exception that occurs during program execution outside of bootstrap code (StatusBEV=0).

General exceptions are vectored to Ebase + 0x180.

At this location, the 32-bit toolchain places a branch instruction targeting a function named _general_exception_context(). The provided implementation of this function saves context, calls an application handler function (_general_exception_handler()), restores context, and performs a return from the exception instruction.

A weak default implementation of _general_exception_handler() is provided in the standard library which merely causes a software Reset.

If the user application provides an implementation of _general_exception_handler(), that implementation will be used instead.

The handler must be attributed with the "nomips16" attribute [e.g., _attribute_((nomips16))] since the start-up code jumps to this function.

1
2
3
4
5
6
7
void __attribute__((nomips16))_general_exception_handler(void)
{
   /* Mask off ExcCode field from the Cause Register */
   /* Disable interrupts */
   /* Examine EPC and ExcCode */
   /* Provide a useful indication of the Exception as well as a recovery mechanism */
}        

Back to Top

Code Example - General Exception Usage

The following MPLAB X IDE project provides a simple example of general exception setup and usage :

Back to Top