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 되는 현상이 발생한다.
이것 때문에 하루종일 삽질을 하여 블로그에 정리해 둔다. 나중에 이런 삽질을 하지 말아야지....