Hello everyone, I am Pi Zi Heng, a serious techie. Today, I will share with you the principles of the Cortex-M interrupt vector table and its redirection methods.
Continuing from the previous article “Three Implementations of Critical Section Protection in Embedded Cortex-M Bare Metal Environment”, in embedded code design, sometimes certain special operations (such as Flash erasing under XIP or switching to low-power mode) cannot be interrupted at will, or some shared data areas cannot be accessed in an unordered manner (Task A is reading while Task B wants to write). In these cases, we can use the global interrupt switch control to achieve what is called critical section protection.
However, in some scenarios, turning off the global system interrupts is not always effective. For example, in the case of Flash erasing under XIP, if there is a background timer (such as SysTick) running in real-time, directly turning off the global interrupts during Flash erasure (which can take a long time) will cause the timer interrupt to be unresponsive, leading to timing errors in the system. Clearly, we cannot turn off the global interrupts in such cases.
To ensure that the system can still respond to timer interrupts (execute interrupt handler functions) during Flash erasure, we need to link the timer interrupt handler function and its related code to execute in RAM, and we also need to redirect the interrupt vector table to RAM. Today, I will discuss the methods for redirecting the interrupt vector table:
1. Introduction to Cortex-M Interrupt Vector Table
Friends familiar with ARM Cortex-M processors should have some understanding of the table below, which is the interrupt vector table. Each vector in the table is 4 bytes in size, and except for the 0th vector, the remaining vectors are function addresses. This table centrally stores the addresses of all system interrupt handler functions (xxxIRQHandler).

For MCUs with embedded Flash, the initial interrupt vector table is generally required to be fixed linked to the starting address of Flash, because the system always fetches the 0th (initial stack) and 1st vector (initial PC, reset function ResetHandler) from the starting address of Flash to begin executing the application code. For some MCUs that contain BootROM or do not have internal Flash, the initial interrupt vector table may be placed at other addresses in Flash, depending on the specific chip design.
Once the application program is running, if an interrupt occurs, the system will find the corresponding peripheral interrupt handler function in the interrupt vector table based on the interrupt number issued by the requesting peripheral and execute it. The Cortex-M core (except for CM0) has a dedicated VTOR register in the SCB module to control the starting address of the interrupt vector table (note that the address needs to be 128-byte aligned). After the program starts running, the user can configure the SCB->VTOR register to reset the interrupt vector table address.

2. Methods for Redirecting the Interrupt Vector Table
Now, we will take the NXP i.MXRT1170 model as an example to introduce the method for redirecting the interrupt vector table, using the example from the project located at \SDK_2.9.1_MIMXRT1170-EVK\boards\evkmimxrt1170\demo_apps\led_blinky\cm7\iar.
2.1 Files Related to the Interrupt Vector Table
In this led_blinky project, there are two files related to the interrupt vector table: one is startup_MIMXRT1176_cm7.s, which contains the definition of the interrupt vector table and the reset function ResetHandler(). From the reset function, you can see that it first resets the SCB->VTOR register.
THUMB
PUBWEAK Reset_Handler
SECTION .text:CODE:REORDER:NOROOT(2)
Reset_Handler
CPSID I ; Mask interrupts
LDR R0, =0xE000ED08 ; SCB->VTOR
LDR R1, =__vector_table ; section .intvec segment start address
STR R1, [R0]
LDR R2, [R1]
MSR MSP, R2
LDR R0, =SystemInit
BLX R0
CPSIE I ; Unmask interrupts
LDR R0, =__iar_program_start
BX R0
The value of __vector_table used in the reset function is determined by the following statement set in the MIMXRT1176xxxxx_cm7_flexspi_nor.icf linker file. Since the i.MXRT1170 does not have internal Flash, the system mapping starting address allocated for external NOR Flash (attached to the FlexSPI1 peripheral) is 0x30000000, while 0x30002000 is one of the application initial interrupt vector table addresses supported by the BootROM (indicated in the IVT boot header).
define symbol m_interrupts_start = 0x30002000;
define symbol m_interrupts_end = 0x300023FF;
define exported symbol __VECTOR_TABLE = m_interrupts_start;
place at address mem: m_interrupts_start { readonly section .intvec };
After compiling the project, you can find the final linked address of the initial interrupt vector table in the generated iled_blinky_cm7.map mapping file. For easier analysis of issues later, we will also list the address of the timer interrupt handler function:
*******************************************************************************
*** PLACEMENT SUMMARY
***
"A0": place at address 0x3000'2000 { ro section .intvec };
Section Kind Address Size Object
------- ---- ------- ---- ------
"A0": 0x400
.intvec ro code 0x3000'2000 0x400 startup_MIMXRT1176_cm7.o [1]
- 0x3000'2400 0x400
*******************************************************************************
*** ENTRY LIST
***
Entry Address Size Type Object
----- ------- ---- ---- ------
SysTick_Handler 0x3000'5767 0x10 Code Gb led_blinky.o [1]
__VECTOR_TABLE {Abs} 0x3000'2000 Data Gb <internal module="">
__Vectors 0x3000'2000 -- Gb startup_MIMXRT1176_cm7.o [1]
__Vectors_End 0x3000'2400 Data Gb startup_MIMXRT1176_cm7.o [1]
__Vectors_Size {Abs} 0x400 -- Gb startup_MIMXRT1176_cm7.o [1]
__vector_table 0x3000'2000 Data Gb startup_MIMXRT1176_cm7.o [1]
</internal>
2.2 Example of Interrupt Redirection Function
The timer interrupt handler function SysTick_Handler() linked in Flash obviously will not work, so we use the IDE feature (for IAR, it is the __ramfunc modifier) to link it to RAM (the MIMXRT1176xxxxx_cm7_flexspi_nor.icf defines the TEXT2_region: 0x0 – 0x3FFFF space to store the section .textrw segment), recompile the project, and check the mapping file to see that the newly allocated address is 0x1.
__ramfunc void SysTick_Handler(void)
{
if (g_systickCounter != 0U)
{
g_systickCounter--;
}
}
*******************************************************************************
*** ENTRY LIST
***
Entry Address Size Type Object
----- ------- ---- ---- ------
SysTick_Handler 0x1 0x14 Code Gb led_blinky.o [1]
Now, we will try to manually relocate the interrupt vector table in the code by finding a free RAM area (for example, 0x20000000 – 0x200003FF) and manually copying the contents of the interrupt vector table over. The example code is as follows. The main function calls this relocate_vector_table() function at the beginning, and after downloading the modified project to the board and running it, everything works normally, indicating that the interrupt vector table redirection operation was successful.
extern uint32_t __VECTOR_TABLE[];
void relocate_vector_table(void)
{
__disable_irq();
// Copy the initial interrupt vector table from 0x30002000 to the new address 0x20000000
memcpy((void *)0x20000000, (void *)__VECTOR_TABLE, 0x400);
// Point VTOR to 0x20000000
SCB->VTOR = 0x20000000;
__enable_irq();
}
int main(void)
{
relocate_vector_table();
// Other code
}
Thus, I have introduced the principles and redirection methods of the Cortex-M interrupt vector table. Where are the applause~~~