当前位置:   article > 正文

STM32启动过程分析_stm32栈顶指针保存数据

stm32栈顶指针保存数据

1. 程序和数据在Flash和SRAM上的存储结构

在讲解 STM32 启动过程之前,我们先来了解一下 STM32 的程序和数据在 Flash 和 SRAM 上到底是如何存储的,因为有了这方面的知识之后,非常有助于我们理解 STM32 启动过程中还做了哪些隐藏的工作。关于 STM32 详细的程序和数据存储分布信息,我们可以从Keil生成的 .map 文件中得到,下面讨论的 STM32 程序和数据的存储结构就是从这份文件和自己积累的一些相关知识总结出来的。要生成 .map 文件操作如下:

在这里插入图片描述

1.1 STM32的程序在flash上的存储结构

STM32 的程序在 Flash 上的存储结构如下图所示:

在这里插入图片描述

  • 栈顶指针 MSP 的值会根据程序数据段(包含 bss 段)的大小,加上定义的堆空间的大小,再加上定义的栈空间的大小,最终加起来才会得到栈顶指针的值的,也就是说栈顶指针所指向的地址还和具体的程序定义了多少全局数据有关。栈顶指针的值会保存在启动文件定义 __initial_sp 这个标号中,这个标号具体的数值在程序编译后才会被确定。
  • Reset_Handler 复位异常的入口函数,系统上电后,程序运行的第一条指令就是从这里开始运行的。
  • 异常向量表:这里面存放了系统异常(上面的 Reset_Handler 也属于系统异常的一种)和外部中断的入口地址,当程序发生异常或中断时,硬件会强制 CPU 跳转到相应的入口执行中断服务函数。
  • 代码段:这里存放的是程序编译得到的二进制机器码。
  • 只读数据:只读数据存放的是文字常量(比如字符串),或者 const 修饰的变量(注意:有些编译器 const 修饰的变量是不会把该变量放在只读数据区的,这个得看编译器的设计)。
  • 已初始化的全局数据:定义的已初始化的全局变量、或者已初始化的静态变量都会存放在这个位置。

补充:Flash上是不会保存没有初始化或初始化为0的数据的,这部分数据要使用之前,把对应的内存空间清零即可,也就是清除 bss 段。另外 Flash 上可能还会存放 Keil 添加的一些代码或者参数,这些参数可能会用于数据段的重定位、清除 bss 段等任务,我对这些参数具体的存放位置还研究得不深,所以没有把这部分内容写上去。

1.2 STM32的数据在SRAM上的存储结构

当程序完成了数据段的重定位和清除bss段的任务之后(对于 STM32 会在启动文件中会调用 __main 函数来完成这部分工作),STM32 的数据在 SRAM 上的存储结构如下图所示:

在这里插入图片描述

  • .data:存放的是已经初始化的全局数据。
  • .bss:存放的是没有初始化或者初始化为0的全局数据。
  • 堆空间:程序员使用 malloc 等函数动态申请的内存空间就是从这里分配的,堆空间的大小可以在启动文件中自定义。
  • 栈空间:栈主要用于程序的局部变量、函数调用、函数参数、中断保存现场等情况的开销的,同样栈空间的大小也可以在启动文件中自定义。

2. STM32启动过程概述

STM32 启动过程主要指的是,系统上电后 CPU 执行的第一条指令,到调用用户写的 main 函数的这一段过程。具体启动过程的步骤和所完成的工作是:

(1) 上电复位,硬件设置 SP、PC 的值

(2) 找到了 Reset_Handler 的地址后,CPU 就从这里开始取指令运行程序;

(3) 调用 SystemInit 函数,设置系统时钟;

(4) 调用 __main 函数,软件对 SP 寄存器赋值,完成数据段的重定位、清除 bss 段,初始化栈空间等工作;

(5) 最终 __main 函数会调用用户的 main 函数,进入到用户程序。

3. 启动过程详细分析

启动过程分析中所涉及到源码主要来自启动文件和 Keil 生成的反汇编文件提取出来的。其中,Keil 生成反汇编文件的设置步骤如下:

在这里插入图片描述

具体输入的命令如下:

fromelf  --bin  --output=YH_STM32F103VET6.bin  Objects\YH_STM32F103VET6.axf
fromelf  --text  -a -c  --output=YH_STM32F103VET6.dis  Objects\YH_STM32F103VET6.axf
  • 1
  • 2

第一条命令是生成 bin 文件的,如果不需要 bin 文件那么可以不输入这条命令。注意 YH_STM32F103VET6.axf 这个 axf 文件要根据自己工程的实际文件名输入。另外,生成反汇编文件时,最好把 Output 选项下的 Debug Information 这个选项勾上,这样生成的反汇编文件的信息比较详细。

3.1 上电复位,硬件设置SP、PC的值

下面是向量表的最前面两条指令:

__Vectors       DCD     __initial_sp               ; Top of Stack
                DCD     Reset_Handler              ; Reset Handler
                ......
  • 1
  • 2
  • 3

对于 Cortex-M3 内核,ARM 规定向量表的起始位置存放的是栈顶指针 MSP 的地址值,紧接着存放的是复位中断入口函数的地址。当刚上电的时候,硬件会根据向量表的地址找到向量表的具体位置(对于向量表的地址是可以通过 NVIC 中的一个重定位寄存器来设置的,复位时该寄存器的值为0),然后会根据向量表中的这两个数据,设置 SP、PC 的值,这时 CPU 就会从复位中断的入口函数开始取指令运行程序。

3.2 调用SystemInit函数,设置系统时钟

硬件设置好了 SP、PC 的值后,这时CPU 会从 Reset_Handler 处开始取指令运行,首先会调用 SystemInit 函数来初始化系统时钟,该函数是官方固件库提供的一个库函数,在 system_stm32f10x.c 这个文件中有该函数的定义。下面是 Reset_Handler 的代码。

Reset_Handler   PROC
                EXPORT  Reset_Handler             [WEAK]
                IMPORT  __main
                IMPORT  SystemInit
                LDR     R0, =SystemInit
                BLX     R0               ; 调用SystemInit函数
                LDR     R0, =__main
                BX      R0
                ENDP
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

3.3 调用__main函数

__main 函数是系统自带的库函数,根据反汇编的文件,得到的该函数的部分代码如下:

__main
    _main_stk
        0x08000130:    f8dfd010    ....    LDR      sp,__lit__00000000 ; [0x8000144] = 0x200007a0
    .ARM.Collect$$$$00000004
    _main_scatterload
        0x08000134:    f000f81a    ....    BL       __scatterload ; 0x800016c
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

从上面的代码可以看到,__main 函数首先软件设置了 SP 寄存器的值等于 0x200007a0,也就是设置了栈顶指针的指向。

接下来就跳转到了 __scatterload 函数,从函数名可以看出这是一个分散加载函数,该函数主要实现的功能就是数据段的重定位和清除 bss 段,初始化栈空间。该函数的反汇编源码如下:

__scatterload
    __scatterload_rt2
        0x0800016c:    4c06        .L      LDR      r4,[pc,#24] ; [0x8000188] = 0x80005d4
        0x0800016e:    4d07        .M      LDR      r5,[pc,#28] ; [0x800018c] = 0x80005f4
        0x08000170:    e006        ..      B        0x8000180 ; __scatterload + 20
        0x08000172:    68e0        .h      LDR      r0,[r4,#0xc]
        0x08000174:    f0400301    @...    ORR      r3,r0,#1
        0x08000178:    e8940007    ....    LDM      r4,{r0-r2}
        0x0800017c:    4798        .G      BLX      r3
        0x0800017e:    3410        .4      ADDS     r4,r4,#0x10
        0x08000180:    42ac        .B      CMP      r4,r5
        0x08000182:    d3f6        ..      BCC      0x8000172 ; __scatterload + 6
        0x08000184:    f7ffffd8    ....    BL       __main_after_scatterload ; 0x8000138
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

__scatterload 函数实际上会去调用 __scatterload_copy 和 __scatterload_zeroinit 函数来完成上述的功能。

3.4 调用main函数

从上面的汇编代码可以看出,__scatterload 函数最后调用了 __main_after_scatterload 函数,这个函数实现的功能就是跳转到了用户的 main 函数,进入到用户程序。__main_after_scatterload 函数的代码如下:

__main_after_scatterload
    _main_clock
    _main_cpp_init
    _main_init
        0x08000138:    4800        .H      LDR      r0,[pc,#0] ; [0x800013c] = 0x80004c1
        0x0800013a:    4700        .G      BX       r0
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

可以看出,代码最后跳转到了 r0 寄存器所指示的地址中去了,实际上这个地址就是用户的 main 函数地址。到这里为止 STM32 的启动过程就完成了,接下来运行的就是用户写的代码了。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/713285
推荐阅读
相关标签
  

闽ICP备14008679号