Interrupts are an extremely important feature of a processor; it allows for seamless communication between the processor and peripherals. Being a more efficient alternative to polling, interrupts save a lot of processor time by avoiding blocking functions and allowing the peripheral to directly interrupt the processor, resulting in a system that is more responsive to the user’s inputs. This report outlines the fundamentals to interrupts, how the processor handles them and how it goes back to its original process. The report will also cover the interrupt handling mechanism of an STM32 MCU and a basic interrupt controller implementation to the simple CPU that was created during this term’s lab sessions.
Interrupts can fundamentally be thought of as a function call which is triggered by an external electrical signal, as opposed to a line of code. An interrupt request is a signal sent by a peripheral, such as a keyboard when a key is pressed, through an interrupt request line (IRQ). Even the simplest processor has one or more of these interrupt request lines, which allows the connected peripherals to interrupt the normal flow of execution of the processor. An interrupt is first received by the interrupt controller, this detects, and masks unwanted interrupts.
When the IRQ is pulled high, the processor stores the next address from the program counter, then branches to a predetermined location in memory called the interrupt vector, this location is normally fixed by hardware and the code located at the interrupt vector is known as the interrupt handler, and this will branch the processor to the interrupt service routine (ISR) which is most appropriate to the peripheral that called the interrupt. In simple processors where there’s only one, or a few peripherals of the same type, there’s only one ISR, whereas, in more powerful processors, there can be hundreds. Several interrupts can be handled by a look-up table in the interrupt vector, where the interrupt sources and their respective interrupt service routines’ memory addresses are mapped, not only this, but the priority of each interrupt is based on the order set in the vector table.
Figure 2: A visual representation of nested ISRs
Some very basic processors, which do not support nesting, disables all incoming interrupts when one is already running as they are only capable of servicing one interrupt at a time. In this case, some peripheral’s interrupt requests may be missed by the processor. However, once the ISR completes and the processor branches back to the main loop, it enables interrupts, allowing one to come in and be processed. More powerful processors which support nesting allows for incoming interrupts to be processed while handling an existing interrupt. Figure 1 shows a visual representation of nested ISRs.
When multiple interrupts are received, the processor handles them in a hierarchical way, where the interrupt with the highest priority is executed first, pausing the main loop, or a lower-priority ISR. When the first interrupt request is received, “Task 4” in the main loop is paused and the processor branches to “ISR Task”, once “ISR Task” finishes, the processor returns to the main loop where it resumes “Task 4” and carries on completing more tasks until another interrupt request is received. While handling an interrupt, another one of higher priority arrives, the interrupt “ISR Task 1” is paused to execute the new interrupt “Nested ISR Task”, and after that finishes, the processor branches back to “ISR Task1”. In essence, the processor can nest a higher priority interrupt on top of a lower priority one when it arrives. The processes in the diagram which are filled in grey shows that they are either paused or resumed. The tasks are a set of instructions.
Enabling and disabling the interrupts mechanism is done through software within a processor. The interrupt enable (IE) is a bit-addressable register which stores a bit which specifies the enablement state of each type of interrupts.
The STM32 Nucleo-64 microcontroller unit, featuring the ARM Cortex-M4 processor handles all interrupts with the help of the Nested Vectored Interrupt Controller (NVIC), this device is closely connected to the processor core and provides configurable interrupt handling functionalities at a very low latency, furthermore, the NVIC also has various power management controls and capabilities, allowing for a more efficient microprocessor unit. The NVIC allows for handling up to 240 interrupts with 256 configurable levels of priority. The STM32F303RE Nucleo-64 MCU supports up to 73 maskable interrupt channels and 16 priority levels. The NVIC can handle saving the processor’s state, passing the address of the interrupt vector table so that the processor can branch to the appropriate ISR, and can also handle late arriving higher priority interrupts with the use of interrupt nesting. The NVIC handles all of this with very low latency, this is possible due to its multiple added features such as its proximity to the processor core, hardware stacking of registers and the ability to suspend the load-multiple and store-multiple register operations.
Address | Name | Description |
0xE000E100 - 0xE000E11C | NVIC_ISER0 - NVIC_ISER7 | Interrupt Set-enable Registers: These registers can enable different types of interrupts and can also show which interrupts are enabled. |
0XE000E180 - 0xE000E19C | NVIC_ICER0 - NVIC_ICER7 | Interrupt Clear-enable Registers: These registers can disable different types of interrupts and can also show which interrupts are disabled. |
0XE000E200 - 0xE000E21C | NVIC_ISPR0 - NVIC_ISPR7 | Interrupt Set-pending Registers: These registers can force interrupts into their pending state and can also show which interrupts are pending. Interrupts in the pending state are not acted on by the NVIC. |
0XE000E280 - 0xE000E29C | NVIC_ICPR0 - NVIC_ICPR7 | Interrupt Clear-pending Registers: These registers can remove the pending state from interrupts and can also show which interrupts are pending. |
0xE000E300 - 0xE000E31C | NVIC_IABR0 - NVIC_IABR7 | Interrupt Active Bit Registers: These registers can indicate which interrupts are active. |
0xE000E400 - 0xE000E4EF | NVIC_IPR0 - NVIC_IPR59 | Interrupt Priority Registers: These registers allow for configurable interrupt priorities and can also show what priority each interrupt has. |
0xE000EF00 | STIR | Software Trigger Interrupt Register: This register allows software to trigger an interrupt by writing to it. |
Table 1: NVIC Registers
The Cortex Microcontroller Software Interface Standard (CMSIS) allows access to various registers within the NVIC via code, allowing the programmer to configure various aspects of the interrupt handing. These registers are listed and described in Table 1.
Setting the Interrupt Set-enable Registers can enable different types of interrupt. When an interrupt is sent by a peripheral, the signal is received by the NVIC. Provided that the interrupt is enabled, and the priority is set to one that’s higher than the current executing process, the NVIC allows the processor to finish the current instruction and increment the program counter, after that, the NVIC pushes registers, including the program counter (PC), link register (LR) and the Interrupt Program Status Register (IPSR) on to a stack. The LR is set to the value relating to the ISR which is being run, and the IPSR is set to the exception number of the interrupt which is being processed. The NVIC then loads the PC with the address of the ISR so that the processor can handle the peripheral’s interrupt. This process is known as the “context switch” and is performed in hardware. Interrupts can be disabled by setting the Interrupt Clear-enable Registers, when an incoming interrupt request is disabled, the NVIC will not act on it, but will set this register as “pending” by writing to the Interrupt Set-pending Registers. The NVIC postpones pending interrupt requests until a later time and will clear it as soon as the Interrupt Clear-pending Registers are written to when the software clears the pending interrupts. Pending interrupts can only be cleared via software, the NVIC cannot clear these tasks, but the pending state is automatically cleared when the processor enters the ISR. When a lower priority interrupt is received, the NVIC will set this interrupt as pending, and will execute it as soon as the currently running higher priority interrupt has finished. Once all the interrupt requests have been handled, the NVIC will return the processor back to the main program by popping the stack back to their registers, each register and the program counter will have the same values as what they had before the interrupts, allowing the processor to branch back to the main program (foreground thread) from the ISR (background thread) and carry on executing seamlessly.
Figure 2: Design of a basic interrupt controller
In this design, the padding bit of the control word, bit 0 (LSB), acts as the release signal. When there is not interrupt requests, the IRQ is low, the counter increments as usual, the output of the counter is sent to the input of the adder, which adds 00001b and outputs the result to the register’s input. The counter’s output is also sent to the address input of the ROM, which then retrieves and outputs the data to the control word output. The control word output is connected to the Datapath’s control word input. When the IRQ pulses high, the counter’s output plus 1 is sent to the input of the register, the register’s enable in high therefore, the input is sent to the output and is stored. The multiplexor selects input 1 (I1) which holds the address of the interrupt vector (10000b), this is then outputted from the MUX into the input of the counter. The load input of the counter pulses high, therefore, the counter loads the value of the interrupt vector and carries on incrementing.
Figure 3: Modified control word
The interrupt service routine can be released by either pulsing a high signal through the release line, or by setting bit 0 of the control word high. When the release is high, the stored value of the register, which is the previous program counter value plus 1, is outputted. This address value is sent to the I0 pin and as the IRQ line is low, the MUX outputs I0. The address is sent to the input of the counter, the load input is also pulsed high, therefore the counter loads this address and starts incrementing, carrying on from where it left off.
Figure 4: Mealy FSM representation of interrupt controller
This process is also described in a Mealy FSM representation shown in Figure 4. The FSM shown has no output representation, but has three inputs, IRQ, release (REL) and reset (RES). S0 is the reset state where CPU processes foreground thread, S1 is the state where the PC stores the next address and S2 is the CPU branching to the ISR.
Figure 5: Timing and process diagram of interrupt controller
Figure 5 shows the timing diagram of the interrupt controller. The flow of actions is the same as that described above. Release is effective after 1 clock cycle, whereas reset and IRQ is effective on the next.