1. 简介
在做bootloader 跳转到application时,经常会看到设置MSP的操作__set_MSP(*(__IO uint32_t*) APPLICATION_ENTRY);
。
1.1 MSP的作用
在STM32微控制器中,MSP(Main Stack Pointer,主堆栈指针)是一个非常重要的寄存器,它用于管理堆栈。堆栈是用于临时存储数据和保持子程序返回地址的区域。MSP在以下几种情况下发挥关键作用:
-
启动时初始化:在系统启动时,MSP会被初始化为向量表中的初始值。这个初始值通常在启动文件(如
startup_stm32fxx.s
)中定义。 -
异常和中断处理:当发生异常或中断时,MSP用于保存当前的程序状态,包括寄存器内容。这样,当异常或中断处理完成后,可以恢复原来的程序状态并继续执行。
-
操作系统使用:在使用实时操作系统(RTOS)时,MSP通常用于处理系统级的中断和异常,而每个任务可能有自己的堆栈指针(PSP,Process Stack Pointer)。MSP和PSP的切换由操作系统来管理,以实现任务之间的切换。
-
函数调用:在函数调用过程中,MSP用于保存局部变量、函数参数和返回地址。当函数调用结束时,MSP会恢复到调用前的状态。
MSP的值可以通过特定的汇编指令(如MRS
和MSR
)来读取和修改。在C语言编程中,通常不需要直接操作MSP,但在底层驱动开发或调试时,了解MSP的作用和如何操作它是很有帮助的。
下面是一个简单的汇编代码示例,展示如何读取和写入MSP:
MRS R0, MSP ; 读取MSP的值到R0寄存器
MSR MSP, R0 ; 将R0寄存器的值写入MSP
通过这些操作,可以查看和修改MSP的值,以便进行调试或特定的系统级操作。
1.2 MSP的设置
MSP 通常在特权模式下使用, 由硬件自动管理和切换, 在系统启动时初始化。
- 系统初始化时,会执行
Reset_Handler
, 先看下这个函数
Reset_Handler:ldr r0, =_estackmov sp, r0 /* set stack pointer *//* Call the clock system initialization function.*/bl SystemInit/* Copy the data segment initializers from flash to SRAM */ldr r0, =_sdataldr r1, =_edataldr r2, =_sidatamovs r3, #0b LoopCopyDataInit......
从上述代码的可以看出, Reset_Handler
把_estack
加载到SP(堆栈指针), 而_estack
在链接文件中定义
/* Highest address of the user mode stack */ _estack = ORIGIN(RAM) + LENGTH(RAM); /* end of "RAM" Ram type memory */
以STM32G070为例, 在.map文件中可以看到_estack
的值
0x20009000 _estack = (ORIGIN (RAM) + LENGTH (RAM))
0x00000200 _Min_Heap_Size = 0x200
0x00000400 _Min_Stack_Size = 0x400
所以上电初始化时, MSP的值应该是 0x20009000.
2. 跳转时是否必须显示的设置MSP的值
2.1 _estack的值存储在哪里
上述1.2节,介绍了_estack的初始化值为0x20009000,但这个值存储在哪个地址?答案是在中断向量表中:
.section .isr_vector,"a",%progbits.type g_pfnVectors, %objectg_pfnVectors:.word _estack.word Reset_Handler.word NMI_Handler.word HardFault_Handler.word 0.word 0.word 0.word 0.word 0.word 0.word 0.word SVC_Handler.word 0.word 0.word PendSV_Handler.word SysTick_Handler.word WWDG_IRQHandler /* Window WatchDog */.word 0 /* reserved */......
由中断向量表代码段可看出, _estack
在向量表的首地址; 查看.map文件:
.isr_vector 0x08000000 0xb80x08000000 . = ALIGN (0x4)*(.isr_vector).isr_vector 0x08000000 0xb8 ./Core/Startup/startup_stm32g070cbtx.o0x08000000 g_pfnVectors0x080000b8 . = ALIGN (0x4)
向量表的首地址为isr_vector 0x08000000
;所以_estack
的值存储在 0x08000000
, 值为 0x20009000
.
通过JLINK可以读出FLASH里的值
或者查看hex文件,同样可以找到
2.2 跳转时设置 MSP
跳转时设置MSP的值:
__set_MSP(*(__IO uint32_t*) APPLICATION_ENTRY);
其中APPLICATION_ENTRY
就是application的起始地址, 这里假设APPLICATION_ENTRY = 0x0800a200
, 那这个地址理论上存放的也是application的 _estack
, 通过JLINK读取这个地址的值验证:
所以,__set_MSP(*(__IO uint32_t*) APPLICATION_ENTRY);
这句话的意思也就是把application的_estack
赋值给MSP。
2.3 跳转时不显式的设置MSP
如果跳转时不加__set_MSP(*(__IO uint32_t*) APPLICATION_ENTRY);
, 会不会有问题?
先看跳转的代码:
// Start applicationdw_reset = *(uint32_t *)(APPLICATION_START);pf_reset = (t_VOID_FUNC)( dw_reset );pf_reset();
这里APPLICATION_START = 0x0800A204
,也就是bootloader 直接跳转到application的 Reset_Handler
。至此,又回到了1.2节, 在Reset_Handler
里会自动将 _estack
的值赋给SP。
所以,如果Reset_Handler
里设置了SP的值,则跳转时,不是必须要显式的设置MSP的值。