STM32在bootloader跳转到application时设置MSP

1. 简介

在做bootloader 跳转到application时,经常会看到设置MSP的操作__set_MSP(*(__IO uint32_t*) APPLICATION_ENTRY);

1.1 MSP的作用

在STM32微控制器中,MSP(Main Stack Pointer,主堆栈指针)是一个非常重要的寄存器,它用于管理堆栈。堆栈是用于临时存储数据和保持子程序返回地址的区域。MSP在以下几种情况下发挥关键作用:

  1. 启动时初始化:在系统启动时,MSP会被初始化为向量表中的初始值。这个初始值通常在启动文件(如startup_stm32fxx.s)中定义。

  2. 异常和中断处理:当发生异常或中断时,MSP用于保存当前的程序状态,包括寄存器内容。这样,当异常或中断处理完成后,可以恢复原来的程序状态并继续执行。

  3. 操作系统使用:在使用实时操作系统(RTOS)时,MSP通常用于处理系统级的中断和异常,而每个任务可能有自己的堆栈指针(PSP,Process Stack Pointer)。MSP和PSP的切换由操作系统来管理,以实现任务之间的切换。

  4. 函数调用:在函数调用过程中,MSP用于保存局部变量、函数参数和返回地址。当函数调用结束时,MSP会恢复到调用前的状态。

MSP的值可以通过特定的汇编指令(如MRSMSR)来读取和修改。在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, =_estack
      mov   sp, r0          /* set stack pointer */
    
    /* Call the clock system initialization function.*/
      bl  SystemInit
    
    /* Copy the data segment initializers from flash to SRAM */
      ldr r0, =_sdata
      ldr r1, =_edata
      ldr r2, =_sidata
      movs r3, #0
      b 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, %object
    
    g_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       0xb8
                    0x08000000                        . = ALIGN (0x4)
     *(.isr_vector)
     .isr_vector    0x08000000       0xb8 ./Core/Startup/startup_stm32g070cbtx.o
                    0x08000000                g_pfnVectors
                    0x080000b8                        . = 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 application
        dw_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的值。

    作者:pirateeee

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32在bootloader跳转到application时设置MSP

    发表回复