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

第一条命令是生成 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
                ......

对于 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

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

从上面的代码可以看到,__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

__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

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

  • 20
    点赞
  • 118
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
查看文章 STM32 keil mdk启动代码发分析_转2010年01月29日 星期五 13:50 ;// Stack Configuration ;// Stack Size (in Bytes) ;// Stack_Size EQU 0x00000200 ;//定义堆栈大小 AREA STACK, NOINIT, READWRITE, ALIGN=3 ;//定义一个数据段 按8字节对齐 ;AREA 伪指令用于定义一个代码段或数据段 NOINIT:指定此数据段仅仅保留了内存单元,而没有将各初始值写入内存单元,或者将各个内存单元值初始化为0 Stack_Mem SPACE Stack_Size ;//保留Stack_Size大小的堆栈空间 分 配连续 Stack_Size 字节的存储单元并初始化为 0 __initial_sp ;//标号,代表堆栈顶部地址,后面有用 ;// Heap Configuration ;// Heap Size (in Bytes) ;// Heap_Size EQU 0x00000020 ;//定义堆空间大小 AREA HEAP, NOINIT, READWRITE, ALIGN=3 ;//定义一个数据段,8字节对齐 __heap_base Heap_Mem SPACE Heap_Size ;//保留Heap_Size的堆空间 __heap_limit ;//标号,代表堆末尾地址,后面有用 PRESERVE8 ;//指示编译器8字节对齐 THUMB ;//指示编译器为THUMB指令 ; Vector Table Mapped to Address 0 at Reset AREA RESET, DATA, READONLY ;//定义只读数据段,其实放在CODE区,位于0地址 EXTERN NMIException EXTERN HardFaultException EXTERN MemManageException EXTERN BusFaultException EXTERN UsageFaultException EXTERN SVCHandler EXTERN DebugMonitor EXTERN PendSVC EXTERN SysTickHandler ;//声明这些符号在外部定义,同C ;//在××it.c中实现这些函数 ,中断就能自动调用了 EXPORT __Vectors EXPORT __initial_sp ;EXPORT:在程序中声明一个全局的标号__Vectors,该标号可在其他的文件中引用;I

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值