IAR编译器如何节省代码占用的flash空间
最近一直在做一个项目,用的IAR的开发环境,芯片的空间flash大小是32kB。在做项目之前评估代码量感觉够用,因此切换平台时选用了32kB的M0+核芯片。可是代码不是你想多大就多大。
所以问题就来了,当芯片的flash空间不足以支撑你的代码量时,如何通过编译优化等等各种手段缩小代码的占用空间呢?
常用的手段有以下几种:
- 优化代码
- 尝试编译器编译优化
- 更改芯片默认配置,最大限度利用芯片空间——扩大芯片可用空间!
本文主要针对第三种手段说一下如何在芯片层面上“扩大空间”。
优化代码
这一点需要根据你的代码实际情况来优化,删除一下冗余的代码片段、尝试一个接口函数兼容多种类似功能、更改循环逻辑等等。这个需要自己对代码有深刻的理解。
尝试编译器优化
这一点说实话有风险。以我用的IAR编译器为例,有以下四种优化等级:
自己实测后发现,不优化和最高等级优化编译后的代码量还是有很大区别的。但是自己测试发现最高优化程序运行没有问题。但是有同事遇到过最高优化后程序无法正常运行的情况。因此,采用何种优化等级,需要自己去测试。这一点涉及到编译器的知识,不多赘述。
更改芯片默认配置,最大限度利用芯片空间——扩大芯片可用空间!
主要说一下这点。
我相信每个芯片或多或少在存储的时候都会因为芯片的配置导致代码不能完全利用空间。
举个例子:
32kB的空间芯片,flash地址从0到0x7FFF,一般在下载程序的时候,编译器会预编译,把不同的代码分配到不同的区,比如向量表一般从0开始,代码区在后面。这样便有一个现象是向量表和代码区之间存在空隙,这样就浪费掉了一部分空间。
如上图,有一块区域为全FF,这块区域并没有装载任何代码。白白浪费了。
这块区域是怎么产生的呢?这就不得不提利用IAR编译时的一个文件:.icf
文件。
下面是.icf
文件。
/*###ICF### Section handled by ICF editor, don't touch! ****/
/*-Editor annotation file-*/
/* IcfEditorFile="$TOOLKIT_DIR$\config\ide\IcfEditor\cortex_v1_0.xml" */
/*-Specials-*/
define symbol __ICFEDIT_intvec_start__ = 0x00000000;
/*-Memory Regions-*/ // ###定义ROM区块和RAM 区块
define symbol __ICFEDIT_region_ROM_start__ = 0x00000000;
define symbol __ICFEDIT_region_ROM_end__ = 0x00007FFF;
define symbol __ICFEDIT_region_RAM_start__ = 0x10000000;
define symbol __ICFEDIT_region_RAM_end__ = 0x10001FFF;
/*-Sizes-*/ // 定义堆栈
define symbol __ICFEDIT_size_cstack__ = 0x400;
define symbol __ICFEDIT_size_heap__ = 0x800;
/**** End of ICF editor section. ###ICF###*/
// 定义CRP区块
define symbol __CRP_start__ = 0x000002FC;
define symbol __CRP_end__ = 0x000002FF;
define memory mem with size = 4G;
define region ROM_region = mem:[from __ICFEDIT_region_ROM_start__ to __ICFEDIT_region_ROM_end__] - mem:[from __CRP_start__ to __CRP_end__];
define region RAM_region = mem:[from __ICFEDIT_region_RAM_start__ to __ICFEDIT_region_RAM_end__];
define region CRP_region = mem:[from __CRP_start__ to __CRP_end__];
define block CSTACK with alignment = 8, size = __ICFEDIT_size_cstack__ { };
define block HEAP with alignment = 8, size = __ICFEDIT_size_heap__ { };
initialize by copy { readwrite };
do not initialize { section .noinit };
place at address mem:__ICFEDIT_intvec_start__ { readonly section .intvec };
place in ROM_region { readonly };
place in RAM_region { readwrite,
block CSTACK, block HEAP };
place in CRP_region { section .crp };
代码中除了定义了常见的ROM区块和RAM区之外,还有一部分向量表和堆栈区。
我们仔细观察会发现有一块用于定义CRP
区块如下:
// 定义CRP区块
define symbol __CRP_start__ = 0x000002FC;
define symbol __CRP_end__ = 0x000002FF;
而结合全FF的区域可以看到,全FF区域结束的地址刚好是CRP区块定义的结束地址,也就是
define symbol CRP_end = 0x000002FF;
看到这,一个自然的想法便是如果CRP
不用的话,可以把他取消,这样不就有了更多的空间了么?
但是在不明白CRP
为何物的情况下,建议不要贸然更改,否则可能会引起不必要的错误。
查看芯片手册。
可以看到原来是代码保护功能。但是CRP只是占用了从0X2FC到0X2FF的四个字节长度,为什么0X2FC之前也会有这么多空间空闲着呢?
这就是编译器编译的问题了。
原来IAR编译器在对代码编译完成后,会根据.icf
文件中的相应区块设置分配代码。
从零地址开始为向量表地址。之后为CRP
地址,再之后为我们的代码区块地址。
这样因为我们定义的CRP
起始地址为0X2FC,而向量表代码量又太少不会占满0X2FC之前的所有空间,所以这块空间便被空闲下来了。
我们的目的是把着跨区域充分利用起来,那么这个问题有没有办法解决呢?
答案是肯定的!
有人说我们可以把CRP
的起始地址定义的靠前点不就行了。但是,查看芯片手册你会发现,这个地址是芯片的默认地址,芯片物理机制只认这个地址,因此你更改了之后,如果在0x2FC~0X2FF之间写入了其他数,有可能会对代码造成不可避免的灾难。
因此我们想到在代码中利用常量将这块区域占住。如下:
#pragma location = 0x000002FC
__root const uint32_t crp= 0xFFFFFFFF;// @".MYTEST";
这样的话从0X2FC~0X2FF这块其余被我们填充了FFFFFFFF,编译器便不会往这块区域分配代码了。
但是仅仅做这点改动,会发现代码区仍然如此,并没有改变分配,只是仅仅把CRP
区块填充了FFFF而已。
在回过头来观察.icf
文件,会发现在编译的时候IAR会根据自己设置的ROM区大小分配地址。如下:
define symbol __ICFEDIT_intvec_start__ = 0x00000000;
/*-Memory Regions-*/ // ###定义ROM区块和RAM 区块
define symbol __ICFEDIT_region_ROM_start__ = 0x00000000;
define symbol __ICFEDIT_region_ROM_end__ = 0x00007FFF;
向量表的起始地址被定义为
intvec_start__ = 0x00000000;
ROM区被定义为0x0000~0x7FFF。由于我的代码达不到7FFF这个地址,这样编译在编译的时候会认为空间足够大,会优先从0x300也就是CRP
后面开始放代码。这样会造成CRP
前面直到向量表都是空闲的。
此时我们只需要把ROM空间分配的小一点,迫使编译器寻找CRP
之前的空闲区域分配代码即可。
如
define symbol __ICFEDIT_intvec_start__ = 0x00000000;
/*-Memory Regions-*/ // ###定义ROM区块和RAM 区块
define symbol __ICFEDIT_region_ROM_start__ = 0x00000000;
define symbol __ICFEDIT_region_ROM_end__ = 0x00000C00;
因为我的代码在编译完之后大约不到3kB的大小。因此我设置ROM结束地址为0xC00,这样编译器会寻找0xC00之前所有的空闲区域放置代码。
下图为我测试的结果。
可以看到相比之前,空闲FF的区域少了很多。而且代码都分配在了0xC00之前。
利用这种方法可以有效的释放未使用的空间,做到ROM空间最大化。
从而扩大芯片可用空间!!!
最后一点需要注意的。设置完.icf
文件后,需要将.icf
文件设置为每次编译均覆盖。具体如下。
这样便可以完美实现空间再利用。