目录
1、MAP文件浅析
1、1 map文件的MDK设置
1、2 .map文件的组成
1、3 .map文件解析
2、ARM单片机启动流程
2、1 STM32的启动模式
2、2 STM32的启动流程
2、3 初始化堆栈指针
2、4 中断向量表
2、5 跳转复位函数
2、6 总结
1、MAP文件浅析
MDK编译工程,会生成一些中间文件(如.o、.axf、.map等等),最终生成.hex文件,以方便下载到MCU上执行,一共有十一种,我们只需要了解其中的几个就足够了
而.map文件是编译器链接时生成的一个文件,它主要包含了交叉编译信息,通过.map文件我们可以知道整个工程的函数调用关系、Flash和RAM占用情况及其详细汇总信息,能具体到单个源文件(.c\.h)的具体占用情况,根据这些信息我们可以对代码进行优化
1、1 map文件的MDK设置
要生成 map 文件,我们需要在 MDK 的魔术棒→Listing选项卡里面,进行相关设置,默认情况是全选的,如果有不需要的可以去掉勾选,然后我们就可以在我们生成的工程目录下找到.map文件并且打开
1、确保编译无误
2、双击打开目标工程,打开.map文件
3、对.map文件进行查看
1、2 .map文件的组成
MAP文件通常由以下几个主要部分组成:
1、程序段交叉引用关系:
描述各个源文件(.c、.s等)之间的函数调用关系。
列出了函数之间的引用关系,帮助理解程序的调用流程。
2、删除映像未使用的程序段:
描述工程中未使用的、被删除的冗余程序段(函数/数据)。
帮助开发者了解工程中存在的冗余代码,有助于优化代码。
3、映像符号表:
描述各个符号在存储器中的地址、类型、大小等信息。
包括函数、变量等的地址和占用大小。
4、映像内存分布图:
描述各个程序段(函数)在存储器中的地址及占用大小。
显示了程序在FLASH和RAM等存储器中的布局。
5、映像组件大小:
汇总整个映像代码(.o文件)占用的空间信息。
给出了程序的总体大小、代码段大小、数据段大小等信息。
为了更好的分析 map 文件,我们先对需要用到的一些基础概念进行一个简单介绍,相关概念如下:
Section:描述映像文件的代码或数据块,我们简称程序段
RO:Read Only 的缩写,包括只读数据(RO data)和代码(RO code)两部分内容占用 FLASH 空间
RW:Read Write 的缩写,包含可读写数据(RW data,有初值,且不为 0),占用 FLASH(存储初值)和RAM(读写操作)
ZI:Zero initialized 的缩写,包含初始化为0的数据(ZIdata), 占用 RAM 空间。
.text: 相当于 RO code
.constdata: 相当于 RO data
.bss: 相当于 ZI data
.data: 相当于RW data
1、3 .map文件解析
我们根据1、2中介绍的.map文件组成来一个个解析他们在.map文件中是如何对应的
1、程序段交叉引用关系:
下面这张图就很详细的介绍了 main文件调用了哪些函数,具体出自于哪些文件,这个了解一下能看懂即可
2、删除映像未使用的程序段:
每一部分开始前前面都有提示,这一部分主要是一些没有被调用的函数,被系统删掉了,节约出的内存大小也写在后面了,也是列举出了哪个文件哪个函数没有用到,节约的字节大小是多少
3、映像符号表:
这个表主要体现函数的入口地址,分为局部和全局的,局部的则为作用域仅限于当前文件可调用的函数,全局的则为工程内全部文件可调用的
红框里的函数类型为:Section(程序段),大小为0。因为” . i “仅仅表示该函数的函数入口地址,并不是指令,所以没有大小。在全局符号段,会列出该函数的大小。
4、映像内存分布图:
这一部分了解一下怎么看就行,1和2都是入口地址,3是占用大小,4是代码的意思,5则是属性,6不用管
5、映像组件大小:
我们用的最多的就是这个映射组件的大小,他给出了整个映像所有代码(.o)的所占用内存的汇总信息,对于我们来说比较有用
上图中,框出的三处信息对我们比较有用,接下来分别介绍:
① 处,表示.c/.s文件生成对象所占空间大小(单位:字节,下同),即.c/.s文件编译后所占代码空间的大小。每个项所代表的意义如下 :
Code(inc.data) : 表示包含内联数据(inc.data)后的代码大小。
如 delay.o(即delay.c)所占的 Code 大小为 142 字节,其中8字节是内联数据。
R0 Data : 表示只读数据所占的空间大小,一般是指 const 修饰的数据大小。
RW Data : 表示有初值(且非 0)的可读写数据所占的空间大小,它同时占用 FLASH和 RAM 空间。
ZI Data : 表示初始化为0的可读写数据所占空间大小,它只占用 RAM 空间。
Debug : 表示调试数据所占的空间大小,如调试输入节及符号和字符串
Object Totals : 表示以上部分链接到一起后,所占映像空间的大小。
(inc1.Generated): 表示链接器生产的映像内容大小,它包含在 0bject Totals 里面了,这里仅仅是单独列出,我们一般不需要关心。
(inc1.Padding):表示链接器根据需要插入填充以保证字节对齐的数据所占空间的大小,它也包含在 0bject Totals 里面了,这里单独列出,一般无需关心。
② 处,表示被提取的库成员(.1ib)添加到映像中的部分所占空间大小。各项意义同①中的说明。我们一般只用看 Library Totals 来分析库所占空间的大小即可。
③ 处,表示本工程全部程序汇总后的占用情况。其中:
Grand Totals:表示整个映像所占空间大小。
ELF Image Totals: 表示 ELF 可执行链接格式映像文件的大小,一般和 Grand Totals一样大小。
ROM Totals: 表示整个映像所需要的 ROM 空间大小,不含 ZI 和 Debug 数据。
Total RO Size:表示 Code 和 RO 数据所占空间大小,本例程为:13452 字节。
Total Rw Size:表示 RW 和 ZI数据所占空间大小,即本映像所需 SRAM 空间的大小,本例程为:3032 字节。
Total ROM Size:表示 Code、RO 和 RW 数据所占空间大小,即本映像所需 FLASH空间的大小,本例程为:13484字节。
2、ARM单片机启动流程
2、1 STM32的启动模式
STM32的启动模式是根据两个Boot引脚来决定的,当Boot0等于0等时,他会定位到flash内部来启动代码,而当Boot0等于1时,就是根据Boot1来决定到底从哪里启动代码了,一般来说从flash里执行代码是最常见的启动模式,而从系统存储器启动通常是需要跑bootloader来更新固件,最后一种则是调试的时候可能会用到
2、2 STM32的启动流程
讲完了启动模式,我们知道他有好几种读取代码的方式,但是具体执行哪些流程呢,下面我们以STM32F103举例,在单片机工程里有一个启动文件,他是单片机上电后执行的第一段代码,而不是从我们的main开始执行的,而这个启动文件一般执行这几件事,下图的内容
1、初始化堆栈指针SP
2、初始化PC指针,指向复位程序的入口
3、初始化中断向量表
4、配置系统时钟
5、跳转到main函数开启循环
2、3 初始化堆栈指针
对于启动文件中的栈空间,默认大小是1KB,栈一般存储局部变量,存储函数在调用中传递的参数,保护现场等等,如果写的程序代码比较大,就需要把栈空间调大一些,如果栈空间大小不够,就会导致栈溢出,让程序出现未知问题,栈空间不能超过SRAM的大小
这是堆空间的配置,跟栈空间差不多,了解一下里头的内容即可
2、4 中断向量表
__Vectors 是 STM32 启动文件中定义的中断向量表(Interrupt Vector Table)。它是一个数据表,通常位于 Flash 的起始地址(例如 0x08000000),用于存储一些关键信息,包括堆栈顶部,复位函数,中断回调函数的地址。
第一步具体做什么?
CPU 从 0x08000000 读取第一个字(32 位数据),这个字是栈顶地址(__initial_sp),用来设置堆栈指针(SP)就回到了我们上面初始化堆栈指针的内容了,然后从下一个地址(0x08000004)读取复位处理程序的地址(Reset_Handler),并跳转到那里执行。
向量表存储的全部都是这些中断源的地址,一旦想执行中断里的代码就必须有他的地址才能跳转,这里几乎标注了所有可能遇到问题的中断,其中断地址在数据手册内部全都可查,用户自行了解即可
2、5 跳转复位函数
上电后的流程分别为 启动模式选择 跳转到中断向量表开始执行代码,设置堆栈指针,执行跳转复位函数,而这个复位程序基本上在下图已经解析完毕了,只需要讲讲这个弱定义就行了,后面带weak的全是弱定义函数,这个意思就是我们可以在外部重新定义这个函数,如果程序检测到了有重新定义的函数则执行重新定义的函数,如果没有重新定义的函数则执行弱定义函数
2、6 总结
可以把启动流程想象成“起床去上班”:
上电/复位:闹钟响了,你醒了。
设置堆栈__initial_sp:整理床铺,准备临时放东西的地方。
跳转 Reset_Handler:开始起床流程。
调用 SystemInit:洗漱、穿衣服(准备硬件)。
跳转 __main:吃早餐(准备软件环境)。
调用 main:出门上班(运行你的任务)。
STM32 的启动流程虽然复杂,但拆开看就很简单,弄懂这个过程,你就掌握了芯片的“开机密码”了。