The Nested Vectored Interrupt Controller and Interrupt Control
As we've seen, The Nested Vectored Interrupt Controller(NVIC) is an intergated part of the Cortex-M3 processor. It is closely linked to the Cortex-M3 CPU core logic. Its control registers are accessible as memory mapped devices. Besides control reigsters and control logic cfor interrupt processing, the NVIC unit also contains control registers for the SYSTICK Timer, and debugging controls. In this chapter, we'll examine the control logic for interrupt processing. Memory Protection Unit and debugging control logic are discussed in later chapters.
The NVIC supports 1-240 external interrupt inputs(commonly known as interrupt request).
The exact number of supported interrupts is determined by the chip manufacturers when they develop their Cortex-M3 chips. In addtion, the NVIC also has a Nonmakable Interrupt(NMI) input.
The NVIC can be accessed in the System Control Space(SCS) address range, which is memory location 0xE000E000. Most of the interrupt control/status reigsters are accessible only in privileged mode, except the Software Trigger Interrupt register(STIR), which can be set up to be accessible . The interrupt contorol/Status reigsters can be accessed in word, half word, word, or byte transfer.
In addtion, a few other interrupt-masking registers are also involved in the interrupts. They are the special register and are accesssed through special registers access instructions : move sepcial register to general purpose register and move to sepcial register from general purpo
8.2 THE BASIC INTERRUPT CONFIGURATION
Each external interrupt has several registers associated with it.
In addotion, a number of other registers can also affect the interrupt processing:
Exception-masking registers(PRIMASK, FAULTMASK and BASEPRI)
Vector Table Offset register
STIR
Prioirty Group
8.2.1 Interrupt Enable and Clear Enable
The Interrupt Eable register is progmammed through two addresses. To set the enable bit, you need to write to the SETENA register address; to clear the enable bit, you need to write to the CLRENA register address. In this way, enabling or disabling an interrupt will not affect other interrupt ebale states.
8.2.2 Interrupt Set Pending and Clear Pending
If an interrupt takes place but cannot be executed immediately (for instance, if another higher-priority interrupt handler is running), it will be pended. The interrupt-pending status can be accessed through the interrupt Set Pending(SETPEND) and Interrupt Clear Pending(CLRPEND) registers.
8.2.3 Priority Levels
Each external interrupt has an associated prioirty-level register, which has a maximum width of 8bits and a minimum width of 3 bits.
Table 8.1
| Address | Name | Type | Reset Value | Description |
|---|---|---|---|---|
| 0xE00E100 | SETENA0 | R/W | 0 | Enable for external interrupt #0-31 |
| ... | ... | ... | ... | ... |
| 0xE00E180 | CLRENA | R/W | 0 | Clear Enable |
Table 8.2 Interrupt Set Pending Registers and Interrupt Clear Pending Registers
Table 8.3 Interrupt Priority Level Registers
8.2.4 Active Status
Each external interrupt has an active satues bit. When processoer starts the interrupt handler, the bit is set to 1 and cleared when the interrupt return is executed. However, during an interrupt Service Routine excution, a higer priority interrupt might occur and casuse preemption. During this period, although the processor is executing another interrupt handler, the previous interrupt is still defined as active. The active registers are 32 bit but can also be accessed using half word or byte-size transfers.

8.2.5 PRIMASK and FAULTMASK Special Register
The PRIMASK register is used to disable all exceptions except NMI and hard fault. It effecively changes the currnet priority level to 0.
void __enable_irq(); // Clear PRIMASK
void __disable_irq(); // Set PRIMASK
void __set_PRIMASK(uint32_t priMask); // Set PRIMASK to value
uint32_t __get_PRIMASK(void); // Read the PRIMASK Value
For assemblt language users, you can change the current status of PRIMASK using Change Process State(CPS) instructions :
CPSIE I; Clear PRIMASK (Enable interrupts)
CPSID I; Set PRIMASK(Disable Interrupts)
This register is also programmable using MRS and MSR instructions.
MOV R0, #1
MSR PRIMASK, R0 ; Write 1 to PRIMASK to disable all interrupts
MOV R0, #0
MSR PRIMASK, R0 l Write 0 to PRIMASK to allow interrupts
PRIMASK is useful for temporarily disabling all interrupts for critical tasks. When PRIMASK is set, if a fault takes place, the hard fault handler will be executed.
FAULTMASK is just like PRIMASK except that it changes the effective current priority level to -1, so that even the hard fault handler is blocked.
Only the NMI can be executed when FAULT-MASK is set. It can be used by fault handlers to raise its priority to -1, so that they can have access to some features for hard fault exception( more information on this is provieded in Chapter 12 ).
8.2.6 The BASEPRI Special Register
In some cases, you might want to disable interrupts only with priority lower than a certain level. In this case, you could use the BASEPRI register.
__set_BASEPRI(0x60); // Disable interrupts with priority
// 0x60-0xFF using CMSIS
8.2.7 Configuration Registers for Other Exceptions
Usage faults, memory management faults, and bus fault exceptions are enabled by the system handler
Table 8.5 The System handler Control and State Register
| Bits | Name | Type | Reset Value | Description |
|---|---|---|---|---|
| 18 | USGFAULTENA | R/W | 0 | Usage fault handler enable |
| 17 | BUSFAULTENA | R/W | 0 | Bus fault handler enable |
| 16 | MEMFAULTENA | R/W | 0 | Memory Management fault handler enable |
| 15 | SVCALLPENDED | R/W | SVC pended; SVC was started but was replaced by a higher-priority exception | |
| 14 | BUSFAULTPENDED | R/W | 0 | Bus fault pended; bus fault handler was started but was replaced by a higher-priority exception |
| 13 | MEMFAULTPENDED | R/W | 0 | Memory management fault pended; memory management fault started but was replaced by a higher priority exception |
| 12 | USGFAULTPENDED | R/W | 0 | Usage fault pended; usage fault started but was replaced by a higher priority exception |
| 11 | SYSTICKACT | R/W | 0 | Read as 1 if SYSTICK exception is active |
| 10 | PENDSVACT | R/W | 0 | Read as if PendSV exception is active |
| 8 | MONITORACT | R/W | 0 | Read as 1 debug monitor exception is active |
...
8.3 EXAMPLE PROCEDURES IN SETTING UP AN INTERRUPT
For most simple applications, the application is stored in ROM and there is no change the exception handlers, we can have the whole vector table coded in the beginning of ROM in the Code region(0x00000000). This way, the vector table offset will always be 0 and the interrupt vector is already in ROM. The only steps required to set up an interrupt will be as follows :
Set up the priority group setting. This step is optional. By default priority group setting zero only bit 0 of the priority level register is used for subpriority.
Set up the priority level of the interrupt. This step is optional. By default, all interrupts are at priority level 0.
Enable the interrupt/
Here is a simple example procedure for setting up an interrupt:
NVIC_SetPriorityGrouping(5);
NVIC_SetPriority(7, 0xC0); // Set IRQ#7
In addition, make sure that you have enough stack memory if you allow a large number of nested interrupt levels. Because exception handlers always use the Main Stack Pointer, the main stack memory should contain enough speace for the largest number of nesting interrupts.
If the interrupt handlers need to be changed at different stage of the application, we might need to relocate the vector table to Statuc Random Access Memory(SRAM), so that we can modify the exception vectors. In this case, the following extra steps would be required:
When the system boots up, the priority group register might need to be set up. By default, the priority group 0 is used(bit[7:1] of priority level is the preemption level and bit[0] is the subpriority level).
Copy the hard fault. NMI handlers and other required vector to a new vector table location in SRAM.
Set up the Vector Table Offset register to point to the new vector table.
Set up the interrupt vector for the interrupt in the new vector table.
Set up the priority level for the interrupt.
Enable the interrupt.
For example, this can be done in C programming with a CMSIS complicant device driver libarary, assume the starting address of the new vector table is defined as "NEW_VECT_TABLE" :
// HW_REG is a macro to convert address value to point
#define HW_REG(addr) (((volatile unsigned long)addr)))
#define NEW_VECT_TABLE 0x20008000 // An SRAM region for vector table.
NVIC_SetPriorityGrouping(5);
...
HW_REG((NEW_VEC_TABLE + 0x8)) = HW_REG(0x8); // copy NMI Vector
HW_REG((NEW_VECT_TABLE + 0xC)) = HW_REG(0xC); //
...
SCB->VTOR = NEW_VECT_TABLE; // Relocate vector table to SRAM
...
HW_REG(4*(7+16)) = (unsigned) IRQ#7 priority level to 0xC0
...
NVIC_EnableIRQ(7);
The program in assembly might be something like this:
LDR R0, = 0xE000ED0C ; Application interrupt and Reset Control Register