Search code examples
stm32interrupt

STM32 replace entry in vector table at runtime


I’m trying to getting get some more understanding of the internal function of the STM32 / Cortex MCUs. I am new to this topic and have little / no understanding. At the moment I try to figure out how the interrupts work.

As far as I understand now at the start of the program a vector table is loaded, which is defined in the >>startup_stm32xxxx.s<< file. When an interrupt occurs, the vector table contains an entry != NULL and the corresponding address exists, the program jumps into the according Interrupt Service Routine.

E.g.: if an interrupt for the DMA1 Channel2 occurs, the execution jumps into the function "DMA1_Channel2_IRQHandler", which is defined in the startup_stm32xxxx.s and implemented in the "stm32xxxx_it.c".

g_pfnVectors:
    .word   _estack
    .word   Reset_Handler
    .word   NMI_Handler
    .word   HardFault_Handler
    .word   MemManage_Handler
    .word   BusFault_Handler
    [...]
    .word   EXTI4_IRQHandler
    .word   DMA1_Channel1_IRQHandler
    .word   DMA1_Channel2_IRQHandler        // the ISR I’d in my example
    .word   DMA1_Channel3_IRQHandler
    [...]

the entry in this table, leads to this function in "stm32xxx_it.c":

void DMA1_Channel2_IRQHandler(void) {
  // jump into this function, when an interrupt occurs
  /* USER CODE BEGIN DMA1_Channel2_IRQn 0 */

  /* USER CODE END DMA1_Channel2_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_spi1_rx);
  /* USER CODE BEGIN DMA1_Channel2_IRQn 1 */

  /* USER CODE END DMA1_Channel2_IRQn 1 */
}

Please correct me if I am wrong.

The question is: is it possible to "bend" this callback address to another, during runtime? To stick with the example above: can I modify the vector table at runtime, so it jumps e.g. into "myDMA1_Channel2_IRQHandler" instead? If yes, are there already macros or functions available in the STM32 HAL? Are there any caveats or pitfalls I am not aware of at the moment?

What I already found out: obviously I can change / edit the "startup_stm32xxxx.s" file and change e.g. the entry for "DMA1_Channel2_IRQHandler" to a function of my choice. As long as this function is implemented correctly, this works.


Solution

  • The interrupt vector table is normally stored at the start of flash memory, and is therefore not easily modified.

    However, the ARM CPU allow the vector table to be stored anywhere (as long as it is correctly aligned), using its VTOR (Vector Table Offset Register) register.

    Therefore, you can copy the vector table from flash into RAM, and change VTOR to point to that. Then you can modify any of the vectors whenever you want.

    There's a good example of someone doing that here: Cortex-M3 realtime interrupt vector remap.

    I'd like to still use HAL, where the speed is not necessary. IMHO it would be an elegant way to turn the whole HAL thing off for certain functionality, but keep the standard for the others.

    You can also choose which drivers to use for each peripheral by opening up your .ioc file, choose the Project Manager tab at the top, and Advanced Settings on the left-hand side. This lets you choose HAL or LL drivers on a per-peripheral basis.

    You can also choose how interrupts are handled, by clicking the Pinout & Configuration tab at the top, select the System Core->NVIC peripheral, then click the code generation tab. You can choose whether or not an ISR is generated, and whether or not it calls the HAL.