IRQ Handler table을 RAM에 올리기

STM32CubeMX에서 기본적으로 생성되는 코드는 Flash 메모리에 IRQ Handler들을 올립니다. 그래서 Interrupt가 발생하게 되면 Flash에 올라가 있는 IRQ Handler를 호출하여 해당 Interrupt를 수행하게 됩니다.

 

간혹 Application이 Flash가 아닌 RAM에서 실행되어야 하는 경우가 생기는데요, 이러한 경우 보통 IRQ Handler를 RAM function으로 만들어 Interrupt가 발생하면 RAM 상에서 해당 Interrupt를 처리하도록 합니다.

 

여기서의 맹점은 무엇일까요?

 

바로 IRQ Handler의 Vector Table이 Flash에 있다는 것입니다. Interrupt가 발생하면 Flash의 Start 영역에 기록된 Vector Table을 참조하여 해당하는 IRQ Handler로 Jump하도록 되어 있습니다. 아래 코드는 이를 나타내는 Startup asm code 입니다.

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

  /* External Interrupts */
  .word     WWDG_IRQHandler                   /* Window WatchDog Interrupt ( wwdg1_it, wwdg2_it) */
  .word     PVD_AVD_IRQHandler                /* PVD/AVD through EXTI Line detection */
  .word     TAMP_STAMP_IRQHandler             /* Tamper and TimeStamps through the EXTI line */
  .word     RTC_WKUP_IRQHandler               /* RTC Wakeup through the EXTI line */
  .word     FLASH_IRQHandler                  /* FLASH                        */
  .word     RCC_IRQHandler                    /* RCC                          */
  .word     EXTI0_IRQHandler                  /* EXTI Line0                   */
  .word     EXTI1_IRQHandler                  /* EXTI Line1                   */
  .word     EXTI2_IRQHandler                  /* EXTI Line2                   */
  .word     EXTI3_IRQHandler                  /* EXTI Line3                   */
  .word     EXTI4_IRQHandler                  /* EXTI Line4                   */
  .word     DMA1_Stream0_IRQHandler           /* DMA1 Stream 0                */
  .word     DMA1_Stream1_IRQHandler           /* DMA1 Stream 1                */
  .word     DMA1_Stream2_IRQHandler           /* DMA1 Stream 2                */
  .word     DMA1_Stream3_IRQHandler           /* DMA1 Stream 3                */
  .word     DMA1_Stream4_IRQHandler           /* DMA1 Stream 4                */
  .word     DMA1_Stream5_IRQHandler           /* DMA1 Stream 5                */
  .word     DMA1_Stream6_IRQHandler           /* DMA1 Stream 6                */
.
.
.
.
 

 

따라서 Interrupt 발생시 완전하게 Flash Access를 막으려면 Vector Table의 위치도 RAM으로 옮겨야 합니다.

 

다행히도 ARM-Cortex M시리즈는 Vector Table의 위치를 유동적으로 할 수 있게 아래와 같은 레지스터를 제공하고 있습니다(M0 Core는 지원하지 않을 수 있으니 확인 필요).

 

VTOR의 초기값은 0x00000000 이며 대부분의 STM32의 0x00000000은 Flash Memory Address 0x8000000의 Alias 값입니다.

 

우리는 이 값은 RAM의 IRQ Handler위치로 변경하여 Interrupt Vector Table을 변경할 수 있습니다. 우선은 VTOR의 값을 변경하기 전에 Flash에 적혀있는 Vector Table의 내용을 RAM에 Copy 해야합니다.

uint32_t irq_handlers[INTERRUPT_COUNT];
memcpy(irq_handlers, (uint32_t*)SCB->VTOR, sizeof(uint32_t) * VECTORTABLE_SIZE);
 

그런 다음 VTOR을 RAM address로 변경하면됩니다.

SCB->VTOR = (uint32_t)irq_handlers;
 

다만 안전한 코딩을 위해 몇가지 반드시 주의해야 할 점이 있습니다.

 

우선은 IRQ Handler의 주소를 변경하는 것이기 때문에 반드시 interrupt 를 disable해야 하며 변경이 완료되면 다시 enable 해줘야 합니다. 필요한 경우 배리어(Barrier)를 추가 해도 좋습니다.

 

"The table alignment requirements mean that bits [8:0] of the table offset are always zero."

 

두번째는 위의 VTOR 레지스터의 설명처럼 RAM에 위치한 Vector table의 address를 alignment 해줘야 합니다. 이 alignment의 범위도 Core 마다 다르니 해당 Device의 Programming manual을 확인하여야만 합니다.

 

이를 고려한 전체 코드는 아래와 같습니다.

#define INTERRUPT_COUNT            (100) /* MCU의 Interrupt IRQ의 총 개수 MCU 마다 다름 */
#define INTERRUPT_ADDR_ALIGNMENT   (512)
uint32_t irq_handlers[INTERRUPT_COUNT + 1] __attribute__((aligned (INTERRUPT_ADDR_ALIGNMENT)));

void relocate_vector_table(void)
{
  __disable_irq();
  memcpy(irq_handlers, (uint32_t*)SCB->VTOR, sizeof(uint32_t) * INTERRUPT_COUNT);
  SCB->VTOR = (uint32_t)irq_handlers;
  __enable_irq();
}
 

위의 코드중 __attribute__((aligned (INTERRUPT_ADDR_ALIGNMENT))는 해당 변수의 시작 주소를 512(0x200) 의 배수 만큼 위치하도록 하는 Complier 지시어입니다. 따라서 Interrupt handler의 개수가 많을 경우에는 512(0x200) 의 제곱 단위로 늘려주시면 됩니다.

 

아시다시피 위의 함수는 main.c의 초기에 호출하는 것이 좋습니다.

 

참조 : https://www.keil.com/pack/doc/CMSIS/Core/html/using_VTOR_pg.html