9. Exception Model in ARM Cortex Mx

This section covers Chapter 2.3 "Exception Model" from the ARM Cortex-M4 Generic User Guide.

1. Types of Exceptions

Introduction

In ARM Cortex Mx processors, exceptions serve as a critical mechanism for handling abnormal conditions and external events. Understanding the types and functionalities of exceptions is vital for any developer working on this platform.

System Exceptions

Definition

System exceptions are built into the ARM Cortex Mx processor. They are typically used for handling core events, such as faults and system-level operations.

Number of System Exceptions

There are 15 system exceptions in the Cortex Mx series. Only 9 are implemented. The other 6 are reserved for future use.

Exception Number Exception Type Priority Brief Description
1 Reset -3, highest Triggered on power up or warm reset; restarts execution from a specific address. Always enabled by default.
2 Non Maskable Interrupt (NMI) -2 Highest priority after Reset; cannot be masked or preempted except by Reset.
3 Hard Fault -1 Occurs due to errors during exception processing or when no other mechanism can handle the exception.
4 Memory Management Fault (MemManage) Configurable Triggered by memory protection related faults, including Execute Never (XN) regions.
5 Bus Fault Configurable Occurs due to memory related faults in instruction or data transactions.
6 Usage Fault Configurable Triggered by faults related to instruction execution, like undefined instructions or illegal access.
11 SVCall Configurable Triggered by the SVC instruction; used for OS kernel functions and device drivers.
14 PendSV Configurable Used for system-level services like context switching in an OS environment.
15 SysTick Configurable Generated by the system timer reaching zero or by software; used as system tick in an OS.
  • The lower the priority number, the higher the priority level.
  • Exception numbers 7-10 and 12-13 are reserved for future use.
  • Exception numbers 16 and onward (like 17, 18, 19, etc.) each correspond to different external interrupt exceptions. For example, exception number 16 corresponds to IRQ0, exception number 17 corresponds to IRQ1, and so on.

Characteristics

  • Fixed priority
  • Cannot be disabled
  • Automatically triggered by the system
  • Controlled by the System Control Block (SCB)

Interrupt Exceptions (IRQs)

Definition

Interrupt exceptions (also known as Interrupt Requests, or IRQs) are externally triggered events that alert the processor to handle specific tasks. They are often configurable and used for device-specific operations.

Number of Interrupt Exceptions

There are 240 interrupt exceptions available in the Cortex Mx series, although the exact number can vary depending on the specific Cortex Mx processor being used as well as the vendor. For example, the STM32F446 has 82 interrupt exceptions, while the TI TM4C123GH6PM, which uses the same ARM Cortex-M4, has 74 interrupt exceptions.

Common Sources

  • GPIO pins
  • Timers
  • Communication interfaces like UART, SPI, I2C
  • Controlled by the Nested Vectored Interrupt Controller (NVIC)

Characteristics

  • Priority levels are configurable
  • Can be enabled or disabled
  • Triggered by external events or peripherals

2. Exception Handlers

Introduction

Exception handlers are specialized routines that are executed when a specific type of exception occurs. In the context of ARM Cortex-M processors, these handlers are crucial for managing both system and interrupt exceptions efficiently. Exception handlers offer a way to respond to events such as illegal memory access, undefined instructions, and external interrupts like button presses or sensor data changes.

Types of Handlers

Interrupt Service Routines (ISRs)

  • Definition: ISRs are responsible for handling external interrupts, such as those triggered by peripherals (timers, sensors, etc.).
  • Characteristics: ISRs are usually short and fast to ensure minimal disruption to the main program flow. They can have configurable priorities.

Fault Handlers

  • Definition: Fault handlers take care of various types of faults in the system, such as Memory Management Faults, Bus Faults, and Usage Faults.
  • Characteristics: These handlers are built into the system and have fixed priorities. Their primary role is to either recover from a fault or provide detailed fault information for debugging.

System Handlers

  • Definition: System handlers manage system-level operations and exceptions like Reset, NMI (Non-Maskable Interrupt), and SysTick (System Tick Timer).
  • Characteristics: Like fault handlers, system handlers are built into the system with fixed priorities. They often perform tasks that are critical for the functioning of the system, such as system initialization and timing.

Purpose of Exception Handlers

The main goal of exception handlers is to provide a controlled environment where the system can either recover from an error or execute specific actions based on external triggers. For example, a Memory Management Fault handler could release erroneously allocated memory, while an external interrupt handler might read data from a sensor.

Structure

In ARM Cortex-M processors, exception handlers are typically defined in the startup code and are part of the vector table. The vector table contains the initial stack pointer value and addresses for all exception handlers. When an exception occurs, the processor looks up the corresponding address in the vector table and jumps to that location to execute the handler code.

Configurability

One of the strengths of the Cortex-M architecture is the ability to configure the priority levels for most types of exceptions, especially interrupt exceptions. This allows developers to dictate which handlers should be executed first in case multiple exceptions occur simultaneously.


3. Vector Table

Introduction

The Vector Table is a crucial data structure in ARM Cortex-M4 processors. It serves as an array of pointers to the entry points of exception and interrupt handlers. Upon encountering an exception or interrupt, the processor consults the Vector Table to determine the appropriate function to execute.

Structure

The Vector Table starts with entries for system exceptions, followed by entries for IRQs (Interrupt Requests). In the Cortex-M4, the table has a default location at the start of the code memory space (0x00000000), but its location can be modified.

Layout

  1. Reset Vector (0x0000_0004)
  2. NMI Handler (0x0000_0008)
  3. Hard Fault Handler (0x0000_000C)
  4. Memory Management Fault Handler (0x0000_0010) ... N. IRQ(N-13) Handler

Please refer to the Reference Manual for STM32F446 for the exact layout of the Vector Table.

vector_table

You can also take a look at startup_stm32f446retx.s to see the Vector Table for the STM32F446.

/******************************************************************************
*
* The STM32F446RETx vector table.  Note that the proper constructs
* must be placed on this to ensure that it ends up at physical address
* 0x0000.0000.
*
******************************************************************************/
  .section .isr_vector,"a",%progbits
  .type g_pfnVectors, %object
  .size g_pfnVectors, .-g_pfnVectors

g_pfnVectors:
  .word _estack
  .word Reset_Handler
  .word NMI_Handler
  .word HardFault_Handler
  .word MemManage_Handler
  .word BusFault_Handler
  .word UsageFault_Handler
  .word 0
  .word 0
  .word 0
  .word 0
  .word SVC_Handler
  .word DebugMon_Handler
  .word 0
  .word PendSV_Handler
  .word SysTick_Handler
  .word WWDG_IRQHandler                         /* Window Watchdog interrupt                                          */
  .word PVD_IRQHandler                          /* PVD through EXTI line detection interrupt                          */
  .word TAMP_STAMP_IRQHandler                   /* Tamper and TimeStamp interrupts through the EXTI line              */
  ...

Relocation

You can relocate the Vector Table to a different part of memory, which is often necessary when using a bootloader. The Vector Table Offset Register (VTOR) allows you to specify the new location.

Priority Settings

Although system exceptions have fixed priorities, you can set the priority levels for IRQs using the Nested Vectored Interrupt Controller (NVIC). The NVIC allows you to configure the priority of each IRQ and enable or disable them individually.


System Control Block (SCB) Registers

The System Control Block (SCB) is a specific component in ARM Cortex-M microcontrollers that provides system implementation information and system control, including configuration, control, and reporting of system exceptions. It's essentially a set of special registers that give you access to various system-level features and configurations of the processor (via the Private Peripheral Bus, or PPB).

When you're programming, you'll typically interact with the SCB through its register map. The SCB is exposed as a C struct in many software libraries (often included through CMSIS, the Cortex Microcontroller Software Interface Standard), allowing you to easily change system settings from your code.

In C code, SCB usually is a pointer to a struct that has fields corresponding to the various system control registers. So when you see syntax like SCB->AIRCR or SCB->ICSR, it's a way to access those specific registers to read or modify their values.

For example, if you're working in C, you might see code like this to configure the priority grouping of interrupts:

#include "core_cm4.h"  // Include appropriate CMSIS header for your MCU

// Set priority grouping
SCB->AIRCR = (SCB->AIRCR & ~SCB_AIRCR_PRIGROUP_Msk) | (some_value << SCB_AIRCR_PRIGROUP_Pos);

Here, SCB->AIRCR refers to the Application Interrupt and Reset Control Register in the System Control Block. This line of code is setting its value to configure interrupt priorities.

  1. SCB->AIRCR (Application Interrupt and Reset Control Register)

    • Controls the priority grouping of interrupts.
    • Provides a system reset request bit.
  2. SCB->ICSR (Interrupt Control and State Register)

    • Holds the interrupt number of the highest-priority pending enabled interrupt.
    • Allows you to set a pending interrupt and clear a pending interrupt.
  3. SCB->SHCSR (System Handler Control and State Register)

    • Used to enable or disable system exceptions like Memory Management Fault, Bus Fault, and Usage Fault.
  4. SCB->CCR (Configuration Control Register)

    • Controls various options such as stack alignment, enabling or disabling of fault traps, and so on.

Exception-Specific Registers

  1. SCB->HFSR (HardFault Status Register)

    • Provides information about the cause of a HardFault.
  2. SCB->CFSR (Configurable Fault Status Register)

    • A combination of three sub-registers (MMFSR, BFSR, UFSR) that provide detailed information about the Memory Management, Bus, and Usage Faults.
  3. SCB->DFSR (Debug Fault Status Register)

    • Contains flags related to debug events.
  4. SCB->MMFAR (Memory Management Fault Address Register)

    • Holds the address that caused a memory management fault.
  5. SCB->BFAR (Bus Fault Address Register)

    • Holds the address that caused a bus fault.
  6. SCB->AFSR (Auxiliary Fault Status Register)

    • Provides additional fault status information that might be required by the application.

Further Reading

For more information on the SCB, see Section 4.3 of the ARM Cortex-M4 Generic User Guide:

scb


4. Nested Vectored Interrupt Controller (NVIC)

While the System Control Block (SCB) is used for configuring system exceptions like hard faults, NMIs, and others, control of IRQs is generally through the Nested Vectored Interrupt Controller (NVIC). NVIC is an integral part of the Cortex-M architecture, designed specifically for managing IRQs, also known as interrupt exceptions. It provides a flexible and efficient mechanism for handling interrupts, offering features such as interrupt masking, priority setting, and preemption.

nvic_in_processor

  • Note: As shown in the figure above, the NVIC sits between the processor core and the "Optional WIC" (which stands for "Optional Wakeup Interrupt Controller"). The WIC is a peripheral that allows the processor to wake up from low-power modes. It's not relevant to the NVIC, so we won't discuss it here.

4.1 Features of NVIC

The NVIC allows for:

  1. Interrupt Enable/Disable: You can selectively enable or disable specific interrupts.
  2. Priority Level Configuration: You can assign different priority levels to individual interrupts.
  3. Interrupt Status: NVIC provides a way to check the active status of interrupt lines.
  4. Interrupt Clearing: You can clear the pending status of an interrupt to acknowledge it.

4.2 NVIC Registers

Control of the NVIC is achieved through a series of registers. Some of the key registers include:

  • NVIC->ISER: Interrupt Set-Enable Register, used to enable specific interrupts.
  • NVIC->ICER: Interrupt Clear-Enable Register, used to disable specific interrupts.
  • NVIC->IPR: Interrupt Priority Register, used to set the priority of an interrupt.
  • NVIC->IABR: Interrupt Active Bit Register, used to check if an interrupt is active.
  • NVIC->ICPR: Interrupt Clear-Pending Register, used to clear the pending status of an interrupt.

4.3 Enabling and Disabling Interrupts

To enable an interrupt, you would typically set the appropriate bit in the NVIC->ISER register. To disable an interrupt, you would clear the corresponding bit in the NVIC->ICER register.

4.4 Setting Priority Levels

Priority levels for interrupts are configured via the NVIC->IPR register. Each interrupt source can be assigned a priority level, and these levels can be used to manage preemptive interrupt handling.

4.5 Interrupt Handling and Preemption

The NVIC supports nested interrupt handling, allowing higher-priority interrupts to preempt lower-priority ones. This is crucial for real-time systems where some operations are time-sensitive and must be executed immediately.


5. Steps for Working with Interrupts

Working with interrupts in ARM Cortex-M involves several key steps, from configuration to handling. Here's a general guide:

5.1 Initialization Steps

  1. Identify the IRQ Number: Determine the IRQ number for the interrupt you're interested in.

  2. Configure the Peripheral: It's not a job of the processor to raise an interrupt, so you need to configure the peripheral to do so. Set up the peripheral to generate an interrupt under specific conditions. For instance, if you're using a timer, you'd set its match value, mode, etc.

  3. Set Interrupt Priority: Use the IRQ number to set the interrupt priority in the NVIC. This is often done using functions like NVIC_SetPriority(). If you don't set the priority, it will be set to the default value.

  4. Enable Interrupt in NVIC: Again, use the IRQ number to enable the interrupt in the NVIC. This is usually done via functions like NVIC_EnableIRQ().

5.2 Define the Interrupt Service Routine (ISR)

  1. Write the ISR: Implement the function that will be called when the interrupt is triggered. This function should be named in a way that corresponds to the IRQ handler in the vector table.

  2. ISR Attributes: Make sure the function has the right attributes to be used as an ISR. For example, in some environments, you might declare the function with __attribute__((interrupt)).

5.3 Inside the ISR

  1. Read the Interrupt Flag: Usually, the first step inside an ISR is to read a flag to confirm that the interrupt was indeed triggered by the expected event.

  2. Clear the Interrupt Flag: To acknowledge the interrupt, clear the interrupt flag in the peripheral. Otherwise, the interrupt will continue to be triggered.

  3. Perform the Task: Execute whatever task needs to be performed. Keep this as short as possible to allow other interrupts to be serviced.

  4. Optional: Disable/Re-enable Interrupts: If you need to prevent this interrupt from being called again before certain conditions are met, disable it at the start of the ISR and re-enable it before exiting.

5.4 Clean Up

  1. Context Switch: If you're using an OS, you might need to perform a context switch at the end of the ISR.

  2. Exit: The ISR usually ends with a return statement, which leads to a return from interrupt instruction being executed, restoring the processor state and resuming normal execution.


6. Exercise: Enabling and Pending of USART3 Interrupt

If you check the Vector Table in the STM32F446 Reference Manual, you'll see that USART3 is IRQ39 (with a priority of 46):

usart_vector_table

Here we will write a program to manually trigger the USART3 interrupt. This is done by setting the Interrupt Set-Pending Register (ISPR) bit corresponding to the USART3 interrupt. When an interrupt is pending, it's like telling the processor, "Hey, you've got something to handle as soon as you can." This is done by setting a specific bit in the ISPR (Interrupt Set-Pending Register).

Pending an interrupt manually is not the typical way interrupts are used. Normally, hardware peripherals or external events set the pending bit automatically. However, manually pending an interrupt can be useful for testing or simulating an event.

#include <stdint.h>
#include <stdio.h>

#define USART3_IRQNO 39

int main(void)
{
    //1. Manually pend the pending bit for the USART3 IRQ number in NVIC
    uint32_t *pISPR1 = (uint32_t*)0XE000E204;
    *pISPR1 |= ( 1 << (USART3_IRQNO % 32));

    //2. Enable the USART3 IRQ number in NVIC
    uint32_t *pISER1 = (uint32_t*)0xE000E104;
    *pISER1 |= ( 1 << (USART3_IRQNO % 32));

    for(;;);
}

//USART3 ISR
void USART3_IRQHandler(void)
{
    printf("in USART3 isr\n");
}
  • The operation % 32 is a modulo operation that wraps the IRQ number back around to 0 after reaching 31. This is done because each ISER (Interrupt Set-Enable Register) and ISPR (Interrupt Set-Pending Register) handles 32 interrupts. So, IRQ numbers 0-31 would be managed by ISER0 and ISPR0, IRQ numbers 32-63 would be managed by ISER1 and ISPR1, and so on.

    When you get to an IRQ number that is greater than 31, the % 32 operation ensures that you are setting the correct bit in the next ISER or ISPR register. For example, USART3 with IRQ number 39 would actually map to the 8th bit (39 % 32 = 7, and counting starts from 0) in the ISER1 and ISPR1 registers.

  • The bit in the ISPR will be cleared once the interrupt service routine (ISR) has been executed. This is typically done automatically by the hardware, either at the start or at the end of the ISR, depending on the architecture and specific peripheral settings.

  • As for the ISER (Interrupt Set-Enable Register), once a bit is set (i.e., the interrupt is enabled), it stays set until you manually clear it or a system reset occurs. Clearing an ISER bit would disable that particular interrupt, and it won't be serviced until the bit is set again.

We can take a look at the SFRs (Special Function Registers) window in the debugger to see the values of the ISPR and ISER registers after the program has been run:

sfr_window