在基于STM32芯片的工程代码中,有一个非常重要的文件,即启动文件。该文件主要由汇编语言编写,文件名以.s结尾。这个文件在芯片程序运行之前首先要执行,它的功能是为用户程序的执行做一些基本准备工作,比如初始化栈和堆,配置时钟系统的默认设置,定义和分配中断向量表等。
在网络上有一些文章对启动文件进行了详细的介绍,但在这里我们只讨论其中的几个小问题。请注意,下面的讨论是基于基于STM32F429芯片的工程,使用ARM MDK作为开发环境。
怎样才能在编译后的MAP文件中看到变量__heap_base呢?
有人发现,在启动文件中明确定义了__initial_sp和__heap_base这两个变量,但在编译后生成的MAP文件中只能看到__initial_sp的地址分配,而找不到__heap_base的记录。这是为什么呢?
原因是我们当前的工程代码中并未涉及到堆的操作。虽然启动文件中对堆进行了配置,但由于实际代码中没有使用堆,编译时没有分配堆的地址相关信息,导致在MAP文件中看不到__heap_base的记录。
以上图为例,其实此时Heap_Size写多大是没啥实际意义的,写0x200和写0没差别。这就像我们在代码定义一些完全不会被用到的变量一样,编译时是不会被安排内存空间的。
然而,如果我们在代码里有涉及HEAP操作,若启动文件里的HEAP配置依然如上图所示,那情况就不一样了。比方我们在用户代码做动态内存分配,这里使用malloc函数进行动态内存分配示例下。
这时我们再去查看MAP文件,就可看到堆的地址信息了,跟启动文件里分配的一致。
顺便提醒下,我们在用户代码里做内存动态分配时注意分配的空间大小要遵循启动文件里预设的HEAP大小限制,必要时需做适当调整。
启动文件里怎么用掉了1KB的RAM?
有人在查看STM32程序代码编译后的MAP文件时,发现启动文件就用掉了1024B的RAM,如下图所示。觉得有点奇怪,想知道这1KB用到哪里去了?是不是固定的?
我们知道启动文件主要基于汇编写成,实现些最基本的软硬件初始化工作,似乎用不到这么多RAM。
其实,这里1KB初始值为0的RAM,是安排给栈用的,而这个栈大小的配置就是在启动文件里实现,但并非仅限于用在启动文件里。MAP文件里显示的启动文件所用ZI数据大小,跟下面栈配置是关联的。
显然,这个RAM开销并非固定的。
尽管我们建立工程时可能有些默认配置或经验配置,但我们完全可以结合自身工程代码的需要灵活调整。如果说,代码里函数调用涉及到的局部变量较少、中断嵌套情形也不多,你完全可以将栈数据设计得小些,或许很多时候512B【0x200】都绰绰有余了。总之,这个数据不是固定不变的,具体开发时按需调整。
比方,我将上面的栈空间大小改为512B,再看看编译后的结果。那个ZI Data大小也随之而变了。
上面是基于栈空间大小的调整来解释启动文件里ZI数据的大小变化。如果说我们的代码里还用到堆,这时启动文件里的ZI数据就不仅仅是栈空间大小的数据了,还会包括堆空间的大小。比方,我们在代码里启用动态内存分配使用到堆。在启动文件里对堆、栈的配置如下:
按照上面配置并启用动态内存分配,再去查看编译结果,基于启动文件所用到的ZI数据大小变成了栈与堆空间二者之和。如下图所示【1024+512=1536】:
启动文件里的RO Data是怎么产生的?
有人在查看MAP文件时,发现启动文件里产生了一批RO只读数据,如下图所示:
上图是基于STM32F429的工程编译后产生的,那个428 Bytes只读数据怎么来的?
其实,这个数据就是存放中断向量地址表所用到的,不同的系列这个数据会有差异。该向量表除了第一个字单元存放MSP栈顶地址外,其它均为系统异常或中断入口地址,作为常量数据存放在FLASH里。我们具体看看这里的428B怎么来的。打开启动文件,我们可以看到一串连续的DCD操作,如下图所示:
以STM32F429为例,在启动文件里稍微数数可得知这里共有107个地址入口项,每项用到一个4字节字,刚好对应上面的428 Bytes.
启动文件里的__main函数跟用户的main()有关系吗?
我们知道,启动文件的主要功能就是为用户程序的正常运行做最基本的初始化准备工作,__main()函数就是完成该使命的重要一环。
它是一个C库初始化函数入口,主要负责执行一些必要的代码及数据从装载区到执行区的拷贝,将ZI内存区的数据初始化为0。对C库函数进行初始化,初始化堆、栈等,有时还可能包括一些代码解压操作,最后跳转至用户man()函数运行用户程序。
一般来讲,关于这个__main()函数的功能及作用大致了解就好,通常将其视为黑盒子。
启动文件里对中断矢量表起始地址进行初始化是在哪里实现的?
在STM32 MCU家族里,除了基于Cortex M0内核的STM32F0系列外,都有个中断矢量寄存器【SCB->VTOR】用来初始化中断矢量表的起始地址。它的初始化一般在启动文件的复位程序里的SystemInit()函数完成。
最后基于该话题顺便给些提醒作为本篇结尾。我们在基于STM32芯片做IAP应用时,对于APP代码记得做好VTOR的重定位【注:F0系列操作例外】;在从BOOT区跳转到APP区之前先将刚才开启过的所有中断使能都禁用掉;如果开启了Cache的话,也将Cache禁用掉;保证跳转时清清爽爽,不捎一滴水,不带一片云。
以上就是良许教程网为各位朋友分享的Linu系统相关内容。想要了解更多Linux相关知识记得关注公众号“良许Linux”,或扫描下方二维码进行关注,更多干货等着你 !