STM32 Jump Application IRQ Handler 이슈 수정

STM32에서 Bootloader를 만들어 사용하는 예제는 인터넷에 많이 있어 상세한 설명은 생락한다.

 

Bootloader는 Non-OS 방식으로 동작하고, Main(Jump) Application은 FreeRTOS로 동작할 경우, FreeRTOS의 port.c에서 실행에 필요한 시스템 설정을 하였기 때문에 Bootloader에서 Main App로 점프하기 전에 __disable_irq()만 실행하면 되었는데, Main App가 Non-OS로 동작할 경우 Bootloader에서 추가작업이 필요하다.

 

MainApp가 Non-OS인 경우 Bootloader에서 __disable_irq()를 실행할 경우 MainApp에서 Vector Table을 설정하고 __enable_irq()를 실행하지 않으면, irq handler가 동작하지 않아 SysTick도 동작하지 않아 MainApp가 동작하지 않는 것인데, Bootloader에서 점프를 못한 것처럼 착각할 수 있다. 따라서 아래 코드와 같이 SystemInit() 함수에서 Vector Table을 설정하고 인터럽트를 활성화 시키도록 해야 한다.

void SystemInit(void)
{
#if defined(USER_VECT_TAB_ADDRESS)
  SCB->VTOR = VECT_TAB_BASE_ADDRESS | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
  __enable_irq();
#endif /* USER_VECT_TAB_ADDRESS */
}

SystemInit() 함수는 STM32에서 최초 실행되는 지점이라고 이해하면 될 것이다. 위와 같이 설정 하였다면 MainApp에서 수정사항은 끝난 것이고, Bootloader에서 Interrupt Enable / Pending Register를 모두 Clear 시켜야 MainApp에서 정상적으로 irq handler가 동작한다.

 

Bootloader에서 MainApp로 점프하기 전에 인터럽트를 모두 disable 시키고, Interrup Enable / Pending Register를 아래와 같이 Clear 시키도록 한다.

void mcu_terminate(uint32_t addr)
{
  HAL_RCC_DeInit();
  HAL_DeInit();
  SysTick->CTRL = 0;
  SysTick->LOAD = 0;
  SysTick->VAL  = 0;

  __disable_irq();
   /* Clear Interrupt Enable Register & Interrupt Pending Register */
  for (uint8_t i = 0; i < sizeof(NVIC->ICER) / sizeof(NVIC->ICER[0]); i++) {
    NVIC->ICER[i] = 0xFFFFFFFF;
    NVIC->ICPR[i] = 0xFFFFFFFF;
  }
}

void system_jump_application(uint32_t addr)
{
  volatile uint32_t msp       = (*(volatile uint32_t*)addr);
  volatile uint32_t jump_addr = (*(volatile uint32_t*)(addr + 4));

  if (msp < 0x20000000 || 0x20030000 < msp) {
    error("%s() : addr = 0x%lx, msp = 0x%lx, jump_addr = 0x%lx", __func__, addr, msp, jump_addr);
    return;
  }
  // debug("%s() : addr = 0x%lx, msp = 0x%lx, jump_addr = 0x%lx", __func__, addr, msp, jump_addr);

  jump_func jump_app = (jump_func)jump_addr;
  mcu_terminate(addr);

  mcu_set_msp(addr);
  jump_app();
  while (1);
}

mcu_terminate() 함수에서 FreeRTOS에선 __disable_irq() 실행만으로 MainApp가 정상적으로 동작하였으나, MainApp가 Non-OS인 경우 NVIC->ICER, NVIC->ICPR 레지스터를 Clear하지 않으면 MainApp에서 irq handler가 동작하지 않거나 gpio init 과정에서 시스템이 hang-up 되는 현상이 발생한다.

 

이것 때문에 하루종일 삽질을 하여 블로그에 정리해 둔다. 나중에 이런 삽질을 하지 말아야지....