h_e*_*sek 5 c arm stm32 bootloader cortex-m
我正在为 Nucleo-F429ZI 编写引导加载程序。我有两个不同的 STM32 项目,一个用于引导加载程序本身,另一个用于从引导加载程序跳转的应用程序。
引导加载程序的链接器脚本
MEMORY
{
CCMRAM (xrw) : ORIGIN = 0x10000000, LENGTH = 64K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 32K
FLASH (rx) : ORIGIN = 0x8000000, LENGTH = 32K
}
Run Code Online (Sandbox Code Playgroud)
应用程序的链接器脚本
_estack = ORIGIN(RAM) + LENGTH(RAM);
MEMORY
{
CCMRAM (xrw) : ORIGIN = 0x10000000, LENGTH = 64K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 192K
FLASH (rx) : ORIGIN = 0x8008000, LENGTH = 64K
}
Run Code Online (Sandbox Code Playgroud)
我没有忘记设置应用程序的闪光偏移。
system_stm32f4xx.c(在应用程序项目中)
#define VECT_TAB_BASE_ADDRESS FLASH_BASE // 0x8000000
#define VECT_TAB_OFFSET 0x00008000U
Run Code Online (Sandbox Code Playgroud)
意法半导体关于bootloaders的教程有如下代码跳转
main.c(在引导加载程序项目中)
#define FLASH_APP_ADDR 0x8008000
typedef void (*pFunction)(void);
uint32_t JumpAddress;
pFunction Jump_To_Application;
void go2APP(void)
{
JumpAddress = *(uint32_t*)(FLASH_APP_ADDR + 4);
Jump_To_Application = (pFunction) JumpAddress;
__set_MSP(*(uint32_t*)FLASH_APP_ADDR); // in cmsis_gcc.h
Jump_To_Application();
}
Run Code Online (Sandbox Code Playgroud)
cmsis_gcc.h(在引导加载程序项目中)
__STATIC_FORCEINLINE void __set_MSP(uint32_t topOfMainStack)
{
__ASM volatile ("MSR msp, %0" : : "r" (topOfMainStack) : );
}
Run Code Online (Sandbox Code Playgroud)
可以看到,__set_MSP函数在跳转到FLASH_APP_ADDR + 4之前设置了主堆栈指针。我通过调试找到了目标位置的内存位置。FLASH_APP_ADDR + 4导致运行应用程序项目的Reset_Handler函数。让我们看看会执行什么。
startup_stm32f429zitx.c(在应用程序项目中)
.section .text.Reset_Handler
.weak Reset_Handler
.type Reset_Handler, %function
Reset_Handler:
ldr sp, =_estack /* set stack pointer */
/* Copy the data segment initializers from flash to SRAM */
ldr r0, =_sdata
ldr r1, =_edata
ldr r2, =_sidata
movs r3, #0
b LoopCopyDataInit
Run Code Online (Sandbox Code Playgroud)
Reset_Handler 所做的第一件事是设置堆栈指针。_estack 在链接描述文件中定义。
如果 Reset_Handler 正在设置堆栈指针,为什么我们要调用 __set_MSP 函数?我删除了函数 __set_MSP,引导过程仍然有效。不过,我检查了一些其他引导加载程序代码,发现了完全相同的逻辑。
我尝试了我所说的,但找不到解释。
小智 1
Cortex-M 内核在启动序列期间SP从地址加载寄存器的初始值。FLASH_BASE+0然后从地址跳转到代码入口点(重置向量)FLASH_BASE+4。任何引导加载程序代码都会模仿核心行为。请注意,FLASH_BASE这里不一定是实际的闪存基础,而是一个抽象值,这取决于所使用的处理器及其设置。
提供的Reset_Handler代码使用 __estack(主堆栈顶部)值加载 sp 寄存器,但并非必须如此!引导加载程序不能期望主程序执行此操作,但在复位后会执行与内核相同的引导顺序。这样,主代码就不必依赖于了解谁启动了它 - 核心、引导加载程序、jtag 或其他东西。
我见过启动代码,它不会加载SP,但会使用第一条指令禁用中断。或者用 C 语言编写的启动代码,它可以将堆栈与第一条指令一起使用。
这里真正的问题可能是:如果这个启动代码已经加载了 SP,为什么还要加载 SP?但也许应该将其转发给原始代码作者。