- 2.1、STM32启动模式(F1/F4/F7/H7)(也称自举模式)
- STM32启动模式(F1)
- STM32启动模式(F4)
- STM32启动模式(F7)
- STM32启动模式(H7)
- 2.2、STM32启动过程(内部FLASH启动为例)
一、MAP文件浅析
MAP文件(Memory Map File)是编译器生成的连接地图文件,提供了有关程序在内存中的布局信息。MAP文件包含了代码、数据、堆栈等在内存中的地址分配情况,以及每个模块的大小等信息。在MDK(Keil Microcontroller Development Kit)编译过程中,MAP文件对于分析程序存储占用情况非常有用。
以下是MAP文件的一些关键信息:
-
模块列表:
- 显示了每个编译单元(源文件)生成的目标模块的详细信息,包括模块名称、起始地址、长度等。
-
内存段信息:
- 描述了程序的内存布局,包括代码段、数据段、堆栈等的起始地址和长度。
-
符号表:
- 包含了程序中使用的所有符号(变量、函数等)的地址和大小。
-
存储占用情况:
- 显示了每个内存段(如FLASH、RAM)的占用情况,包括已使用空间、未使用空间等。
-
调用图:
- 提供了函数之间的调用关系,可以帮助分析程序的执行流程。
通过分析MAP文件,开发者可以了解程序的内存布局,查看各个模块的存储占用情况,优化代码,解决内存溢出等问题。
MAP文件概念和作用
MAP文件(Memory Map File)是在编译过程中由链接器生成的一种文件,用于描述程序在内存中的布局和各个模块的分配情况。以下是MAP文件的主要概念和作用:
-
概念:
- 内存映射: 将程序的各个部分(代码、数据、堆栈等)映射到目标设备的物理内存地址上。
- 地址空间: 描述程序在内存中的布局,通常包括FLASH、RAM等区域,每个区域都有起始地址和长度。
- 符号表: 记录了程序中使用的符号(变量、函数等)的地址和大小。
-
作用:
- 存储占用分析: 提供了每个模块(源文件、库文件等)在内存中的占用情况,包括代码段、数据段等的大小和地址。
- 符号信息: 列出了程序中定义和引用的符号,有助于了解各个符号在内存中的分布情况。
- 调用关系: 提供了函数之间的调用关系,有助于理解程序的执行流程。
- 优化代码: 开发者可以根据MAP文件分析结果,对代码进行优化,减小存储占用,提高执行效率。
MAP文件是在调试和优化阶段非常有用的工具,通过分析这些信息,开发者可以更好地理解程序在内存中的分布,从而优化代码、解决内存相关问题。
MAP文件组成
MAP文件通常由以下几个主要部分组成:
-
程序段交叉引用关系:
- 描述各个源文件(.c、.s等)之间的函数调用关系。
- 列出了函数之间的引用关系,帮助理解程序的调用流程。
-
删除映像未使用的程序段:
- 描述工程中未使用的、被删除的冗余程序段(函数/数据)。
- 帮助开发者了解工程中存在的冗余代码,有助于优化代码。
-
映像符号表:
- 描述各个符号在存储器中的地址、类型、大小等信息。
- 包括函数、变量等的地址和占用大小。
-
映像内存分布图:
- 描述各个程序段(函数)在存储器中的地址及占用大小。
- 显示了程序在FLASH和RAM等存储器中的布局。
-
映像组件大小:
- 汇总整个映像代码(.o文件)占用的空间信息。
- 给出了程序的总体大小、代码段大小、数据段大小等信息。
这些信息有助于开发者深入了解程序在内存中的布局和分布情况,通过MAP文件,可以更好地进行代码优化、查找冗余代码、解决内存相关的问题。
MAP文件实操
学会分析:哪个.c占用flash 和ram比较大,以便针对性的优化
map 文件的 MDK 设置
设置好 MDK 以后,我全编译当前工程,当编译完成后(无错误),就会生成.map
文件。在 MDK 里面打开 .map
文件的方法如图所示:
- 先确保工程编译成功(无错误)。
- 双击 LED,打开.map 文件。
- map 文件打开成功。
二、STM32启动过程
2.1、STM32启动模式(F1/F4/F7/H7)(也称自举模式)
启动模式,也被称为自举模式(bootstrapping mode),是指在单片机复位后执行的初始化和启动操作。对于STM32和许多其他微控制器,启动模式包括以下几个关键步骤:
-
取出堆栈指针 MSP 的初始值:
- 从地址0x0000 0000处读取的第一个字(32位)是堆栈指针的初始值。这个值表示了栈的初始位置。
-
取出程序计数器指针 PC 的初始值:
- 从地址0x0000 0004处读取的第二个字是程序计数器的初始值,即复位向量。这个值指示了执行程序的起始地址。
-
地址映射:
- 注意到芯片厂商可能会将0x0000 0000和0x0000 0004地址映射到其他地址。这是由于一些芯片允许在不同的启动模式下加载不同的固件。这样,启动模式可以选择不同的起始地址。
在STM32中,这些初始值是由芯片上的启动加载器(Bootloader)提供的,该加载器通常存储在固定的引导区域中。通过在这个引导区域中设置不同的启动模式标志位,可以选择加载不同的程序或固件。
这个启动过程确保了在复位后,程序能够正确地开始执行,并且栈和程序计数器都被正确初始化。
STM32启动模式(F1)
STM32启动模式(F4)
STM32启动模式(F7)
STM32启动模式(H7)
2.2、STM32启动过程(内部FLASH启动为例)
STM32启动过程的步骤详细说明:
-
Reset复位:
- 当STM32芯片上电或复位时,处理器会进入复位状态,即复位向量。在复位状态下,处理器会执行一系列初始化步骤来确保正常运行。
-
获取MSP值0X0800 0000:
- 复位向量的第一个字(32位)包含堆栈指针(MSP - Main Stack Pointer)的初始值。处理器将该值加载到堆栈指针寄存器(SP)中。
-
获取PC值0X0800 0004:
- 复位向量的第二个字包含程序计数器(PC - Program Counter)的初始值,即复位向量。处理器将该值加载到程序计数器寄存器(PC)中。
-
Reset_Handler:
- 复位向量中的复位向量指向Reset_Handler。这是一个特殊的函数,由启动文件(通常是startup_stm32xxx.s)提供。Reset_Handler 会执行一些处理器的基本初始化,然后跳转到 main 函数。
-
启动文件 startup_stm32xxx.s:
- 启动文件是一个汇编文件,包含处理器初始化和启动的汇编代码。它负责初始化数据段、BSS 段、调用系统初始化等。
-
main函数:
- 一旦启动文件执行完毕,它将跳转到 main 函数。在 main 函数中,用户可以编写他们的应用程序代码。
这个启动过程确保了在复位后,程序能够正确地开始执行。内部FLASH启动是默认的启动模式,但STM32芯片通常支持其他启动模式,例如通过串口或外部存储器引导。在这种情况下,复位向量和启动文件可能会有所不同。
启动文件介绍
对STM32启动过程的这些关键步骤概述。以下是对每个步骤的一些进一步的解释:
-
初始化MSP:
- MSP(Main Stack Pointer)是处理器的主堆栈指针,指向主堆栈区的顶部。在启动时,它的初始值通常是存储在复位向量表的第一个位置(0x0800 0000)的值。这个值通常是设备的系统内存(RAM)的顶部。
-
初始化PC:
- PC(Program Counter)是处理器的程序计数器,指向要执行的下一条指令的内存地址。在启动时,PC 的初始值存储在复位向量表的第二个位置(0x0800 0004)。这个值通常是 Reset_Handler 函数的地址,它是启动文件中的一个特殊函数。
-
设置堆栈大小:
- 堆栈和堆是两个不同的内存区域。在启动时,通常设置堆栈大小和堆大小。这些值由链接脚本(.ld 文件)定义,然后由链接器使用。
-
初始化中断向量表:
- 中断向量表是一系列指向中断服务例程(ISR)的指针。在启动时,这些指针的初始值存储在复位向量表中。
__Vectors
是一个包含这些指针的数组。
- 中断向量表是一系列指向中断服务例程(ISR)的指针。在启动时,这些指针的初始值存储在复位向量表中。
-
调用初始化函数:
- SystemInit 函数是一个可选的函数,用于执行系统级的初始化。这可能包括设置系统时钟、中断优先级等。
-
调用 __main:
__main
是标准 C 库函数,它执行一系列的设置,并最终调用main
函数。在用户代码中,main
函数是程序的起始点。
这些步骤确保了在复位后,处理器能够按照预期的方式执行程序。这个过程是由启动文件和链接脚本控制的,具体的细节可能会因设备型号和编译环境而有所不同。
Reset_Handler函数介绍
这段汇编代码是 Cortex-M 处理器的启动代码,主要完成了以下几个步骤:
-
导出 Reset_Handler 函数:
EXPORT
关键字用于标明Reset_Handler
函数是一个全局可见的标识符,可以被其他模块引用。 -
声明 __main 和 SystemInit 函数:
IMPORT
关键字用于声明__main
和SystemInit
函数,这两个函数通常是由编译器生成的。 -
加载 SystemInit 函数的地址到 R0 寄存器: 使用
LDR
指令将SystemInit
函数的地址加载到 R0 寄存器。 -
通过 BLX 指令调用 SystemInit 函数:
BLX
指令用于调用函数,这里将通过 R0 寄存器中的地址调用SystemInit
函数。这个函数通常包含了系统初始化的相关操作。 -
加载 __main 函数的地址到 R0 寄存器: 使用
LDR
指令将__main
函数的地址加载到 R0 寄存器。 -
通过 BX 指令跳转到 __main 函数:
BX
指令用于跳转到指定地址的子程序,这里将跳转到__main
函数,即 C 语言程序的入口点。 -
定义 Reset_Handler 过程结束:
ENDP
关键字用于表示 Reset_Handler 过程的结束。 -
弱定义标记:
WEAK
关键字表示这是一个弱定义,如果在其他地方有相同的函数定义,链接器可以选择其中一个。在这里,如果系统提供了自定义的SystemInit
函数,它将被使用;否则,使用默认的SystemInit
函数。
这段代码是启动文件中的一部分,它负责初始化系统,并跳转到主程序的入口点。
堆栈简介
堆栈是在程序运行时动态分配和管理内存的两个主要区域。这两个区域分别是:
-
栈(Stack):
- 由编译器自动分配和释放。
- 用于存放函数参数、局部变量等。
- 具有后进先出(LIFO)的结构,即最后入栈的数据最先出栈。
- 栈的大小在编译时就已经确定,通常比较有限,如果函数调用层次太深或者局部变量较多,可能导致栈溢出。
-
堆(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 文件)中定义和使用。
三、总结