从引导加载程序跳转到应用程序时,我们通常将堆栈指针更新为应用程序堆栈指针,然后将程序计数器更新为应用程序的Reset_Handler。
void jump_to_application(void)
{
/* Function pointer to the address of the user application. */
fnc_ptr_for_app jump_to_app;
jump_to_app = (fnc_ptr_for_app)(*(volatile uint32_t*) (FLASH_APP_START_ADDRESS+4u));
__CRC_CLK_DISABLE();
HAL_DeInit();
/* Change the main stack pointer. */
__set_MSP(*(volatile uint32_t*)FLASH_APP_START_ADDRESS);
jump_to_app();
}
Reset_Handler的第一行代码如下,该代码将堆栈指针初始化为其自己的堆栈指针值
Reset_Handler:
ldr sp, =_estack /* Atollic update: set stack pointer */
/* Copy the data segment initializers from flash to SRAM */
movs r1, #0
b LoopCopyDataInit
但是仍然建议使用应用程序的指针更新堆栈指针。
为什么需要它,如果我们不更新就跳下去会有什么副作用
答案 0 :(得分:5)
在ARM Cortex-M中,初始堆栈指针存储在向量表的前4个字节中,而初始程序计数器存储在后4个字节中。重置后,硬件从这8个字节加载SP和PC。例如,这允许将Cortex-M启动代码用C而不是汇编器编写。
您的引导程序通过将硬件取消初始化为复位状态,然后从应用程序的向量表中加载SP和PC,来模拟此 reset 行为。这样,应用程序就可以像 一样从复位状态启动,而无需依赖引导加载程序的任何初始化或设置。
bootloader是与应用程序分开编译和链接的,并且必须能够加载具有适当起始地址的任何应用程序代码。因此,引导加载程序无法确定或强制任何加载的应用程序代码将设置堆栈指针,因为它可以合理地假设硬件已经设置了它。同样在这种情况下,_estack != *(volatile uint32_t*)FLASH_APP_START_ADDRESS)
在任何情况下都是完全可能的。
对引导加载程序的观察是,它没有将向量表设置为应用程序的向量表,这是潜在的危险-如果启动代码在未先设置向量表的情况下启用了中断,则可能会导致引导程序中断错误调用的处理程序。拥有它会更安全:
// Switch vector table
SCB->VTOR = APPLICATION_START_ADDR ;
在jump_to_app()
之前。或者,如果您选择使用HAL:
NVIC_SetVectorTable( NVIC_VectTab_FLASH, APPLICATION_START_ADDR ) ;