单片机上电后并不会直接进入 main() 函数,而是首先执行启动文件 startup_stm32f40_41xxx.s 中定义的启动流程,包含读取中断向量表、PC指针的复位等。其中包含两个重要的角色:

  •  MSP(栈指针):指向内存中栈区(一种特殊的数据结构)的当前位置。栈用于存储函数调用时的返回地址、局部变量、寄存器上下文等。
  • PC(程序计数器):指示CPU接下来要执行下一条指令。它就像一个指针,始终指向内存中将要执行的下一条指令的地址。CPU每执行完一条指令,PC会自动增加指向下一条,除非遇到跳转指令(如函数调用、条件分支)改变它。

下面通过一个实例介绍MCU其中程序中MSP、PC指针的赋值与变化

    int main(void)
    {
        int a = 1;
        int b = a;
    
        SysTick_Config(16000000);
    
        while (1)
        {
            // 主循环
        }
    }

    主程序启动分为两个阶段:

    • 读取向量表  → 初始化 MSP / PC

          复位时,MSP与PC指针的变化如下:

    //地址              内容
    0x08000000        0x20020000     ← 栈顶地址
    0x08000004        0x08000101     ← Reset_Handler 地址
      
    //CPU复位后
    MSP = 0x20020000
    SP  = MSP = 0x20020000
    PC  = 0x08000101
    • Reset_Handler → main()→ 局部变量 a、b 入栈 → SysTick_Config()

      初始化时钟配置systemInit()后才进入主程序 main()

    main()转化为汇编指令下的MSP、PC变化过程:

    地址             汇编指令                      PC变化                          MSP/SP变化
    ------------------------------------------------------------------------------------------------
    0x08000100    PUSH {LR}           PC: 0x08000100 → 0x08000102       SP: 0x20020000 → 0x2001FFFC
    
    0x08000102    SUB SP, SP, #8      PC: 0x08000102 → 0x08000104       SP: 0x2001FFFC → 0x2001FFF4
    
    0x08000104    MOVS R0, #1         PC: 0x08000104 → 0x08000106       SP不变
    
    0x08000106    STR R0, [SP, #4]    PC: 0x08000106 → 0x08000108       SP不变
                                  ; a = 1
    
    0x08000108    LDR R0, [SP, #4]    PC: 0x08000108 → 0x0800010A       SP不变
                                  ; 读取 a
    
    0x0800010A    STR R0, [SP, #0]    PC: 0x0800010A → 0x0800010C       SP不变
                                  ; b = a
    
    0x0800010C    LDR R0, =16000000   PC: 0x0800010C → 0x08000110       SP不变
    
    0x08000110    BL SysTick_Config   PC: 0x08000110 → SysTick_Config   SP本身不一定变
                                  ; LR = 0x08000115                 ; 函数内部可能继续用栈
    
    0x08000114    B loop              PC: 0x08000114 → 0x08000114       SP不变

    分析:

    1. 进入 main() 函数后,若函数中定义了局部变量,编译器通常会通过调整 SP/MSP 为当前函数建立栈帧,从而在栈中为局部变量分配空间。例如,两个 int 类型局部变量通常需要 8 字节栈空间,因此可能通过 SUB SP, SP, #8 使栈指针向低地址移动。
    2. 对局部变量的读写并不是移动栈顶,而是通过 SP 加偏移地址的方式访问,例如 [SP, #4] 表示访问栈帧中某个局部变量的位置。函数返回或栈帧释放时,SP/MSP 再恢复到进入函数前的位置

    总结:单片机上电后,CPU 根据中断向量表初始化 MSP,并将 PC 设置为 Reset_Handler 的入口地址,从而开始执行启动代码。PC 在顺序执行时通过自增自动指向下一条指令。进入 main() 后,局部变量通过调整 MSP 的栈顶的位置来分配存储空间;变量的读写则通过基于 MSP 的偏移寻址完成。

    更多推荐