NU32v2: Interrupts

From Mech
Revision as of 05:26, 16 January 2016 by Lynch (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigationJump to search

THIS PAGE REFERS TO A PRE-RELEASE VERSION OF THE NU32 PIC32 DEVELOPMENT BOARD. FOR INFORMATION, SAMPLE CODE, AND VIDEOS RELATED TO THE PRODUCTION VERSION (2016 AND LATER), AND TO THE CORRESPONDING BOOK "EMBEDDED COMPUTING AND MECHATRONICS WITH THE PIC32 MICROCONTROLLER," VISIT THE NU32 PAGE.


Say your PIC is attending to some mundane task when an important event occurs. For example, the user has pressed a button. We want the PIC to respond immediately. To do so, we have this event generate an interrupt, or interrupt request (IRQ), which interrupts the program and sends the CPU to execute some other code, called the interrupt service routine (or ISR). Once this code has completed, the CPU returns to its original task.

Interrupts are a key concept in real-time control, and they can arise from many different events. This page provides a summary of PIC32 interrupt handling.

Overview

Interrupts can be generated by the processor core, peripherals, and external inputs. Example events include

  • a digital input changing its value,
  • information arriving on a communication port,
  • the completion of some task the PIC32 was executing in parallel with the CPU, and
  • the elapsing of a specified amount of time.

As an example, to guarantee performance in real-time control applications, we must read the sensors and calculate new control signals at a known fixed rate. For a robot arm, a common control loop frequency is 1 kHz. So we would configure one of the PIC32's counter/timers to use the peripheral bus clock as input, and choose prescaler and period register values so that the counter rolls over every 1 ms. This roll-over "event" generates the interrupt that calls our feedback control ISR. In this case, we would have to make sure that our control ISR is efficient code that always executes in (much) less than 1 ms.

Say the PIC is currently controlling the robot arm to hold steady in a particular position. It then receives new information from the user, who asks the arm to move to a new location. This new information also generates an interrupt, and the corresponding ISR reads in the information and stores it in global variables representing the desired state. These desired states are used in the feedback control ISR.

So what happens if we are in the middle of executing the control ISR and a communication interrupt is generated? Or if we are in the middle of the communication ISR and a control interrupt is generated? We must make a choice about which has higher priority. If a high priority interrupt occurs while a low priority ISR is executing, the CPU will jump to the high priority ISR, complete it, and then return to finish the low priority ISR. If a low priority interrupt occurs while a high priority ISR is executing, the low priority ISR will wait patiently until the high priority ISR is finished executing. When it is finished, the CPU jumps to the low priority ISR.

In our example, communication could be slow, taking several milliseconds to complete, and we might not have a guarantee as to the duration. To ensure the stability of the robot arm, we would probably choose the control interrupt to have higher priority than the communication interrupt.

Every time an interrupt is generated, the CPU must store its current context on the stack, so that when it returns from the ISR, it can read this information back in and continue where it left off. If interrupts are being nested (one ISR interrupts another which has interrupted another, etc.), then the PIC may run out of memory, causing a "stack overflow" and the program to crash. These errors can be very difficult to debug, so it is a good idea to ensure that any high priority ISRs execute in a known, fixed amount of time.

The address of the ISR in virtual memory is called the interrupt vector, and the PIC32 can support up to 64 unique interrupt vectors arising from up to 96 different interrupt sources. If all interrupts jump to the same ISR, the PIC32 is in "single vector mode." This is the default on reset. If each interrupt has its own ISR, the PIC32 is in "multi-vector mode."

Details

The CPU jumps to an ISR when three conditions are satisfied: (1) the interrupt has been enabled by setting a bit in the SFR IECx (Interrupt Enable Control) to 1; (2) an interrupt has been requested by setting the same bit in the SFR IFSx (Interrupt Flag Status) to 1; and (3) the priority of the interrupt, as represented in the SFR IPCy (Interrupt Priority Register), is greater than the current priority of the CPU. If the first two conditions are satisfied, but not the third, the interrupt remains pending until the CPU's priority drops lower.

The "x" in the IECx and IFSx SFRs above can be 0, 1, or 2, or (3 registers) x (32 bits) = 96 interrupt sources. The "y" in IPCy takes values {0..15}. Each of the IPCy registers contains 20 relevant bits: 5 bits encoding the priority for each of four different interrupt vectors. So we have (16 registers) x (4 priorities) = 64 priorities for the 64 interrupt vectors. The 5 bits defining the priority are further separated into 3 bits for the main priority taking values 1..7, with 7 being the highest priority (all bits equal to zero causes the interrupt to be disabled), and 2 bits for the sub-priority (taking values 0..3) to distinguish among ISRs at the same priority level. Thus we essentially have 7x4 = 28 priority levels. The mapping from each interrupt source and vector to x, y, and the bit locations are given in Chapter 7 of the Data Sheet. Note that since there are more interrupt sources than interrupt vectors, some sources share the same vector. For example, IRQs (interrupt request sources) 29, 30, and 31, each corresponding to events on I^2C communication channel 1, all share the same vector.

If two interrupts have been requested, and they have the same priority and sub-priority, their priority is determined by the "natural order priority" table given in the Data Sheet. (In this table, though, higher priority corresponds to lower numbers!)

The last thing any ISR should do is clear the interrupt flag (clear the appropriate bit of IFSx to zero), indicating that the interrupt has been serviced and the CPU is free to either return to the the program state when the ISR was called or service another interrupt that has been requested in the meantime.

When setting up an interrupt, you will set a bit in IECx to 1 indicating the interrupt is enabled (all bits are set to zero upon reset) and assign values to the associated IPCy priority bits. (These priority bits default to zero upon reset, which will keep the interrupt disabled.) As mentioned above, you will also clear the IFSx bit at the end of the ISR. You will generally never write code setting the IFSx bit to 1, however. Instead, when you set up the device that generates the interrupt (e.g., a counter/timer), you will indicate that it should set the interrupt flag upon the appropriate event.

Apart from the SFRs IECx, IFSx, and IPCy, three other SFRs are relevant to interrupts: INTCON (Interrupt Control), INTSTAT (Interrupt Status), and TPTMR (Temporal Proximity Timer). The SFRs are summarized below.

  • IECx (Interrupt Enable Control): Three 32-bit SFRs for 96 interrupt sources (x = 0, 1, or 2). A 1 enables the interrupt, a 0 disables it. See the Reference Manual for the correspondence between IRQ and {x, bit number}.
  • IFSx (Interrupt Flag Status): Three 32-bit SFRs for 96 interrupt sources (x = 0, 1, or 2). A 1 indicates an interrupt has been requested, a 0 indicates no interrupt is requested.
  • IPCy (Interrupt Priority Control): Sixteen registers, each with 5 bit priority values for 4 different interrupt vectors (64 vectors total).
  • INTCON (Interrupt Control): Determines whether the interrupt controller operates in single vector or multi-vector mode. Also determines whether the five external interrupt pins INT0..INT4 generate an interrupt on a rising edge or a falling edge. It also configures some other things we will ignore; see the Reference Manual for more details.
  • INTSTAT (Interrupt Status): Read-only: information on the address and priority level of the latest IRQ given to the CPU. (Not relevant for us.)
  • TPTMR (Temporal Proximity Timer): We can ignore this.

More details can be found in Chapter 7 of the Data Sheet and Section 8 of the Reference Manual.

Library Functions

Note: Some interrupt functions that are commonly used in Microchip's own sample code, as well as our sample code, are now listed as "legacy." You can check your distribution at pic32-libs/include/peripheral/legacy (files beginning "int").

Usually we will not directly manipulate the interrupt registers. Instead, we will use library functions, found in pic32-libs/include/peripheral, the files beginning with "int". Below are some relevant functions. In addition to these, you will need library functions for the specific peripheral to enable the appropriate interrupt bit in IECx and to set interrupt flags in IFSx. See information on the specific peripheral.

"Legacy" (but you are more likely to use these):

  • void INTEnableSystemMultiVectoredInt(void): Performs the tasks of IntEnableInterrupts() above (enables CPU for interrupts) and INTConfigureSystem(INT_SYSTEM_CONFIG_MULT_VECTOR). In pic32-libs/peripheral/int/source.
  • mT2ClearIntFlag(): Clears the interrupt flag for Timer 2. Many similar such definitions in pic32-libs/include/peripheral/legacy/int_5xx_6xx_7xx_legacy.h.

Also, in pic32-libs/include/peripheral/int.h:

  • void INTConfigureSystem(xxx): xxx could be INT_SYSTEM_CONFIG_MULT_VECTOR (the usual case) or INT_SYSTEM_CONFIG_SINGLE_VECTOR. Sets up the interrupts to be single vector or multi-vector.
  • unsigned int INTEnableInterrupts(void): Enables the CPU for interrupts and returns the status of CP0 register Status.IE bit (we can ignore it).
  • unsigned int INTDisableInterrupts(void): Disables the CPU for interrupts and returns the status of CP0 register Status.IE bit (we can ignore it).
  • void INTClearFlag(xxx): To be executed at the end of the ISR. The xxx appear to be defined in int_5xx_6xx_7xx.h, and include INT_T1 (timer 1), INT_INT0 (external interrupt 0), etc.
  • void INTSetVectorPriority(xxx,yyy): xxx is the vector (e.g., INT_EXTERNAL_0_VECTOR) declared in int_5xx_6xx_7xx.h (but since it is a memory location, it is defined elsewhere, not in C) and yyy is the priority level (e.g., INT_PRIORITY_LEVEL_4 defined in int.h, or just 4).

Sample Code

See pages on specific peripherals to see how to create interrupts with those peripherals (e.g., counter/timers).

Configuring the system to be able to handle multiple interrupts:

// first your code setting up peripherals to generate interrupts (with priorities), then...
IntEnableSystemMultiVectoredInt();

Writing an ISR:

void __ISR(_TIMER_2_VECTOR, ipl2) Timer2Handler(void){  
  // your ISR code
  mT2ClearIntFlag();  // clear the interrupt flag
}

Note that the vector names (like _TIMER_2_VECTOR) can be found in pic32-libs/include/proc/p32mx795f512l.h, and priority level can be written as ipl{1..7}. Note that this priority level must match the priority level assigned to the function/peripheral generating the interrupt. You can name your ISR routine whatever you want (here it is Timer2Handler). Macros for clearing different interrupt flags can be found in pic32-libs/include/peripheral/legacy/int_5xx_6xx_7xx_legacy.h.

Alternatives for clearing the same interrupt flag include

IFS0bits.T2IF = 0;

(see p32mx795f512l.h) and

IntClearFlag(INT_T2);

More Information