本帖最后由 正点原子运营 于 2024-6-14 10:53 编辑
1)实验平台:正点原子 精英STM32F103开发板
2) 章节摘自【正点原子】STM32F103开发指南 V1.3
3)购买链接:https://detail.tmall.com/item.htm?&id=609294889447
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boar ... 103_jingyingV2.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子STM32技术交流QQ群:672399978
9.2.3 系统启动流程 我们知道启动模式不同,启动的起始地址是不一样的,下面我们以代码下载到内部FLASH的情况举例,即代码从地址0x08000000开始被执行。 当产生复位,并且离开复位状态后,CM3内核做的第一件事就是读取下列两个32位整数的值: (1)从地址 0x0800 0000 处取出堆栈指针MSP 的初始值,该值就是栈顶地址。 (2)从地址 0x0800 0004 处取出程序计数器指针PC 的初始值,该值指向中断服务程序 Reset_Handler。下面用示意图表示,如图9.2.3.1所示。 file:///C:/Users/ALIENTEK/AppData/Local/Temp/msohtmlclip1/01/clip_image002.jpg 我们看看STM32F103开发板HAL库例程的实验1 跑马灯实验中,取出的MPS和PC的值是多少,方法如图9.2.3.2所示。 由图9.2.3.2可以知道地址0x0800 0000的值是0x2000 0788,地址0x0800 0004的值是0x0800 01CD,即堆栈指针 SP =0x2400 0BD8,程序计数器指针PC = 0x08000339(即复位中断服务程序Reset_Handler的入口地址),即。因为CM3内核是小端模式,所以倒着读。 请注意,这与传统的 ARM 架构不同——其实也和绝大多数的其它单片机不同。传统的ARM 架构总是从 0 地址开始执行第一条指令。它们的 0 地址处总是一条跳转指令。而在 CM3内核中,0 地址处提供 MSP 的初始值,然后就是向量表(向量表在以后还可以被移至其它位置)。向量表中的数值是 32 位的地址,而不是跳转指令。向量表的第一个条目指向复位后应执行的第一条指令,就是Reset_Handler这个函数。下面继续以正点原子STM32F103MINI 开发板HAL库例程的实验1跑马灯实验为例,代码从地址0x0800 0000开始被执行,讲解一下系统启动,初始化堆栈、MSP和PC后的内存情况。 图9.2.3.3 初始化堆栈、MSP和PC后的内存情况 因为CM3使用的是向下生长的满栈,所以MSP的初始值必须是堆栈内存的末地址加1。 举例来说,如果你的栈区域在0x20000388�0x2000 0787(1KB大小)之间,那么MSP的初始值就必须是0x20000788。 向量表跟随在MSP的初始值之后——也就是第2个表目。 R15是程序计数器,在汇编代码中,可以使用名字“PC”来访问它。ARM规定:PC最低两位并不表示真实地址,最低位LSB用于表示是ARM指令(0)还是Thumb指令(1),因为 CM3 主要执行 Thumb指令,所以这些指令的最低位都是1(都是奇数)。因为 CM3 内部使用了指令流水线,读 PC 时返回的值是当前指令的地址+4。比如说: 0x1000: MOV R0, PC ; R0 = 0x1004 如果向PC写数据,就会引起一次程序的分支(但是不更新LR寄存器)。CM3中的指令至少是半字对齐的,所以PC的LSB总是读回 0。然而,在分支时,无论是直接写PC的值还是使用分支指令,都必须保证加载到PC的数值是奇数(即LSB=1),表明是在Thumb状态下执行。倘若写了0,则视为转入ARM模式,CM3将产生一个fault异常。 正因为上述原因,图9.2.3.3中使用0x0800 01CD来表达地址0x0800 01CC。当0x0800 01CD 处的指令得到执行后,就正式开始了程序的执行(即去到C的世界)。所以在此之前初始化 MSP 是必需的,因为可能第1条指令还没执行就会被 NMI 或是其它 fault 打断。MSP 初始化好后就已经为它们的服务例程准备好了堆栈。 STM32启动文件分析就给大家介绍到这里,更多内容请看《STM32启动文件浅析》。 9.3 map文件分析 9.3.1 MDK编译生成文件简介 MDK编译工程,会生成一些中间文件(如.o、.axf、.map 等),最终生成hex文件,以便下载到MCU上面执行,以STM32F103开发板HAL库例程的实验1跑马灯实验为例(其他开发板类似),编译过程产生的所有文件,都存放在 Output文件夹下,如图9.3.1.1 所示: 可以看到,总共生成了43个文件,11个类型,分别是:.axf、.crf、.d、.dep、.hex、.lnp、.lst、.o、.htm、bulild_log.htm 和.map。43个文件(勾选Browse informatio-n时为59个)看着不是很多,但是随着工程的增大,这些文件也会越来越多,大项目编译一次,可以生成几百甚至上千个这种文件,不过文件类型基本就是上面这些。 对于MDK工程来说,基本上任何工程在编译过程中都会有这11类文件,常见的MDK编译过程生产文件类型如表 9.3.1.1所示: 注 1,可重定向是指该文件包含数据/代码,但是并没有指定地址,它的地址可由后续链接的时候进行指定。 注 2,不可重定向是指该文件所包含的数据/代码都已经指定地址了,不能再改变。 9.3.2 map文件分析 .map文件是编译器链接时生成的一个文件,它主要包含了交叉链接信息。通过.map文件,我们可以知道整个工程的函数调用关系、FLASH和RAM占用情况及其详细汇总信息,能具体到单个源文件(.c/.s)的占用情况,根据这些信息,我们可以对代码进行优化。.map 文件可以分为以下5个组成部分: 1,程序段交叉引用关系(Section Cross References) 2,删除映像未使用的程序段(Removing Unused input sections from theimage) 3,映像符号表(Image Symbol Table) 4,映像内存分布图(Memory Map of the image) 5,映像组件大小(Image component sizes) 9.3.2.1 map文件的MDK设置 要生成map文件,我们需要在MDK的魔术棒→Listing选项卡里面,进行相关设置,如图9.3.2.1.1所示: file:///C:/Users/ALIENTEK/AppData/Local/Temp/msohtmlclip1/01/clip_image010.jpg 图9.3.2.1.1中红框框出的部分就是我们需要设置的,默认情况下,MDK 这部分设置就是全勾选的,如果我们想取消掉一些信息的输出,则取消相关勾选即可(一般不建议)。 如图9.3.2.1.1设置好MDK以后,我全编译当前工程,当编译完成后(无错误),就会生成.map文件。在MDK里面打开.map 文件的方法如图9.3.2.1.2所示: file:///C:/Users/ALIENTEK/AppData/Local/Temp/msohtmlclip1/01/clip_image012.jpg 1,先确保工程编译成功(无错误)。 2,双击LED,打开.map文件。 3,map文件打开成功。 9.3.2.2 map文件的基础概念 为了更好的分析map文件,我们先对需要用到的一些基础概念进行一个简单介绍,相关概念如下: Section:描述映像文件的代码或数据块,我们简称程序段 RO:ReadOnly 的缩写,包括只读数据(RO data)和代码(RO code)两部分内容,占用 FLASH 空间 RW:ReadWrite 的缩写,包含可读写数据(RW data,有初值,且不为 0),占用 FLASH(存储初值)和 RAM(读写操作) ZI:Zeroinitialized 的缩写,包含初始化为 0 的数据(ZI data),占用 RAM 空间。 .text:相当于 RO code .constdata:相当于 RO data .bss:相当于 ZI data .data:相当于 RW data 9.3.2.3 map文件的组成部分说明 我们前面说map 文件分为5个部分组成,下面以STM32F103开发板HAL库例程的实验1跑马灯实验为例,简要讲解一下。 1.程序段交叉引用关系(Section Cross References) 这部分内容描述了各个文件(.c/.s 等)之间函数(程序段)的调用关系,举个例子如图9.3.2.3.1所示: file:///C:/Users/ALIENTEK/AppData/Local/Temp/msohtmlclip1/01/clip_image014.jpg 上图中,框出部分: main.o(i.main)refers to sys.o(i.sys_stm32_clock_init) for sys_stm32_clock_init表示:main.c 文件中的 main 函数,调用了 sys.c 中的 sys_stm32_clock_init 函数。其中:i.main表示 main 函数的入口地址,同理 i. sys_stm32_clock_init 表示 sys_stm32_clock_init函数的入口地址。 2. 删除映像未使用的程序段(Removing Unused input sections from the image) 这部分内容描述了工程中由于未被调用而被删除的冗余程序段(函数/数据),如图9.3.2.3.2 所示: file:///C:/Users/ALIENTEK/AppData/Local/Temp/msohtmlclip1/01/clip_image016.jpg file:///C:/Users/ALIENTEK/AppData/Local/Temp/msohtmlclip1/01/clip_image018.jpg 上图中,列出了所有被移除的程序段,比如usart.c里面的usart_init函数就被移除了,因为该例程没用到usart_init函数。 另外,在最后还有一个统计信息:216 unused section(s) (total15556bytes) removed from the image. 表示总共移除了216个程序段(函数/数据),大小为15556字节。即给我们的 MCU 节省了 15556字节的程序空间。 为了更好的节省空间,我们一般在 MDK→魔术棒→C/C++选项卡里面勾选:One ELFSection per Function,如图 9.3.2.3.3所示: file:///C:/Users/ALIENTEK/AppData/Local/Temp/msohtmlclip1/01/clip_image020.jpg 图9.3.2.3.3 MDK 勾选 One ELF Section per Function 3. 映像符号表(Image Symbol Table) 映像符号表(Image Symbol Table)描述了被引用的各个符号(程序段/数据)在存储器中的存储地址、类型、大小等信息。映像符号表分为两类:本地符号(Local Symbols)和全局符号(Global Symbols)。 本地符号(Local Symbols)记录了用 static 声明的全局变量地址和大小,c文件中函数的地址和用static声明的函数代码大小,汇编文件中的标号地址(作用域:限本文件)。 全局符号(Global Symbols)记录了全局变量的地址和大小,C文件中函数的地址及其代码大小,汇编文件中的标号地址(作用域:全工程)。 4. 映像内存分布图(Memory Map of the image) 映像文件分为加载域(Load Region)和运行域(Execution Region)。一个加载域必须有至少一个运行域(可以有多个运行域),而一个程序又可以有多个加载域。加载域为映像程序的实际存储区域,而运行域则是 MCU上电后的运行状态。加载域和运行域的简化关系(这里仅表示一个加载域的情况)图,如图9.3.2.3.4所示: file:///C:/Users/ALIENTEK/AppData/Local/Temp/msohtmlclip1/01/clip_image022.png 由图可知,RW区也是存放在 ROM(FLASH)里面的,在执行main函数之前,RW(有初值且不为0的变量)数据会被拷贝到RAM区,同时还会在RAM里面创建ZI区(初始化为0的变量)。 5. 映像组件大小(Image component sizes) 映像组件大小(Image component sizes)给出了整个映像所有代码(.o)占用空间的汇总信息。这部分是程序实际功能可执行代码的存储空间。 由于篇幅较长,更多内容请大家查阅《STM32 MAP文件浅析》文档的内容。
|