Introduction

A common question is whether C28x interrupts can be nested. This article explains how interrupt nesting can be achieved with simple changes to the interrupt service routine (ISR) code.

This article assumes the reader is already familiar with the following:

  • C28x PIE module: control registers, vector table, PIE groups

  • C28x interrupt control registers: IER, IFR, INTM in particular.

For more information on these topics please refer to the following:

Interrupt Prioritization

Hardware Prioritization

Interrupts are automatically prioritized by the C28x hardware. Prioritization for all interrupts can be found in the System Control guide specific to the particular device family. The following table shows the priority for multiplexed interrupts. Group 1, which corresponds to CPU INT1, has the highest priority. Within each group there are 8 interrupts with INTx.1 being the highest priority and INTx.8 having the lowest.

Table 1 Hardware Interrupt Priority

Priority

Group

Interrupt

Highest

Group 1

INT1.1

Group 1

INT1.2

Group 1

INT1.8

Group 2

INT2.1

Group 2

INT2.2

Group 12

INT12.1

Group 12

INT12.2

Lowest

Group 12

INT12.8

PIE Interrupt Organization

The PIE block is organized such that the interrupts are in a logical order. Interrupts that typically require higher priority, are organized higher up in the table and will thus be serviced with a higher priority by default.

The interrupts in a 28x system can be categorized as follows (ordered highest to lowest priority):

  • Non-Periodic, Fast Response

    These are interrupts that can happen at any time and when they occur, they must be serviced as quickly as possible. Typically these interrupts monitor an external event.

    Such interrupts are allocated to the first few interrupts within PIE Group 1 and PIE Group 2. This position gives them the highest priority within the PIE group. In addition, Group 1 is multiplexed into the CPU interrupt INT1. CPU INT1 has the highest hardware priority. PIE Group 2 is multiplexed into the CPU INT2 which is the 2nd highest hardware priority.

  • Periodic, Fast Response

    These interrupts occur at a known period, and when they do occur, they must be serviced as quickly as possible to minimize latency. The A/D converter is one good example of this. The A/D sample must be processed with minimum latency. Such interrupts are allocated to the group 1 in the PIE table. Group 1 is multiplexed into the CPU INT1. CPU INT1 has the highest hardware priority.

  • Periodic

    These interrupts occur at a known period and must be serviced before the next interrupt. Some of the PWM interrupts are an example of this. Many of the registers are shadowed, so the user has the full period to update the register values. Such interrupts are mapped to group 2 - group 5. These groups are multiplexed into CPU INT3 to INT5 (the ePWM, eCAP and eQEP), which are the next lowest hardware priority.

  • Periodic, Buffered

    These interrupts occur at periodic events, but are buffered and hence the processor need only service such interrupts when the buffers are ready to filled/emptied. All of the serial ports (SCI / SPI / I2C / CAN) either have FIFO’s or multiple mailboxes such that the CPU has plenty of time to respond to the events without fear of losing data. Such interrupts are mapped to INT6, INT8, and INT9, which are the next lowest hardware priority.

C28x Interrupt Response - No Nesting (Default Behavior)

The remainder of this article describes how the C28x CPU responds to an interrupt request. It is assumed that the interrupt was enabled at the (a) peripheral level, the (b) PIE level and the (c) CPU level (IER and INTM) and now the CPU is ready to begin the interrupt service routine. :The following table shows the steps taken when an interrupt is serviced .

  • Hardware: Items are performed by the silicon itself. No action is required on the part of the software.

  • Software: Items are performed in software. When using the compiler these steps are handled by the compiler for any interrupt. If the interrupt is in assembly, then these items must be taken care of by the user.

Notice the following:

  • Interrupts are automatically disabled when an interrupt service routine begins.

  • Steps 1-3 are protected from interrupts by the hardware.

  • Step 4 disables interrupts by setting the INTM and DBGM (global interrupt mask) bits. This prevents new interrupts from being serviced during the ISR.

  • INTM and DBGM will stay set unless the software re-enables interrupts or the CPU returns from the ISR.

  • The automatic context restore includes the INTM and DBGM bits.

Table 2 C28x Interrupt Service Routine Without Nesting

Step

Hardware

Software

Notes

1

Empty the pipeline Automatic context Save

Protected from interrupts Context save is documented in C28x CPU Ref Guide (SPRU430)

2

Clear corresponding IFRx bit

Protected from interrupts

3

Clear corresponding IERx bit

Protected from interrupts

4

Set INTM/DBGM and Clear LOOP, EALLOW, IDLESTAT

INTM=1 means maskable interrupts are now disabled These bits are in ST1 which was saved on the stack in step 1.

5

Request interrupt vector from PIE

6

PC = Vector

7

Execute the ASP instruction

In C/C++ the compiler takes care of this. Makes sure the stack is even aligned INTM=1 so maskable interrupts are still disabled.

8

Manual context save (if needed)

In C/C++ the compiler takes care of this. Depends on what registers are used in the ISR.

9

Execute ISR routine Clear any required flags (ex: peripheral level) Acknowledge PIE group (PIEACK)

10

Manual context restore (if needed)

In C/C++ the compiler takes care of this

11

Execute the NASP instruction

In C/C++ the compiler takes care of this. Reverses any alignment made by ASP

12

Execute IRET instruction

Interrupt return

13

Automatic context restore

This will restore INTM to its previous state (enable interrupts). DBGM and IER

14

PC = return address

15

Continues execution

Global and Group Priority

The application software is in control of when interrupts are re-enabled within a interrupt service routine. This might be at the beginning of the routine or after some time-critical code has completed.

There are two controls the software has:

  • Global Priority: This priority can be managed by manipulating the CPU IER register. This register controls the 16 maskable CPU interrupts (INT1 - INT16).

  • Group Priority: This can be managed by manipulating the PIE block interrupt enable registers (PIEIERx). It is very important that only the PIEIERx register for the same group be changed.

CAUTION

Do not modify PIEIER registers outside of an ISR for that group. For example, PIEIER1 should only be modified within an ISR from group 1. Likewise PIEIER2 should only be modified within a group 2 ISR.

This modification should be done while the PIEACK bit for the group is still set and therefore no interrupts will be sent to the CPU while the modification is being done.

If this rule is violated, then spurious INTx.1 interrupts can be triggered. If this is required for an application, then the procedure outlined in section Peripherals Interrupts-> Disabling Interrupts under System Control chapter in device TRM must be followed to avoid these spurious interrupts

Adding Simple Software Prioritization (Nesting)

Therefore, the steps required to nest interrupts are:

Step 1: Set the global priority:
  • Modify the IER register to allow CPU interrupts with a higher user priority to be serviced.

  • Note: at this time IER has already been saved on the stack.

Step 2: Set the group priority: (optional)
  • Modify the appropriate PIEIERx register to allow group interrupts with a higher user set priority to be serviced.

  • Do NOT clear PIEIER register bits from another group other than that being serviced by this ISR. Doing so can cause erroneous interrupts to occur.

Step 3: Enable interrupts:
  • There are three steps to do this:

    1. Clear the PIEACK bits

    2. Wait at least one cycle

    3. Clear the INTM bit. Use the assembly statement asm(” CLRC INTM”); or TI examples use #define EINT asm(” CLRC INTM”)

Step 4:

Run the main part of the ISR

Step 5:

Set INTM to disable interrupts. Use asm(” SETC INTM”); or TI examples use #define DINT asm(” SETC INTM”)

Step 6:

Restore PIEIERx (optional depending on step 2)

Step 7: Return from ISR
  • This will restore INTM and IER automatically.

Example code

// // C28x ISR Code // // Enable nested interrupts // // interrupt
void EPWM1_TZINT_ISR(void)
{
        uint16_t TempPIEIER;
        TempPIEIER = PieCtrlRegs.PIEIER2.all; // Save PIEIER register for later
        IER |= 0x002;                         // Set global priority by adjusting IER
        IER &= 0x002;
        PieCtrlRegs.PIEIER2.all &= 0x0002;    // Set group priority by adjusting PIEIER2 to allow INT2.2 to interrupt current ISR
        PieCtrlRegs.PIEACK.all = 0xFFFF;      // Enable PIE interrupts
        asm("       NOP");                    // Wait one cycle
        EINT;                                 // Clear INTM to enable interrupts
        //
        // Insert ISR Code here.......
        // for now just insert a delay
        //
        for(i = 1; i <= 10; i++) {}
        //
        // Restore registers saved:
        //
        DINT;
        PieCtrlRegs.PIEIER2.all = TempPIEIER;
}

Example: Using Mask Values to Manage the Prioritization

In most cases a system will only require one or two interrupts to be nested. This is easily handled by the example shown previously. There is, however, an example provided by which covers possibilities for every single interrupt in the group. This example provides a method for managing the global and group priorities using simple mask values that are configured during compile time. This allows the priorities to be managed easily. This scheme is included in C2000Ware. Refer to the software prioritization example in the device support for your particular device.

Example Configuration

The configuration used by the example is done in the DSP280x_SWPrioritizedIsrLevels.h file. To configure the prioritization: * Assign global priority levels

INT1PL - INT16PL

These values are used to assign a priority level to each of the 16 interrupts controlled by the CPU IER register. A value of 1 is the highest priority while a value of 16 is the lowest. More then one interrupt can be assigned the same priority level. In this case the default hardware priority would determine which would be serviced first. A priority of 0 is used to indicate that the interrupt is not used.

  • Assign PIE group priority levels GxyPL (where x = PIE group number 1 - 12 and y = interrupt number 1 - 8) These values are used to assign a priority level to each of the 8 interrupts within a PIE group. A value of 1 is the highest priority while a value of 8 is the lowest. More then one interrupt can be assigned the same priority level. In this case the default hardware priority would determine which would be serviced first. A priority of 0 is used to indicate that the interrupt is not used.

The compiler will use the assigned global and group priority levels to generate mask values that can be used to change the IER and PIEIERx registers. The application uses these mask values within each ISR to specify which interrupts are allowed to be serviced within an ISR.

MASK Values Generated

The masks that are generated at compile time are: * IER mask values:

MINT1 - MINT16

The user assigned INT1PL - INT16PL values are used at compile time to calculate an IER mask for each CPU interrupt. This mask value will be used within an ISR to allowCPU interrupts with a higher priority to interrupt the current ISR and thus be serviced at a higher priority level.

  • PIEIERxy mask values: MGxy (where x = PIE group number 1 - 12 and y = interrupt number 1 - 8) The assigned group priority levels (GxyPL) are used at compile time to calculate PIEIERx masks for each PIE group. This mask value will be used within an ISR to allow interrupts within the same group that have a higher assigned priority to interrupt the current ISR and thus be serviced at a higher priority level.

Using the MASK Values

Within an interrupt service routine, the global and group priority can be changed by software to allow other interrupts to be serviced. The steps are the same as described before. The only difference is the mask values for IER and PIEIERx have been managed in the DSP28_SWPrioritizedIsrLevels.h file.

Step 1: Set the global priority
  • Modify IER to allow CPU interrupts from the same PIE group as the current ISR.

  • Modify IER to allow CPU interrupts with a higher user defined priority to be serviced.

Step 2: Set the group priority
  • Save the current PIEIERx value to a temporary register.

  • The PIEIER register is then set to allow interrupts with a higher priority within a PIE group to be serviced.

Step 3: Enable interrupts
  • This requires 3 steps:

    1. Enable all PIE interrupt groups by writing all 1’s to the PIEACK register

    2. Wait at least one cycle

    3. Enable global interrupts by clearing INTM

Step 4: Execute ISR.

Interrupts that were enabled in steps 1-3 (those with a higher software priority) will be allowed to interrupt the current ISR and thus be serviced first.

Step 5 :

Disable interrupts

Step 6:

Restore the PIEIERx register

Step 7:

Exit

// // C28x ISR Code // // Enable nested interrupts using masks defined // in the software prioritization example code // // Connected to PIEIER2_1 (use MINT2 and MG21 masks) //

if (G21PL != 0)
interrupt void EPWM1_TZINT_ISR(void) // EPWM1 Trip Zone
{
        uint16_t TempPIEIER;
        TempPIEIER = PieCtrlRegs.PIEIER2.all;
        IER |= M_INT2;
        IER &= MINT2;                         // Set "global" priority
        PieCtrlRegs.PIEIER2.all &= MG21;      // Set "group" priority
        PieCtrlRegs.PIEACK.all = 0xFFFF;      // Enable PIE interrupts
        asm("       NOP");                    // Wait one cycle
        EINT;                                 // Clear INTM to enable interrupts
        //
        // Insert ISR Code here.......
        // for now just insert a delay
        //
        for(i = 1; i <= 10; i++) {}
        //
        // Restore registers saved:
        //
        DINT;
        PieCtrlRegs.PIEIER2.all = TempPIEIER;
}