STM32启动过程浅析(MAP文件、STM32启动过程、启动模式、Reset_Handler函数、__initial_sp堆栈的起始地址、堆栈)

参考

http://t.csdnimg.cn/9Y6n4

一、MAP文件浅析

在这里插入图片描述
在这里插入图片描述
MAP文件(Memory Map File)是编译器生成的连接地图文件,提供了有关程序在内存中的布局信息。MAP文件包含了代码、数据、堆栈等在内存中的地址分配情况,以及每个模块的大小等信息。在MDK(Keil Microcontroller Development Kit)编译过程中,MAP文件对于分析程序存储占用情况非常有用。

以下是MAP文件的一些关键信息:
1.模块列表:
显示了每个编译单元(源文件)生成的目标模块的详细信息,包括模块名称、起始地址、长度等。

2.内存段信息:
描述了程序的内存布局,包括代码段、数据段、堆栈等的起始地址和长度。

3.符号表:
包含了程序中使用的所有符号(变量、函数等)的地址和大小。

4.存储占用情况:
显示了每个内存段(如FLASH、RAM)的占用情况,包括已使用空间、未使用空间等。

5.调用图:
提供了函数之间的调用关系,可以帮助分析程序的执行流程。

通过分析MAP文件,开发者可以了解程序的内存布局,查看各个模块的存储占用情况,优化代码,解决内存溢出等问题。

MAP文件概念和作用

在这里插入图片描述
MAP文件(Memory Map File)是在编译过程中由链接器生成的一种文件,用于描述程序在内存中的布局和各个模块的分配情况。以下是MAP文件的主要概念和作用:

1.概念:
内存映射: 将程序的各个部分(代码、数据、堆栈等)映射到目标设备的物理内存地址上。
地址空间: 描述程序在内存中的布局,通常包括FLASH、RAM等区域,每个区域都有起始地址和长度。
符号表: 记录了程序中使用的符号(变量、函数等)的地址和大小。

2.作用:
存储占用分析: 提供了每个模块(源文件、库文件等)在内存中的占用情况,包括代码段、数据段等的大小和地址。
符号信息: 列出了程序中定义和引用的符号,有助于了解各个符号在内存中的分布情况。
调用关系: 提供了函数之间的调用关系,有助于理解程序的执行流程。
优化代码: 开发者可以根据MAP文件分析结果,对代码进行优化,减小存储占用,提高执行效率。

MAP文件是在调试和优化阶段非常有用的工具,通过分析这些信息,开发者可以更好地理解程序在内存中的分布,从而优化代码、解决内存相关问题。

MAP文件组成

在这里插入图片描述
MAP文件通常由以下几个主要部分组成:
1.程序段交叉引用关系:
描述各个源文件(.c、.s等)之间的函数调用关系。
列出了函数之间的引用关系,帮助理解程序的调用流程。

2.删除映像未使用的程序段:
描述工程中未使用的、被删除的冗余程序段(函数/数据)。
帮助开发者了解工程中存在的冗余代码,有助于优化代码。

3.映像符号表:
描述各个符号在存储器中的地址、类型、大小等信息。
包括函数、变量等的地址和占用大小。

4.映像内存分布图:
描述各个程序段(函数)在存储器中的地址及占用大小。
显示了程序在FLASH和RAM等存储器中的布局。

5.映像组件大小:
汇总整个映像代码(.o文件)占用的空间信息。
给出了程序的总体大小、代码段大小、数据段大小等信息。

这些信息有助于开发者深入了解程序在内存中的布局和分布情况,通过MAP文件,可以更好地进行代码优化、查找冗余代码、解决内存相关的问题。

MAP文件实操

学会分析:哪个.c占用flash 和ram比较大,以便针对性的优化

map 文件的 MDK 设置

在这里插入图片描述
在这里插入图片描述
设置好 MDK 以后,我全编译当前工程,当编译完成后(无错误),就会生成.map 文件。在 MDK 里面打开 .map 文件的方法如图所示:
在这里插入图片描述
1.先确保工程编译成功(无错误)。
2.双击 LED,打开.map 文件。
3.map 文件打开成功。

二、STM32启动过程

在这里插入图片描述

2.1、STM32启动模式(F1/F4/F7/H7)(也称自举模式)

在这里插入图片描述
启动模式,也被称为自举模式(bootstrapping mode),是指在单片机复位后执行的初始化和启动操作。对于STM32和许多其他微控制器,启动模式包括以下几个关键步骤:

1.取出堆栈指针 MSP 的初始值:
从地址0x0000 0000处读取的第一个字(32位)是堆栈指针的初始值。这个值表示了栈的初始位置。

2.取出程序计数器指针 PC 的初始值:
从地址0x0000 0004处读取的第二个字是程序计数器的初始值,即复位向量。这个值指示了执行程序的起始地址。

3.地址映射:
注意到芯片厂商可能会将0x0000 0000和0x0000 0004地址映射到其他地址。这是由于一些芯片允许在不同的启动模式下加载不同的固件。这样,启动模式可以选择不同的起始地址。

在STM32中,这些初始值是由芯片上的启动加载器(Bootloader)提供的,该加载器通常存储在固定的引导区域中。通过在这个引导区域中设置不同的启动模式标志位,可以选择加载不同的程序或固件。

这个启动过程确保了在复位后,程序能够正确地开始执行,并且栈和程序计数器都被正确初始化。

STM32启动模式(F1)

在这里插入图片描述

STM32启动模式(F4)

在这里插入图片描述

STM32启动模式(F7)

在这里插入图片描述

STM32启动模式(H7)

在这里插入图片描述

2.2、STM32启动过程(内部FLASH启动为例)

在这里插入图片描述
STM32启动过程的步骤详细说明:
1.Reset复位:
当STM32芯片上电或复位时,处理器会进入复位状态,即复位向量。在复位状态下,处理器会执行一系列初始化步骤来确保正常运行。

2.获取MSP值0X0800 0000:
复位向量的第一个字(32位)包含堆栈指针(MSP - Main Stack Pointer)的初始值。处理器将该值加载到堆栈指针寄存器(SP)中。

3.获取PC值0X0800 0004:
复位向量的第二个字包含程序计数器(PC - Program Counter)的初始值,即复位向量。处理器将该值加载到程序计数器寄存器(PC)中。

4.Reset_Handler:
复位向量中的复位向量指向Reset_Handler。这是一个特殊的函数,由启动文件(通常是startup_stm32xxx.s)提供。Reset_Handler 会执行一些处理器的基本初始化,然后跳转到 main 函数。

5.启动文件 startup_stm32xxx.s:
启动文件是一个汇编文件,包含处理器初始化和启动的汇编代码。它负责初始化数据段、BSS 段、调用系统初始化等。

6.main函数:
一旦启动文件执行完毕,它将跳转到 main 函数。在 main 函数中,用户可以编写他们的应用程序代码。

这个启动过程确保了在复位后,程序能够正确地开始执行。内部FLASH启动是默认的启动模式,但STM32芯片通常支持其他启动模式,例如通过串口或外部存储器引导。在这种情况下,复位向量和启动文件可能会有所不同。

启动文件介绍

在这里插入图片描述
对STM32启动过程的这些关键步骤概述。以下是对每个步骤的一些进一步的解释:
1.初始化MSP:
MSP(Main Stack Pointer)是处理器的主堆栈指针,指向主堆栈区的顶部。在启动时,它的初始值通常是存储在复位向量表的第一个位置(0x0800 0000)的值。这个值通常是设备的系统内存(RAM)的顶部。

2.初始化PC:
PC(Program Counter)是处理器的程序计数器,指向要执行的下一条指令的内存地址。在启动时,PC 的初始值存储在复位向量表的第二个位置(0x0800 0004)。这个值通常是 Reset_Handler 函数的地址,它是启动文件中的一个特殊函数。

3.设置堆栈大小:
堆栈和堆是两个不同的内存区域。在启动时,通常设置堆栈大小和堆大小。这些值由链接脚本(.ld 文件)定义,然后由链接器使用。

4.初始化中断向量表:
中断向量表是一系列指向中断服务例程(ISR)的指针。在启动时,这些指针的初始值存储在复位向量表中。__Vectors 是一个包含这些指针的数组。

5.调用初始化函数:
SystemInit 函数是一个可选的函数,用于执行系统级的初始化。这可能包括设置系统时钟、中断优先级等。

6.调用 __main:
__main 是标准 C 库函数,它执行一系列的设置,并最终调用 main 函数。在用户代码中,main 函数是程序的起始点。

这些步骤确保了在复位后,处理器能够按照预期的方式执行程序。这个过程是由启动文件和链接脚本控制的,具体的细节可能会因设备型号和编译环境而有所不同。

Reset_Handler函数介绍

在这里插入图片描述
这段汇编代码是 Cortex-M 处理器的启动代码,主要完成了以下几个步骤:

1.导出 Reset_Handler 函数: EXPORT 关键字用于标明 Reset_Handler 函数是一个全局可见的标识符,可以被其他模块引用。

2.声明 __main 和 SystemInit 函数: IMPORT 关键字用于声明 __main 和 SystemInit 函数,这两个函数通常是由编译器生成的。

3.加载 SystemInit 函数的地址到 R0 寄存器: 使用 LDR 指令将 SystemInit 函数的地址加载到 R0 寄存器。

4.通过 BLX 指令调用 SystemInit 函数: BLX 指令用于调用函数,这里将通过 R0 寄存器中的地址调用 SystemInit 函数。这个函数通常包含了系统初始化的相关操作。

5.加载 __main 函数的地址到 R0 寄存器: 使用 LDR 指令将 __main 函数的地址加载到 R0 寄存器。

6.通过 BX 指令跳转到 __main 函数: BX 指令用于跳转到指定地址的子程序,这里将跳转到 __main 函数,即 C 语言程序的入口点。

7.定义 Reset_Handler 过程结束: ENDP 关键字用于表示 Reset_Handler 过程的结束。

8.弱定义标记: WEAK 关键字表示这是一个弱定义,如果在其他地方有相同的函数定义,链接器可以选择其中一个。在这里,如果系统提供了自定义的 SystemInit 函数,它将被使用;否则,使用默认的 SystemInit 函数。

这段代码是启动文件中的一部分,它负责初始化系统,并跳转到主程序的入口点。

堆栈简介

在这里插入图片描述
堆栈是在程序运行时动态分配和管理内存的两个主要区域。这两个区域分别是:

1.栈(Stack):
由编译器自动分配和释放。
用于存放函数参数、局部变量等。
具有后进先出(LIFO)的结构,即最后入栈的数据最先出栈。
栈的大小在编译时就已经确定,通常比较有限,如果函数调用层次太深或者局部变量较多,可能导致栈溢出。

2.堆(Heap):
由程序员负责手动分配和释放。
通过动态内存分配函数(如malloc、calloc、realloc等)进行操作。
用于存储程序运行时动态分配的数据。
堆的大小通常比栈大得多,但使用不当可能导致内存泄漏或者堆碎片问题。

在嵌入式系统开发中,尤其是对于资源有限的系统,合理管理栈和堆的大小是很重要的。太小的栈可能导致栈溢出,而太小的堆可能导致动态内存分配失败。在确定栈和堆的大小时,需要考虑程序的复杂性、函数调用深度、局部变量和动态内存需求等因素。

STM32启动过程图解

在这里插入图片描述

在嵌入式系统中,Reset_Handler 是一个特殊的函数,它是系统复位后执行的第一个函数。该函数负责初始化系统,设置堆栈和跳转到主函数(通常是 main 函数)。下面是一个简单的 Reset_Handler 函数的例子:

Reset_Handler PROC
    EXPORT Reset_Handler [WEAK]
    IMPORT __main
    IMPORT SystemInit

    LDR R0, =SystemInit
    BLX R0

    LDR R0, =__main
    BX R0
    ENDP

在这个例子中:
EXPORT 表示将 Reset_Handler 函数标记为可被外部调用。
IMPORT 表示引入外部定义的函数,这里是 __main 和 SystemInit。
LDR R0, =SystemInit 将 SystemInit 函数的地址加载到寄存器 R0。
BLX R0 调用 SystemInit 函数。
接着调用 __main 函数,通常在该函数中会进入到用户编写的 main 函数。

关于 __initial_sp,它是初始化堆栈指针的一个符号。在 ARM Cortex-M 处理器中,启动代码通常将 __initial_sp 设置为堆栈的起始地址。在链接脚本中,你可能会看到类似如下的定义:

__initial_sp = 0x20020000; /* 堆栈的起始地址,具体值根据实际情况而定 */

这个地址指向的是堆栈的起始位置,即堆栈的最高地址。在 Reset_Handler 函数中,会将这个地址加载到堆栈指针寄存器 MSP(Main Stack Pointer)中,从而初始化堆栈。这个过程通常在 SystemInit 函数中完成。

总的来说,Reset_Handler 负责系统的初始化,包括设置堆栈和调用主函数,而 __initial_sp 是堆栈的初始地址。这两者通常在启动文件(startup 文件)中定义和使用。
在这里插入图片描述
在这里插入图片描述

  • 17
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
查看文章 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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值